Skip to content

The Cratis Kotlin Application template

The cratis-kotlin template is the Kotlin flavor of the full-stack starting point. One command gives you a running application with the whole stack wired: Arc for commands and queries over HTTP, Chronicle for the event log, MongoDB for read models, and a React frontend that talks to generated TypeScript proxies — all authored in Kotlin on Spring Boot.

Install the package and scaffold:

Terminal window
dotnet new install Cratis.Templates.Kotlin
dotnet new cratis-kotlin -n MyStore

MongoDB is the default database. Pass --Database to scaffold against PostgreSQL, MsSql, or SQLite instead — the docker-compose.yml then provisions the matching database for the Chronicle kernel, and the read-model sink switches to SQL:

Terminal window
dotnet new cratis-kotlin -n MyStore --Database PostgreSQL

The application code itself is identical for all databases — read models are persisted by the Chronicle kernel, so only the infrastructure the kernel runs against changes.

MyStore/
├── build.gradle.kts — Gradle build with the Arc plugin
├── settings.gradle.kts — plugin and dependency repositories
├── gradlew, gradle/wrapper/ — Gradle wrapper (9.1.0)
├── docker-compose.yml — Chronicle dev container + Aspire dashboard
├── package.json — frontend tooling (Vite, React, Cratis Components)
├── .frontend/ — Vite and TypeScript configuration
├── App.tsx, Home.tsx — frontend application shell
├── SomeModule/ — the sample frontend slices
└── src/main/kotlin/com/example/
├── CratisAppApplication.kt — Spring Boot entry point
├── ChronicleConfiguration.kt — Chronicle options and domain artifact registration
└── somemodule/ — the sample backend vertical slices

The Registration slice shows the command side — a @Command record with a @CommandKey, returning the event to append. No controller, no route registration:

@Command
@AllowAnonymous
data class Register(
@CommandKey val id: SomeId,
val name: SomeName
) {
fun handle(): Registered = Registered(name)
}

SomeId is a strongly-typed identity — a ConceptAs<String> — rather than a raw String. The command key routes the command to its event source; the generated TypeScript command sends it as part of the payload.

The Listing slice shows the read side — a read model built by a @Reducer from the Registered event, with a query exposing it over HTTP:

@ArcReadModel
@ChronicleReadModel
@AllowAnonymous
data class Listing(
val id: String = "",
val name: SomeName = SomeName("")
) {
companion object {
@JvmStatic
@Path("/api/listings")
suspend fun all(
@FromServices eventStore: IEventStore
): List<Listing> = eventStore.readModels.getInstances(Listing::class)
}
}

Post to the command route and the whole loop runs — command handled, event appended to Chronicle, reactor notified, read model updated:

Terminal window
curl -sS -X POST http://localhost:8080/api/register \
-H 'Content-Type: application/json' \
-d '{"id":"'$(uuidgen)'","name":"Cratis"}'
curl -sS -X QUERY http://localhost:8080/api/listings \
-H 'Content-Type: application/json' \
-d '{"arguments":{}}'

The template pins the versions it was verified against — Arc 7.2.0 (Gradle plugin and arc-chronicle-spring-boot-starter, which pins Chronicle 4.0.0), Kotlin 2.4.20, Spring Boot 4.1.1, JDK 17, and Gradle 9.1.0. Upgrade by bumping these in build.gradle.kts, keeping the plugin and starter versions in sync.

The scaffolded project includes a .cratis/ai.json with the Cratis AI profiles, languages, and coding agent harnesses for this template — the Cratis application selection for the Kotlin clients. Make sure the Cratis CLI is installed, then run:

Terminal window
cratis ai update

See the CLI AI documentation for the full command reference.