Templates/Starter template · Kotlin + Ktor

Starter template · Kotlin + Ktor

A runnable Ktor application with one vertical slice: two entities, a repository query, a service, two endpoints, and the tests that cover them. Runs on H2 out of the box; PostgreSQL is a configuration change.

KotlinKtor 3H2 or PostgreSQL
Use this template →
$git clone https://github.com/storm-orm/storm-template-kotlin-ktor.git
View on GitHub →

A minimal, runnable Ktor application on Storm: two entities, one repository query, one service, two endpoints and the tests that cover them. It is a starting point, not a demo — there is nothing here to delete before writing your own code.

./gradlew run          # http://localhost:8080
./gradlew test

Both commands work on a fresh clone: the application runs on an H2 database in ./data, created by the Flyway migration on first start, and the tests run on H2 of their own.

What is here

src/main/kotlin/com/example/app/
├── Application.kt          one install(Storm) — pool, repositories, schema validation, migration
├── model/                  City and User, immutable data classes with @PK and @FK
├── repository/             EntityRepository interfaces; Storm implements them
├── service/                UserService, where the transactions are
└── web/                    routes and the request and response types
src/main/resources/
├── application.conf        port and the storm.datasource section
└── db/migration/           V1 creates the schema, V2 seeds two rows
src/test/kotlin/com/example/app/
├── UserRepositoryTest.kt           @StormTest on H2, asserting the SQL through SqlCapture
└── EntitySchemaValidationTest.kt   every entity checked against the migration

The vertical slice

GET /users?city=Amsterdam reads users and their city in one statement; the metamodel path User_.city.name navigates the foreign key, so the query never names the join.

POST /users registers a user, creating the city on first use. Both writes run in one transaction { }, which is coroutine-native: everything inside it, on any dispatcher, runs on the transaction's connection and commits or rolls back together.

curl 'http://localhost:8080/users?city=Amsterdam'
curl -X POST http://localhost:8080/users -H 'Content-Type: application/json' \
  -d '{"email":"grace@example.com","street":"Oudegracht 3","postalCode":"3511 AA","city":"Utrecht"}'

Making it yours

  1. Rename the com.example.app package and the rootProject.name in settings.gradle.kts.
  2. Replace V1__create_schema.sql with your schema and the entities with your tables. Storm validates the two against each other at startup, so a mismatch fails the application rather than the first request that hits it.
  3. Delete V2__sample_data.sql.

PostgreSQL

The application runs on H2 so that a fresh clone needs nothing installed. To use PostgreSQL instead, start it and swap the storm.datasource block in application.conf for the commented one below it:

docker compose up -d

Both dialect modules and both drivers are already on the runtime classpath, so nothing else changes.

Where to go next