# SQLite3 in Node

January 12, 2026 Javascript Database Node Typescript

SQLite is the world's most used database and is now natively supported by Node. Here are some basic usage examples.

Desfining the Database class

import { DatabaseSync } from "node:sqlite";
import { z } from "zod";

class DatabaseConfig {
	constructor(public readonly path: string = "site.db") {}
}

class Database {
	conn: DatabaseSync;

	constructor(config: DatabaseConfig) {
		this.conn = new DatabaseSync(config.path);
	}
}

Defining entities

type Entity = {
	schema(): string;
};

type UserRole = "ADMIN" | "CUSTOMER";

class User {
	id: string;
	email: string;
	password: string;
	role: UserRole;
	createdAt: Date;
	deletedAt: Date | undefined;

	#schema = z.object({
		id: z.uuid(),
		email: z.email(),
		password: z.string(),
		role: z.enum(["ADMIN", "CUSTOMER"]),
		createdAt: z.coerce.date(),
		deletedAt: z.coerce.date().optional(),
	});

	constructor(raw: unknown) {
		const v = this.#schema.parse(raw);
		this.id = v.id;
		this.email = v.email;
		this.password = v.password;
		this.role = v.role;
		this.createdAt = v.createdAt;
		this.deletedAt = v.deletedAt;
	}

	static make(email: string, password: string, role: UserRole): User {
		const id = crypto.randomUUID().toString();
		const now = new Date();
		return new User({ id, email, password, role, createdAt: now });
	}

	static schema(): string {
		return `
            create table if not exists users (
                id varchar(40)
                , email varchar(100) not null
                , password varchar(255) not null
                , role varchar(20) not null
                , createdAt timestamp not null default current_timestamp
                , deletedAt timestamp
                , primary key (id)
                , constraint email_unique unique (email)               
            );
        `;
	}
}

Defining a basic migrations manager

class MigrationManager {
	db: Database;
	entities: Entity[];

	constructor(db: Database, entities: Entity[]) {
		this.db = db;
		this.entities = entities;
	}

	async migrate() {
		for (const entity of this.entities) {
			const ddl = entity.schema();
			this.db.conn.exec(ddl);
		}
	}
}

Implementation of repository

class UserRepo {
	db: Database;

	constructor(db: Database) {
		this.db = db;
	}

	list(limit: number = 10, offset: number = 0): User[] {
		const statement = this.db.conn.prepare(`
            select * from users
            where deletedAt is null
            limit ?
            offset ?
        `);

		const result = statement.all(limit, offset);
		return result.map((u) => new User(u));
	}

	insert(user: User) {
		const statement = this.db.conn.prepare(`
            insert into users (id, email, password, role, createdAt)
            values (?, ?, ?, ?, ?)
        `);

		statement.run(
			user.id,
			user.email,
			user.password,
			user.role,
			user.createdAt.toUTCString(),
		);
	}

	findById(id: string): User | null {
		const statement = this.db.conn.prepare(`
            select * from users
            where id = ?
            and deletedAt is null
            limit 1;
        `);

		const result = statement.all(id);
		if (!result.length) {
			return null;
		}

		return new User(result[0]);
	}
}

Putting it all together

function main(): void {
	const config = new DatabaseConfig();
	const db = new Database(config);
	const migrations = new MigrationManager(db, [User]);

	console.log("running migrations...");
	migrations.migrate();

	const userRepo = new UserRepo(db);
	const dummyUsers = [
		User.make("admin@site.com", "akscnalskcna", "ADMIN"),
		User.make("customer@site.com", "kasnclaksncla", "CUSTOMER"),
	];
	dummyUsers.forEach((user) => userRepo.insert(user));

	const users = userRepo.list();
	console.log(users);
}

main();