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.
┌─────────────────────────────────────────────────────┐
│ 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 │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────┘
| Library | Version |
|---|---|
| Kotlin | 2.3.20 |
| Compose Multiplatform | 1.10.3 |
| Room | 3.0.0-alpha03 |
| AndroidX SQLite Web | 2.7.0-alpha03 |
| KSP | 2.3.6 |
| Koin | 4.2.0 |
./gradlew composeApp:wasmJsBrowserDevelopmentRunOpens at http://localhost:8080.
Note: The dev server is configured with
Cross-Origin-Opener-PolicyandCross-Origin-Embedder-Policyheaders, required for SharedArrayBuffer / OPFS.
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
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.
- Room 3.0 Release Notes — Full docs on Room KMP + Web setup
- WebWorkerSQLiteDriver source (KDoc) — Protocol specification
- Official worker.js reference — AndroidX source tree
- SQLite Wasm + OPFS (Chrome Blog) — Why SQLite needs a Web Worker for persistent storage
- SQLite Wasm persistence docs — OPFS VFS and browser compatibility
- Room 3.0 announcement (Android Developers Blog)
Copyright 2026 Georgios Karanikolas
Licensed under the Apache License, Version 2.0
See LICENSE for details.