Skip to content

Repository files navigation

KMP Web Room

A minimal Kotlin Multiplatform project demonstrating Room 3 database running in the browser via Kotlin/Wasm and SQLite OPFS.

Data is stored persistently in the browser using the Origin Private File System (OPFS) — a real SQLite database that survives page refreshes.

Architecture

┌─────────────────────────────────────────────────────┐
│ commonMain │
│ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │
│ │NoteEntity│ │ NoteDao │ │ AppDatabase │ │
│ │ @Entity │ │ @Dao │ │ @ConstructedBy │ │
│ └──────────┘ └──────────┘ └───────────────────┘ │
│ │ │
│ expect getDatabaseBuilder() │
└────────────────────────────────────┬────────────────┘
│
┌────────────────────────────────────▼────────────────┐
│ wasmJsMain │
│ │
│ actual fun getDatabaseBuilder() │
│ Room.databaseBuilder(name = "notes.db") │
│ .setDriver(WebWorkerSQLiteDriver(...)) │
│ │ │
│ @JsFun → new Worker('worker.js') │
│ Web Worker thread │
│ ┌──────────────────┐ │
│ │ SQLite WASM │ │
│ │ OPFS storage │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────┘

Tech Stack

LibraryVersion
Kotlin2.3.20
Compose Multiplatform1.10.3
Room3.0.0-alpha03
AndroidX SQLite Web2.7.0-alpha03
KSP2.3.6
Koin4.2.0

Run

./gradlew composeApp:wasmJsBrowserDevelopmentRun

Opens at http://localhost:8080.

Note: The dev server is configured with Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers, required for SharedArrayBuffer / OPFS.

Project Structure

composeApp/
├── sqlite-wasm-worker/ # Local npm package — SQLite Web Worker
│ ├── package.json
│ └── worker.js
├── webpack.config.d/
│ └── cors-headers.js # COOP/COEP headers for OPFS
├── src/
│ ├── commonMain/kotlin/.../
│ │ ├── data/
│ │ │ ├── NoteEntity.kt # @Entity
│ │ │ ├── NoteDao.kt # @Dao with Flow
│ │ │ ├── AppDatabase.kt # @Database + @ConstructedBy
│ │ │ └── DatabaseBuilder.kt # expect fun
│ │ ├── di/
│ │ │ └── AppModule.kt # Koin module
│ │ └── ui/
│ │ ├── App.kt # Compose UI
│ │ └── NotesViewModel.kt # ViewModel with StateFlow
│ └── wasmJsMain/kotlin/.../
│ ├── data/
│ │ └── DatabaseBuilder.wasmJs.kt # actual — WebWorkerSQLiteDriver
│ └── main.kt # Entry point + Koin init
└── build.gradle.kts

How It Works

The Room database layer (Entity, DAO, Database) lives in commonMain — identical to Android. The only web-specific part is the actual fun getDatabaseBuilder() which uses WebWorkerSQLiteDriver to bridge Room with a SQLite instance running inside a Web Worker via OPFS.

The worker.js is the official AndroidX reference implementation and handles four commands over postMessage: open, prepare, step, close.

References

License

Copyright 2026 Georgios Karanikolas
Licensed under the Apache License, Version 2.0

See LICENSE for details.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages