Home/Quickstart

Your first query.
Five minutes.

Install Storm, define an entity as a plain Kotlin data class, and run a type-safe query. No persistence context, no proxies, no XML. Here is the whole path.

Kotlin~5 minJDK 21+

1Set up

The fastest way in is the Storm CLI. It installs Storm-aware rules and skills for your AI coding assistant (Claude, Cursor, Copilot, Windsurf, Codex) and sets up a schema-aware MCP server, so the entities and queries it generates match your real schema.

terminal shell
1
npx @storm-orm/cli init

Prefer to wire the build by hand? Add the Storm modules yourself. This is the full Kotlin set for a runnable project on an in-memory H2 database; the BOM keeps the versions aligned.

build.gradle.kts Gradle · Kotlin DSL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// build.gradle.kts
plugins {
    kotlin("jvm") version "2.0.21"
    id("com.google.devtools.ksp") version "2.0.21-1.0.28"
}

dependencies {
    implementation(platform("st.orm:storm-bom:1.12.0"))
    implementation("st.orm:storm-kotlin")
    runtimeOnly("st.orm:storm-core")
    runtimeOnly("st.orm:storm-h2")          // zero-setup in-memory database
    runtimeOnly("com.h2database:h2:2.2.224")
    ksp("st.orm:storm-metamodel-ksp")
    kotlinCompilerPluginClasspath("st.orm:storm-compiler-plugin-2.0")
}

The ksp dependency generates the type-safe metamodel (Movie_), and the compiler plugin makes SQL templates injection-safe by default. On a real database, swap storm-h2 for your dialect and add its JDBC driver. See the installation guide for all options.

2Define an entity

Entities are plain immutable data classes that implement Entity<ID>. Field names map to columns automatically (camelCase to snake_case). There is no base class to extend and no generated draft type to route through: the class you write is the object you get back.

Movie.kt Kotlin
1
2
3
4
5
6
7
// Movie.kt — a plain data class. This is the whole entity.
data class Movie(
    @PK val id: Int = 0,
    val title: String,
    val year: Int,
    val rating: Double,
) : Entity<Int>

Storm maps to an existing schema rather than creating one, so define the table with your migration tool (Flyway, Liquibase) or a one-off DDL script. Storm can verify at startup that your entities match it with validateSchema().

schema.sql SQL
1
2
3
4
5
6
7
-- create the table with your migration tool, or run this once
CREATE TABLE movie (
    id      INT PRIMARY KEY AUTO_INCREMENT,
    title   VARCHAR(200),
    year    INT,
    rating  DOUBLE
);

3Run a query

Open an ORMTemplate on your DataSource, ask for a repository, and query it. Toggle Show SQL to see exactly what Storm runs: one statement, fully parameterized, no surprises.

Main.kt Kotlin Show SQL
1
2
3
4
5
6
7
8
9
10
11
// Open an ORM on any JDBC DataSource. Thread-safe; create it once.
val orm = dataSource.orm

// A repository for Movie: every CRUD method included, nothing to implement.
val movies = orm.entity<Movie>()

// Insert returns a copy with the generated id.
val saved = orm insert Movie(title = "Dune: Part Two", year = 2024, rating = 8.5)

// One type-safe line. Movie_ is generated at compile time, so a typo fails to compile.
val recent = movies.findAll(Movie_.year eq 2024)
generated sql
-- orm insert Movie(...)
INSERT INTO movie (title, year, rating) VALUES (?, ?, ?)

-- movies.findAll(Movie_.year eq 2024)
SELECT m.id, m.title, m.year, m.rating
FROM movie m
WHERE m.year = ?

That is the core loop. Insert, findById, update, and remove all come for free on the repository; add your own one-line queries whenever you need them.

4Where to next

You have the whole shape of Storm in three steps. From here: