Skip to content

Instantly share code, notes, and snippets.

@felix19350
Last active July 15, 2023 12:42
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save felix19350/bcb39e50820dcc6872f624d2e925dd9a to your computer and use it in GitHub Desktop.
Save felix19350/bcb39e50820dcc6872f624d2e925dd9a to your computer and use it in GitHub Desktop.
Barebones Ktor REST API example
package org.example
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import io.ktor.application.call
import io.ktor.http.HttpStatusCode
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.routing.Route
import io.ktor.routing.get
import io.ktor.routing.post
import kotlinx.coroutines.experimental.Dispatchers
import kotlinx.coroutines.experimental.withContext
import org.jetbrains.exposed.dao.EntityID
import org.jetbrains.exposed.dao.LongEntity
import org.jetbrains.exposed.dao.LongEntityClass
import org.jetbrains.exposed.dao.LongIdTable
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.transactions.transaction
import javax.sql.DataSource
// Data model - data classes for input and output formats
data class MyAppUser(val email: String, val realName: String)
data class CreateMyAppUserCommand(val email: String, val realName: String)
// Core service definition
internal interface MyAppUserService {
suspend fun list(): List<MyAppUser>
suspend fun create(newUserCmd: CreateMyAppUserCommand): MyAppUser
}
// Data store connection setup shennanigans
fun quickNDirtyDb(): DataSource {
return HikariDataSource(HikariConfig().apply {
poolName = "HIKARI-POOL"
driverClassName = "org.h2.Driver"
jdbcUrl = "jdbc:h2:mem:test"
maximumPoolSize = 5
isAutoCommit = false
transactionIsolation = "TRANSACTION_READ_COMMITTED"
validate()
})
}
class DatastoreConnection(private val dataSource: DataSource) {
private val database: Database by lazy {
Database.connect(dataSource)
}
suspend fun <T> query(block: () -> T): T = withContext(Dispatchers.IO) {
transaction(database) {
block()
}
}
}
// Object relational mapping
internal object MyAppUserTable : LongIdTable("my_app_user_table") {
val email = varchar("user_email", 255).uniqueIndex()
val realName = varchar("real_name", 255)
}
internal class MyAppUserDAO(id: EntityID<Long>) : LongEntity(id) {
companion object : LongEntityClass<MyAppUserDAO>(MyAppUserTable)
var email by MyAppUserTable.email
var realName by MyAppUserTable.realName
fun toModel(): MyAppUser {
return MyAppUser(email, realName)
}
}
// Service implementation for datastore of choice
internal class MyAppUserServiceImpl(private val dbc: DatastoreConnection) : MyAppUserService {
override suspend fun list(): List<MyAppUser> {
return dbc.query {
MyAppUserDAO.all().map { it.toModel() }
}
}
override suspend fun create(newUserCmd: CreateMyAppUserCommand): MyAppUser {
return dbc.query {
MyAppUserDAO.new {
this.email = newUserCmd.email
this.realName = newUserCmd.realName
}.toModel()
}
}
}
//REST-ish API
fun Route.sampleApi() {
val service: MyAppUserService = MyAppUserServiceImpl(DatastoreConnection(quickNDirtyDb()))
get("/myUsers") {
call.respond(HttpStatusCode.OK, service.list())
}
post("/myUsers") {
//Don't forget to validate the input...
val newUser = service.create(call.receive())
call.respond(HttpStatusCode.OK, newUser)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment