ktor-scaffold
navikt/copilot/skills/ktor-scaffold/SKILL.md
Scaffold eit nytt Ktor-prosjekt med Kotliquery, Flyway, Koin og Nais-konfigurasjon
Skill54 starsChanged 19 days ago
What's in it
- Ktor Project Scaffold
- Related Skills
- Step 1: Project Structure
- Step 2: Gradle Configuration
- libs.versions.toml
- build.gradle.kts
- Step 3: Application.kt
- Step 4: Config.kt
- Step 5: Database.kt
- Step 6: Dockerfile
- Step 7: NAIS Manifest
- Verification
---
name: ktor-scaffold
description: Scaffold eit nytt Ktor-prosjekt med Kotliquery, Flyway, Koin og Nais-konfigurasjon
license: MIT
compatibility: Kotlin with Ktor on Nais
metadata:
domain: backend
tags: kotlin ktor scaffold nais kotliquery
---
# Ktor Project Scaffold
Scaffold a new Kotlin/Ktor service following Nav's standard patterns. This skill composes patterns from existing Nav skills and instructions.
## Related Skills
Use these skills for deeper guidance on specific areas:
| Skill | When |
|-------|------|
| `kotlin-app-config` | Sealed class configuration pattern |
| `flyway-migration` | Database migration scripts |
| `tokenx-auth` | Service-to-service authentication |
| `observability-setup` | Prometheus metrics and health endpoints |
| `api-design` | REST API conventions and error handling |
## Step 1: Project Structure
Generate this directory layout:
```
my-service/
├── build.gradle.kts
├── settings.gradle.kts
├── gradle/
│ └── libs.versions.toml
├── src/
│ ├── main/
│ │ ├── kotlin/no/nav/myservice/
│ │ │ ├── Application.kt
│ │ │ ├── Config.kt
│ │ │ ├── Routes.kt
│ │ │ ├── Database.kt
│ │ │ └── domain/
│ │ └── resources/
│ │ ├── db/migration/
│ │ │ └── V1__initial_schema.sql
│ │ └── logback.xml
│ └── test/
│ └── kotlin/no/nav/myservice/
│ └── ApplicationTest.kt
├── .nais/
│ ├── app.yaml
│ └── app-dev.yaml
└── Dockerfile
```
## Step 2: Gradle Configuration
### libs.versions.toml
```toml
[versions]
kotlin = "2.3.20"
ktor = "3.4.2"
kotliquery = "1.9.1"
hikaricp = "7.0.2"
flyway = "12.2.0"
postgresql = "42.7.10"
koin = "4.2.0"
logback = "1.5.32"
logstash = "9.0"
micrometer = "1.16.4"
kotest = "6.1.10"
mockk = "1.14.9"
testcontainers = "2.0.4"
mockOAuth2 = "3.0.1"
[libraries]
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }
ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" }
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
ktor-serialization-jackson = { module = "io.ktor:ktor-serialization-jackson", version.ref = "ktor" }
ktor-server-auth = { module = "io.ktor:ktor-server-auth", version.ref = "ktor" }
ktor-server-auth-jwt = { module = "io.ktor:ktor-server-auth-jwt", version.ref = "ktor" }
ktor-server-metrics-micrometer = { module = "io.ktor:ktor-server-metrics-micrometer", version.ref = "ktor" }
ktor-server-test-host = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor" }
kotliquery = { module = "com.github.seratch:kotliquery", version.ref = "kotliquery" }
hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikaricp" }
flyway-core = { module = "org.flywaydb:flyway-core", version.ref = "flyway" }
flyway-postgres = { module = "org.flywaydb:flyway-database-postgresql", version.ref = "flyway" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" }
koin-logger-slf4j = { module = "io.insert-koin:koin-logger-slf4j", version.ref = "koin" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
logstash-encoder = { module = "net.logstash.logback:logstash-logback-encoder", version.ref = "logstash" }
micrometer-prometheus = { module = "io.micrometer:micrometer-registry-prometheus", version.ref = "micrometer" }
kotest-runner = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
kotest-assertions = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
testcontainers-postgres = { module = "org.testcontainers:postgresql", version.ref = "testcontainers" }
mock-oauth2-server = { module = "no.nav.security:mock-oauth2-server", version.ref = "mockOAuth2" }
```
### build.gradle.kts
```kotlin
plugins {
kotlin("jvm") version "2.1.10"
id("com.gradleup.shadow") version "9.0.0-beta6"
}
application {
mainClass.set("no.nav.myservice.ApplicationKt")
}
dependencies {
implementation(libs.ktor.server.core)
implementation(libs.ktor.server.netty)
implementation(libs.ktor.server.content.negotiation)
implementation(libs.ktor.serialization.jackson)
implementation(libs.ktor.server.auth)
implementation(libs.ktor.server.auth.jwt)
implementation(libs.ktor.server.metrics.micrometer)
implementation(libs.kotliquery)
implementation(libs.hikaricp)
implementation(libs.flyway.core)
implementation(libs.flyway.postgres)
runtimeOnly(libs.postgresql)
implementation(libs.koin.ktor)
implementation(libs.koin.logger.slf4j)
implementation(libs.logback.classic)
implementation(libs.logstash.encoder)
implementation(libs.micrometer.prometheus)
testImplementation(libs.ktor.server.test.host)
testImplementation(libs.kotest.runner)
testImplementation(libs.kotest.assertions)
testImplementation(libs.mockk)
testImplementation(libs.testcontainers.postgres)
testImplementation(libs.mock.oauth2.server)
}
tasks.test {
useJUnitPlatform()
}
```
## Step 3: Application.kt
```kotlin
package no.nav.myservice
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import org.koin.ktor.plugin.Koin
fun main() {
val config = Config.from(System.getenv())
Database.migrate(config.database)
embeddedServer(Netty, port = 8080) {
install(Koin) {
modules(appModule(config))
}
configureRouting()
configureSerialization()
configureMonitoring()
}.start(wait = true)
}
```
## Step 4: Config.kt
Use the sealed class pattern from `kotlin-app-config` skill:
```kotlin
package no.nav.myservice
sealed class Config(
val database: DatabaseConfig,
) {
data class Dev(private val env: Map<String, String>) : Config(
database = DatabaseConfig(
url = env.getValue("DATABASE_URL"),
username = env.getValue("DATABASE_USERNAME"),
password = env.getValue("DATABASE_PASSWORD"),
),
)
data class Prod(private val env: Map<String, String>) : Config(
database = DatabaseConfig(
url = env.getValue("DATABASE_URL"),
username = env.getValue("DATABASE_USERNAME"),
password = env.getValue("DATABASE_PASSWORD"),
),
)
data object Local : Config(
database = DatabaseConfig(
url = "jdbc:postgresql://localhost:5432/myservice",
username = "postgres",
password = "postgres",
),
)
companion object {
fun from(env: Map<String, String>): Config = when (env["NAIS_CLUSTER_NAME"]) {
"dev-gcp" -> Dev(env)
"prod-gcp" -> Prod(env)
else -> Local
}
}
}
data class DatabaseConfig(val url: String, val username: String = "", val password: String = "")
```
## Step 5: Database.kt
```kotlin
package no.nav.myservice
import com.zaxxer.hikari.HikariDataSource
import kotliquery.queryOf
import kotliquery.sessionOf
import kotliquery.using
import org.flywaydb.core.Flyway
import javax.sql.DataSource
object Database {
fun dataSource(config: DatabaseConfig): HikariDataSource = HikariDataSource().apply {
jdbcUrl = config.url
username = config.username
password = config.password
maximumPoolSize = 10
minimumIdle = 2
}
fun migrate(config: DatabaseConfig) {
Flyway.configure()
.dataSource(config.url, config.username, config.password)
.load()
.migrate()
}
}
```
## Step 6: Dockerfile
```dockerfile
FROM gradle:8-jdk21 AS build
WORKDIR /app
COPY build.gradle.kts settings.gradle.kts ./
COPY gradle ./gradle
RUN gradle dependencies --no-daemon
COPY src ./src
RUN gradle shadowJar --no-daemon
FROM europe-north1-docker.pkg.dev/cgr-nav/pull-through/nav.no/jre:openjdk-21
WORKDIR /app
COPY --from=build /app/build/libs/*-all.jar app.jar
CMD ["-jar", "app.jar"]
```
## Step 7: NAIS Manifest
Generate using the `nais-manifest` prompt, ensuring:
- PostgreSQL via `gcp.sqlInstances`
- Prometheus enabled at `/metrics`
- Health endpoints at `/isalive` and `/isready`
## Verification
After scaffolding, run:
```bash
./gradlew build
./gradlew test
```
More agent context in navikt/copilot
35 other files this repository gives its agents.
AGENTS.md
Copilot instructions
Skill
- ai-news-researchskills/ai-news-research/SKILL.md
- aksel-builderskills/aksel-builder/SKILL.md
- aksel-spacingskills/aksel-spacing/SKILL.md
- api-designskills/api-design/SKILL.md
- conventional-commitskills/conventional-commit/SKILL.md
- deliberate-ai-useskills/deliberate-ai-use/SKILL.md
- flyway-migrationskills/flyway-migration/SKILL.md
- jackson-3-migrationskills/jackson-3-migration/SKILL.md
- java-to-kotlinskills/java-to-kotlin/SKILL.md
- kafkaskills/kafka/SKILL.md
- klarsprakskills/klarsprak/SKILL.md
- kotlin-app-configskills/kotlin-app-config/SKILL.md
- naisskills/nais/SKILL.md
- nav-architecture-reviewskills/nav-architecture-review/SKILL.md
- nav-authskills/nav-auth/SKILL.md
- nav-deep-interviewskills/nav-deep-interview/SKILL.md
- nav-dekoratorenskills/nav-dekoratoren/SKILL.md
- nav-planskills/nav-plan/SKILL.md
- nav-troubleshootskills/nav-troubleshoot/SKILL.md
- observability-debuggingskills/observability-debugging/SKILL.md
- observability-setupskills/observability-setup/SKILL.md
- playwright-testingskills/playwright-testing/SKILL.md
- postgresql-reviewskills/postgresql-review/SKILL.md
- readme-reviewskills/readme-review/SKILL.md
- rust-developmentskills/rust-development/SKILL.md
- security-owaspskills/security-owasp/SKILL.md
- security-reviewskills/security-review/SKILL.md
- spring-boot-scaffoldskills/spring-boot-scaffold/SKILL.md
- terse-modeskills/terse-mode/SKILL.md
- threat-modelskills/threat-model/SKILL.md
- tokenx-authskills/tokenx-auth/SKILL.md
- web-design-reviewerskills/web-design-reviewer/SKILL.md
- workstation-securityskills/workstation-security/SKILL.md
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
No reports yet. Be the first to say whether it worked.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool public_context_discussion, action report. How to connect one.

