Help us understand the problem. What is going on with this article?

Kotlin製webアプリフレームワーク Ktor + exposed

More than 1 year has passed since last update.

Easy to use, fun and asynchronous.

Ktorの思想です。

簡単に言えば、100%Kotlin製のCoroutinesを駆使した超軽量フレームワークです。

背景

最近Kotlinを触り始めた訳ですが、今までJavaをメインに使っていた自分はJavaのwebアプリフレームワークはSpring Bootしか使ったことがありませんでした。

Spring BootはもちろんKotlinもサポートしているので、Kotlin製Spring Bootを使ってみようと思ったのですが、
Software Designという雑誌を読んでいたところ、Ktorという100%Kotlin製webアプリフレームワークが紹介されていたのを目にし、それをきっかけに勉強してみました。

Ktorというフレームワークについての思想は省略しますので、詳しく知りたい方は、Ktor 公式をご覧下さい。

Ktorを使ってみる

Ktorプロジェクトを始めるには色々方法がありますが、今回はIInteliJ IDEAを使います。

環境

  • macOS Mojava v10.14.1
  • InteliJ IDEA ULTIMATE 2018.3
  • open JDK 11.0.1
  • gradle

InteliJ IDEAでNew Project

IDEAにデフォルトでKtorが搭載されている訳ではないので、まずはプラグインをインストールします。

プラグインをインストール

スクリーンショット 2019-06-11 14.42.36.png

インストールしたら再起動。
再びIDEAが起動したら、New Projectを選択します。

Ktorの設定

スクリーンショット 2019-06-11 14.49.53.png

デフォルトでは写真のようになります。

  • ビルドツール...gradle
  • サーバエンジン...Netty
  • Ktor 1.2.0

使用するFeatureが決まっていればここで選択すればいいですが、後から追加することも可能なのでこのまま次に進みます。

スクリーンショット 2019-06-11 14.58.58.png

Springプロジェクト作成する時同様に、GroupId,ArtifactIdを入力します。適当に。

スクリーンショット 2019-06-11 14.59.43.png

また同様に、プロジェクト名を入力します。

FINISHを押下します。

スクリーンショット 2019-06-11 15.01.42.png

ここは自分好みの設定で。OK押すとプロジェクトが作成されgraldeが必要なライブラリ群をインストールして構築完了です。

ディレクトリ構成

初期の状態ではこんな感じです。

.
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── resources
│   ├── application.conf
│   └── logback.xml
├── settings.gradle
└── src
    └── Application.kt

実装

メインプロセス起動

mainとなるのは、Application.ktです。
なお、クラス名は自由に変えてしまって問題ありません。

Application.kt
package com.example

import io.ktor.application.*
import io.ktor.response.*
import io.ktor.request.*

fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)

@Suppress("unused") // Referenced in application.conf
@kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
}

このままプロセス起動はできそうなのですが、いざ起動してみるとエラーが発生します。

2019-06-11 15:09:13.922 [main] INFO  Application - No ktor.deployment.watch patterns specified, automatic reload is not active
Exception in thread "main" java.lang.ClassNotFoundException: Module function cannot be found for the fully qualified name 'com.example.ApplicationKt.module'
    at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.executeModuleFunction(ApplicationEngineEnvironmentReloading.kt:367)
    at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.access$executeModuleFunction(ApplicationEngineEnvironmentReloading.kt:33)
    at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1$$special$$inlined$forEach$lambda$1.invoke(ApplicationEngineEnvironmentReloading.kt:287)
    at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1$$special$$inlined$forEach$lambda$1.invoke(ApplicationEngineEnvironmentReloading.kt:33)
    at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartupFor(ApplicationEngineEnvironmentReloading.kt:320)
    at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.access$avoidingDoubleStartupFor(ApplicationEngineEnvironmentReloading.kt:33)
    at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:286)
    at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:33)
    at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartup(ApplicationEngineEnvironmentReloading.kt:302)
    at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:284)
    at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:137)
    at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:257)
    at io.ktor.server.netty.NettyApplicationEngine.start(NettyApplicationEngine.kt:116)
    at io.ktor.server.netty.NettyApplicationEngine.start(NettyApplicationEngine.kt:22)
    at io.ktor.server.engine.ApplicationEngine$DefaultImpls.start$default(ApplicationEngine.kt:56)
    at io.ktor.server.netty.EngineMain.main(EngineMain.kt:21)
    at com.example.ApplicationKt.main(Application.kt:7)

Ktor 公式を見れば分かるのですが、サーバの起動の仕方が違います。
IDEAのプラグインがサポートできているversionが古いのでしょうか、、?分かりませんが、公式に合わせます。

Application.kt
package com.example

import io.ktor.application.*
import io.ktor.http.HttpStatusCode
import io.ktor.response.*
import io.ktor.request.*
import io.ktor.routing.get
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty

fun main(args: Array<String>) {
    val server = embeddedServer(Netty, 8080) {
        routing {
            get("/") {
                call.respond(HttpStatusCode.OK,"Hello, Kotlin")
            }
        }
    }
    server.start()
}

これで無事起動できました。
実際にルーティングを設定したエンドポイントにGETリクエストしてみると

$ curl localhost:8080 
Hello, Kotlin%

レスポンスが返ってきます。

embeddedServerio.ktor.server.engine.EmbeddedServer.ktに定義されたメソッドです。
スクリーンショット 2019-06-11 15.21.33.png

この書き方だと、エンジンやポートをコード内にベタ書きしてしまっていてあまりキレイではありませんが、一旦スルーします。。

Controller

上記のようにルーティングを設定できますが、あそこにAPIの数だけ設定を追加していくとかなり肥大化されてしまいます。
一方、Springでは@Controllerがマッピングの役割を担っていてキレイにコンポーネント間の責務を果たしていました。あんな風にルーティングは分けたいところです。

はい、分けましょう。

srcディレクトリにcontrollerパッケージを作成し、ここではUserControllerを作成してみます。

controller/UserController
package com.example.controller

import io.ktor.application.call
import io.ktor.response.respondText
import io.ktor.routing.Route
import io.ktor.routing.get
import io.ktor.routing.route

fun Route.userController() {

    route("/user") {
        get {
            call.respondText { "user routing ok" }
        }
        get("/detail") {
            call.respond(HttpStatusCode.OK, "user detail routing ok")
        }
    }
}

簡単に言うと、route()の引数でエンドポイントを指定し、そこを基準に細かく各APIのルーティング設定ができます。
Springで言うと、クラスレベルとメソッドレベル両方を用いた@RequestMappingと似ていますね。

次に、mainメソッドを以下のように書き換えます。

Application.kt
package com.example

import io.ktor.application.*
import io.ktor.http.HttpStatusCode
import io.ktor.response.*
import io.ktor.request.*
import io.ktor.routing.get
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty

fun main(args: Array<String>) {
    val server = embeddedServer(Netty, 8080) {
        routing {
            userController()
        }
    }
    server.start()
}

そうして、再度プロセスを再起動すると、

$ curl localhost:8080/user
user routing ok%

$ curl localhost:8080/user/detail
user detail routing ok% 

ちゃんとルーティングされてレスポンスが返ってきます。
この他に、/userだけでなく/courseのようにエンドポイントを設定したければCourseControllerなどを作成して分けるといいでしょう。(ただの一例です。)

データベース接続

簡単なREST APIを実装できたところで、DB接続をしたいと思います。

KtorでDBにアクセスするとなると、exposedと言うライブラリを使用するのが一般的だそうで、ここでもこれを使ってみます。
ちなみにexposedはInteliJ IDEAの開発元であるJetBrainsが提供している純Kotlin製ORMです。

GitHubはこちら

gradleの設定

少し遅れてしまいましたが、デフォルトではbuild.gradleは以下のようになっています。

build.gradle
buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'kotlin'
apply plugin: 'application'

group 'example'
version '0.0.1'
mainClassName = "io.ktor.server.netty.EngineMain"

sourceSets {
    main.kotlin.srcDirs = main.java.srcDirs = ['src']
    test.kotlin.srcDirs = test.java.srcDirs = ['test']
    main.resources.srcDirs = ['resources']
    test.resources.srcDirs = ['testresources']
}

repositories {
    mavenLocal()
    jcenter()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    compile "io.ktor:ktor-server-netty:$ktor_version"
    compile "ch.qos.logback:logback-classic:$logback_version"
    testCompile "io.ktor:ktor-server-tests:$ktor_version"
}
gradle.properties
ktor_version=1.2.0
kotlin.code.style=official
kotlin_version=1.3.31
logback_version=1.2.1

ここに、expoesedを使えるよう依存関係を追加します。

build.gradle
// 上記省略
dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    compile "io.ktor:ktor-server-netty:$ktor_version"
    compile "ch.qos.logback:logback-classic:$logback_version"
    testCompile "io.ktor:ktor-server-tests:$ktor_version"
    // 以下追加
    compile "org.jetbrains.exposed:exposed:$exposed_version"
}

2019/06/11現在、exposedの最新版は0.14.1なので、それを利用することとします。

gradle.properties
ktor_version=1.2.0
kotlin.code.style=official
kotlin_version=1.3.31
logback_version=1.2.1
# 以下追加
exposed_version=0.14.1

※別にバージョンをプロパティファイルに外だししなくても構いません。

DBの種類

GitHubにあるように、exposedはMySQLやOracleをはじめ、多様なDBをサポートしています。
ですが、ここではDBの用意を省略したいので、Ktorで標準で利用可能なインメモリDBのH2を利用します。

H2に接続するには、単純にJDBCを経由するだけです。

Application.kt
package com.example

import com.example.controller.userController
import io.ktor.application.*
import io.ktor.http.HttpStatusCode
import io.ktor.response.*
import io.ktor.request.*
import io.ktor.routing.get
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import org.jetbrains.exposed.sql.Database

fun main(args: Array<String>) {
    // DBへ接続
    Database.connect("jdbc:h2:mem:ktor_db;DB_CLOSE_DELAY=-1", "org.h2.Driver")

    val server = embeddedServer(Netty, 8080) {
        routing {
            userController()
        }
    }
    server.start()
}

サーバを起動する前にDBへのコネクションを確立させれば、以後利用可能になります。

DBアクセス方法

上記でDBへの接続はできました。
肝心のDB操作方法ですが、exposedにはDBを操作する手段として、以下の2つの方法があります。

  • SQLをラップしたDSL
  • 軽量なDAO(Data Access Object)

どちらも試してみましたが、どちらがどうと言う訳でも甲乙がある訳でもなく、好きな方を使えばいいと思います。
(詳細な使用方法の違い等お分かりの方は教えていただければ幸いです。)

ここでは、DAOを利用することとします。

DAOの作成

daoパッケージを作成して、そこにUsersクラスを作成します。

dao/Users.kt
package com.example.dao

import org.jetbrains.exposed.dao.IntIdTable

object Users : IntIdTable() {
    val name = varchar("name",20).uniqueIndex()
    val age = integer("age")
}

IntIdTableクラスを継承することで、auto incrementが適用されたint型のプライマリーキーを持つTBLを定義できます。
プライマリーキー以外のカラムは1つ1つ上記のように定義します。良いのか悪いのか、単純なauto incrementなPKを記述する手間が省けますね。

DAOはSpringでいうRepositoryのようなもので、これとは別にレコードをKotlinの中で扱うためためのmodelを作成する必要があります。
SpringでいうEntityのようなものです。

Entityの作成

entityパッケージを作成して、その中にUserクラスを作成します。

entity/User.kt
package com.example.entity

import com.example.dao.Users
import org.jetbrains.exposed.dao.EntityID
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass

class User(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<User>(Users)

    var name by Users.name
    var age by Users.age
}

ところで、Javaを書いていた癖というか、データアクセス層とドメイン層はパッケージ別としていますが、Kotlinらしいパッケージ構成はどうなるのでしょうか。
Kotlinの性質上、1クラスに両方定義できますが、、趣味で書いている時ってパッケージ構成とかあまり気にしないから分かりませんね。

CRUD

H2はインメモリなDBのため、初期状態としてデータを永続化させるにはファイルに保存するか、アプリ起動時に初期データをinsertする必要があります。
ここではinsertのやり方も兼ねて後者を採用します。

Application.kt
package com.example

import com.example.controller.userController
import com.example.dao.Users
import com.example.entity.User
import io.ktor.application.*
import io.ktor.http.HttpStatusCode
import io.ktor.response.*
import io.ktor.request.*
import io.ktor.routing.get
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction

fun main(args: Array<String>) {
    Database.connect("jdbc:h2:mem:ktor_db;DB_CLOSE_DELAY=-1", "org.h2.Driver")
    // DBへレコードinsert
    transaction {
        SchemaUtils.create(Users)
        User.new {
            name = "swallowtail"
            age = 24
        }
    }
    val server = embeddedServer(Netty, 8080) {
        routing {
            userController()
        }
    }
    server.start()
}

こうすることで、アプリ起動時に投入できます。

/Library/Java/JavaVirtualMachines/jdk-11.0.1.jdk/Contents/Home/bin/java "-javaagent:/Users/riku/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/183.4886.37/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=56743:/Users/riku/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/183.4886.37/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath /Users/riku/qiitasample/out/production/classes:/Users/riku/qiitasample/out/production/resources:/Users/riku/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-server-netty/1.2.0/c30ea7f287343d3007a0794dbfeb3f69aad76ca8/ktor-server-netty-1.2.0.jar/Users/riku/.gradle/caches/modules-2/files-2.1/io.netty/netty-common/4.1.24.Final/7eeecd7906543214c3c1c984d275d3c6de10b99d/netty-common-4.1.24.Final.jar com.example.ApplicationKt
2019-06-11 19:28:12.087 [main] DEBUG Exposed - CREATE TABLE IF NOT EXISTS USERS (ID INT AUTO_INCREMENT PRIMARY KEY, "NAME" VARCHAR(20) NOT NULL, AGE INT NOT NULL)
2019-06-11 19:28:12.094 [main] DEBUG Exposed - ALTER TABLE USERS ADD CONSTRAINT USERS_NAME_UNIQUE UNIQUE ("NAME")
2019-06-11 19:28:12.131 [main] DEBUG Exposed - INSERT INTO USERS (AGE, "NAME") VALUES (24, 'swallowtail')
2019-06-11 19:28:12.299 [main] INFO  ktor.application - No ktor.deployment.watch patterns specified, automatic reload is not active
2019-06-11 19:28:12.413 [main] INFO  ktor.application - Responding at http://0.0.0.0:8080

プロセス起動時のログを見てみると、ちゃんとTBLが作成され、レコードがinsertされていることを確認できます。

Serviceの作成

それでは、上で作ったUserControllerを修正します。

/userへのGETリクエストには、DBのUSER全レコードを返すようにします。
この時、Controllerから直接データアクセスしても問題はありませんが、Springを使用してきたことからService層を挟んだ方がしっくりくるのでここでもそうしたいと思います。

service/UserService.kt
package com.example.service

import com.example.entity.User
import org.jetbrains.exposed.sql.transactions.transaction

class UserService {

    fun getAllUsers(): List<User> {
        var result: List<User> = listOf()
        transaction {
            result = User.all().toList()
        }
        return result
    }

    fun createUser(name: String, age: Int) {
        transaction {
            User.new {
                this.name = name
                this.age = age
            }
        }
    }
}

簡単に、Userの作成・取得のメソッドを定義します。

そして、UserControllerを以下のようにします。

controller/UserController.kt
package com.example.controller

import com.example.msg.CreateUserReqMsg
import com.example.msg.GetUserResMsg
import com.example.service.UserService
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 io.ktor.routing.route

fun Route.userController() {
    val userService = UserService() // DIしたい

    route("/user") {
        get {
            call.respond(
                HttpStatusCode.OK,
                userService.getAllUsers().map { user -> GetUserRespMsg(user.name, user.age) })
        }
        post {
            val result = call.receive<CreateUserReqMsg>()
            userService.createUser(result.name, result.age)
            call.respond(HttpStatusCode.OK)
        }

}

これでUserの取得と作成のAPIが作成できました。
更新・削除はまた別の機会に。。

Jackson

JSONからKotlinで扱うObjectへシリアライズ/デシリアライズするため、Jacksonを使用します。
他に、GSONというライブラリがありますが、今回は普段から使用しているJacksonを選びます。

Application.kt
package com.example

import com.example.controller.userController
import com.example.dao.Users
import com.example.entity.User
import io.ktor.application.*
import io.ktor.http.HttpStatusCode
import io.ktor.response.*
import io.ktor.request.*
import io.ktor.routing.get
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction

fun main(args: Array<String>) {
    Database.connect("jdbc:h2:mem:ktor_db;DB_CLOSE_DELAY=-1", "org.h2.Driver")
    // DBへレコードinsert
    transaction {
        SchemaUtils.create(Users)
        User.new {
            name = "swallowtail"
            age = 24
        }
    }
    val server = embeddedServer(Netty, 8080) {
        // jacksonをinstall
        install(ContentNegotiation) {
            jackson {
            }
        }
        routing {
            userController()
        }
    }
    server.start()
}

注意点

entity.UserのListをそのままJSONにシリアライズすると、JsonMappingExceptionが発生しました。循環的に起きているようです。

そのため、APIレスポンス用のObjectを作成します。

msg/GetUserRespMsg
package com.example.msg

data class GetUserRespMsg(val name: String, val age: Int)

同様に、POSTリクエストのボディをデシリアライズするためのObjectを作成します。

msg/CreateUserReqMsg
package com.example.msg

data class CreateUserReqMsg(val name: String, val age: Int)

これで、改めてリクエストを送ってみます。

$ curl -X POST -H "Content-Type: application/json" -d '{"name":"riku", "age":12}' localhost:8080/user

$ curl localhost:8080/user/
[{"name":"swallowtail","age":24},{"name":"riku","age":12}]%

POSTで作成したユーザもGETで参照できました。

ディレクトリ構成

最後に、最終的なディレクトリ構成は以下のようになります。

.
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── out
├── resources
│   ├── application.conf
│   └── logback.xml
├── settings.gradle
└── src
    ├── Application.kt
    ├── controller
    │   └── UserController.kt
    ├── dao
    │   └── Users.kt
    ├── entity
    │   └── User.kt
    ├── msg
    │   ├── CreateUserReqMsg.kt
    │   └── GetUserRespMsg.kt
    └── service
        └── UserService.kt

次試したいこと

  1. DIコンテナ
  2. Connection pool
  3. H2 -> MySQLへ置換

*1
Koinを利用すればDIできるそうです。時間があるときに試したいです。
*2
Hikari CPを利用してコネクションプールの設定が可能です。これもまた今度試したい。
*3
最初はMySQLで実装するはずだったのですが、

val url = "jdbc:mysql://localhost:3306/some_schema"
val drive = "com.mysql.cj.jdbc.Driver"
val user = "root"
val password = "password"
Database.connect(url, drive, user, password)

どうやら上記の設定ではダメなようで、以下のようなエラーが発生しました。
すぐには解決できなかたので一旦諦めました。笑

Caused by: java.sql.SQLException: No suitable driver found for jdbc:mysql//localhost:3306/some_schema

どなたか分かる方ーー

------------追記 2019/08/15------------

上記のMySQLへのコネクションですが、MySQL Driverをgradleの依存関係に追加するだけで解決できました。
コメントしてくださった @ih6109_at_nagaoka さんありがとうございます。

build.gradle
// 上記省略
dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    implementation "io.ktor:ktor-server-netty:$ktor_version"
    implementation "io.ktor:ktor-jackson:$ktor_version"

    implementation "ch.qos.logback:logback-classic:$logback_version"
    implementation "org.jetbrains.exposed:exposed:$exposed_version"
    implementation "com.h2database:h2:$h2_version"
    implementation "mysql:mysql-connector-java:$mysql_version"  // ここ追加!

    testImplementation "io.ktor:ktor-server-tests:$ktor_version"
}

上記のようにdriverを追加して、

Application.kt
package com.example

import com.example.controller.userController
import com.example.dao.Users
import com.example.entity.User
import io.ktor.application.*
import io.ktor.http.HttpStatusCode
import io.ktor.response.*
import io.ktor.request.*
import io.ktor.routing.get
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction

fun main(args: Array<String>) {

    // Database.connect("jdbc:h2:mem:ktor_db;DB_CLOSE_DELAY=-1", "org.h2.Driver")  // H2への接続
    val url = "jdbc:mysql://localhost:3306/some_schema"
    val driver = "com.mysql.cj.jdbc.Driver"
    val user = "root"
    val password = "password"
    Database.connect(url, driver, user, password)  // MySQLへの接続
    // DBへレコードinsert
    transaction {
        SchemaUtils.create(Users)
        User.new {
            name = "swallowtail"
            age = 24
        }
    }
    val server = embeddedServer(Netty, 8080) {
        // jacksonをinstall
        install(ContentNegotiation) {
            jackson {
            }
        }
        routing {
            userController()
        }
    }
    server.start()
}

アプリケーションからDBへの接続処理を上記のように変更すれば、MySQLへのコネクションが可能になりました。

swallowtail62
エンジニア3年目 Java, TypeScriptを中心に、サーバサイド・クライアントサイド共に開発中。 時々、Kotlin, Python, Go...
https://github.com/swallowtail62
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away