Kotlin
Primate compiles ordinary Kotlin route files with Kotlin/Wasm. Handlers may suspend, use Kotlin coroutines, access requests and sessions, and return Kotlin values directly. Production routes run on Node, Bun, and Deno.
For a first route and configuration, follow Add a Kotlin backend. The Kotlin
SDK run.primate:kotlin is published on Maven Central.
Setup
Install the Primate adapter:
npm install @primate/kotlinUse JDK 25 or later with current compatible Gradle and Kotlin releases.
Configure a Kotlin Multiplatform wasmJs executable in build.gradle.kts:
@file:OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
plugins {
kotlin("multiplatform") version "2.4.21"
}
layout.buildDirectory = file(".primate/kotlin-build")
kotlin {
wasmJs {
nodejs()
binaries.executable()
}
sourceSets.wasmJsMain {
kotlin.srcDir("routes")
kotlin.srcDir(".primate-kotlin/src")
dependencies {
implementation("run.primate:kotlin:0.7.0")
implementation(
"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0",
)
}
}
}Add Maven Central to settings.gradle.kts, then enable the adapter:
import kotlin from "@primate/kotlin";
import app from "primate/app";
export default app({
modules: [kotlin()],
});Primate watches Kotlin routes and Gradle sources and compiles incremental changes into one application-wide Wasm module. Development builds use a managed helper; production builds and tests run Gradle normally.
For a nonstandard project layout, use inputs to specify watched paths and
command, bootstrap, or artifacts to customize the build. Use
developmentCommand and developmentArtifacts for development-specific
overrides. Custom commands or artifacts use the command path unless you also
configure developmentToolingCommand; set developmentTooling: false to use
the command path directly.
Routes
Each .kt route declares a package and a main function that registers one or
more handlers. The package must be unique within the application.
// routes/extended-route/Index.kt
package routes.extended_route
import run.primate.Route
fun main() {
Route.get { "Hello from Kotlin" }
}This file serves /extended-route. Route paths follow Primate's lowercase
filesystem policy. For example,
routes/users/[id]/Index.kt matches /users/42.
Route supports get, post, put, delete, patch, head, connect,
options, and trace. Pass a content type before the handler when required:
Route.post("application/json") { request ->
mapOf("body" to request.bodyText())
}
Requests
Handlers receive a Request with these properties and functions:
method,url, andpathname;- nullable indexed
query,path,headers, andcookiesbags; - suspending
bodyText(); sessionfor suspending session operations.
Route.get { request ->
mapOf(
"id" to request.path["id"],
"search" to request.query["q"],
"agent" to request.headers["user-agent"],
"session" to request.cookies["session_id"],
"pathname" to request.pathname,
)
}Missing bag entries return null. Query, path-parameter, and header lookup use
Primate's case-normalized keys; cookie names remain case-sensitive.
Primate preserves request and transaction context when a handler resumes after Kotlin coroutine suspension or a host Promise.
Responses
See the cross-language response examples for equivalent Go, Python, Ruby, Kotlin, and JS/TS routes.
Return strings, booleans, numbers, lists, maps with string keys, or null.
These become normal Primate responses. Keep Kotlin Long and ULong values
within JavaScript's safe integer range.
The Kotlin SDK supports explicit abort, binary, error, JSON, null, redirect,
SSE, text, view, and WebSocket responses. Response.Page(props, options)
renders the page collocated with the current route, such as Index.svelte
beside Index.kt. For collocated pages, register the frontend module (for
example, @primate/svelte) after kotlin() so the .kt route handles the
shared Index basename. SSE and WebSocket callbacks stay active while streams
and sockets are open.
The SSE callback infers its source type; when storing a source, use
Response.SSE.Source. WebSocket callbacks use Response.WS.Actions,
Response.WS.Message, and Response.WS.Socket.
Use the response types for explicit responses:
import run.primate.Response
Route.get { Response.Redirect("/login", 307) }
Route.post { Response.Error("Invalid input", 422) }
Route.get { Response.View("profile.svelte", mapOf("name" to "Ada")) }
Sessions
Session data is an ordinary Map<String, Any?>. Call session operations from a
suspending handler.
Route.post { request ->
request.session.create(mapOf("userId" to 42))
request.session.get()
}Available operations are create, get, tryGet, set, and destroy.
They use Primate's configured session store and join an active transaction on
the same database. Rolled-back lifecycle changes do not emit a session cookie.