Skip to content

SQLite

You author plain SQLite SQL inside one or more .sql files. The platform runs an in-browser SQLite instance via the official @sqlite.org/sqlite-wasm build — every non-test .sql file gets executed automatically on save against the same in-memory database, in lexicographic order.

The seed ships a script.sql that creates a greetings table, inserts the row (1, 'Hello, World!'), and selects it back — extend it by adding more schema, seed data, and queries.

Entry file

Solution files live at the project root:

  • script.sql — editable solution file. The starter contains a TODO walkthrough for creating the greetings table and selecting the message.
  • You can rename script.sql to anything, and you can create additional .sql files (organize them under subfolders if you like). The auto-runner discovers every non-test .sql file in the tree.

Files run in lexicographic order by full path. Prefix file names (01-schema.sql, 02-data.sql, 03-queries.sql) when execution order matters.

Database playground

The right panel is a live Database tab that talks to the same in-memory SQLite instance:

  • Auto-run result — every SELECT (or other row-producing statement) from your .sql files renders here, grouped by source file, on every save.
  • Tables — schema sidebar populated from sqlite_master listing every table plus its columns + types (via PRAGMA table_info). Click Preview on a table to instantly SELECT * FROM <table> LIMIT 20 in the ad-hoc tab.
  • Ad-hoc query — Monaco-powered SQL editor for one-off queries against the live database. Ctrl/⌘ + Enter runs the query without touching your source files.

The database is in-memory: refreshing the preview wipes it. To clean up without reloading, run DROP TABLE … in any source file or in the ad-hoc tab.

Version

Running sqlite-wasm v3.46.1-build3 (SQLite WASM) with Vitest v3.2.4 on Node v22.

Supported languages

SQL (SQLite dialect)

Testing framework

Vitest with the @dojocode/sql-test-helpers helper.

Special reminders and implementation details

  • Standard SQLite dialect. Use INTEGER PRIMARY KEY AUTOINCREMENT (NOT PostgreSQL's SERIAL), INSERT OR IGNORE / INSERT OR REPLACE for upserts.
  • Schema introspection uses sqlite_master (SELECT name FROM sqlite_master WHERE type = 'table') and PRAGMA table_info(<table>) for column metadata.
  • Files matching *.test.sql are skipped by auto-run — reserved for a future raw-SQL test runner.
  • Tests are TypeScript (*.test.ts) using the shared createSqliteTestDb(import.meta.url) helper. The helper boots a fresh sqlite-wasm instance (with the OPFS init warning silenced) and auto-loads every non-test .sql file alongside the test before assertions run.
  • query() and exec() are synchronous here — unlike the PGlite helper, no await is required for individual calls (the helper itself is async only because the underlying sqlite-wasm module init returns a Promise).

Example with Vitest:

typescript
import { describe, it, expect, beforeAll } from 'vitest';
import { createSqliteTestDb, type SqliteTestDb } from '@dojocode/sql-test-helpers/sqlite';

let db: SqliteTestDb;

beforeAll(async () => {
  db = await createSqliteTestDb(import.meta.url);
});

describe('greetings', () => {
  it("contains the row with message 'Hello, World!'", () => {
    const rows = db.query<{ message: string }>(
      'SELECT message FROM greetings WHERE id = 1'
    );
    expect(rows[0]?.message).toBe('Hello, World!');
  });

  it('has exactly one row', () => {
    const rows = db.query<{ n: number }>('SELECT COUNT(*) AS n FROM greetings');
    expect(rows[0].n).toBe(1);
  });
});

The helper returns { query<T>(sql): T[], exec(sql): void, raw: sqlite3.oo1.DB }. Both query and exec are synchronous — they return / mutate immediately. Use raw only for advanced cases (prepared statements with bind parameters, transactions, the wasm.poke* family).

Included libraries

How to debug

Three ways to inspect what your SQL is doing:

1. The Auto-run result tab

Every SELECT result is rendered there with its source file label. If a query returns nothing, the section is omitted — that itself is a useful signal that a row didn't get inserted or a WHERE filter is too tight.

2. The Tables tab

Confirms which tables exist and their column types right now. If you're hitting "no such table" errors, the table didn't get created — usually a syntax error in a CREATE statement above it.

3. The Ad-hoc query tab

Run targeted SELECTs against the live database without modifying your source files:

sql
SELECT name FROM sqlite_master WHERE type = 'table';
PRAGMA table_info(greetings);
SELECT * FROM greetings WHERE id = 1;

Common pitfalls

  • Using PostgreSQL syntaxSERIAL doesn't exist in SQLite; use INTEGER PRIMARY KEY AUTOINCREMENT. ON CONFLICT (col) DO NOTHING works too, but INSERT OR IGNORE is more idiomatic in SQLite.
  • Forgetting INSERT OR IGNORE — re-running your script will fail with unique-constraint violations if a previous run already inserted the same rows.
  • Cross-folder execution order — files are sorted by their full path, so a/02-init.sql runs BEFORE b/01-init.sql. Prefix folders too when order matters across directories.