Prisma ORM 7 changed how it connects to SQLite. The client now needs a driver adapter, and the database URL lives in a new config file, so setup steps from older Prisma tutorials fail. In this article I’ll walk you through setting up Prisma 7 with an SQLite file from scratch, up to the queries you’ll run every day.
TLDR
- Install the Prisma packages plus the SQLite driver adapter,
@prisma/adapter-better-sqlite3. - Run
npx prisma init --datasource-provider sqlite. The database URL isfile:./dev.db. - Create the tables with
npx prisma migrate dev --name init, thennpx prisma generate. - In Prisma 7, pass the adapter to the client:
new PrismaClient({ adapter: new PrismaBetterSqlite3({ url }) }). - The database is an ordinary SQLite file, so the
sqlite3shell can read it.
What is Prisma with SQLite and what will we build?
Prisma ORM is a TypeScript ORM. You describe your tables in a schema.prisma file, Prisma turns that into SQL migrations, and it generates a typed client for queries. With SQLite, the whole database is one local file, so there’s no server to install. The Prisma SQLite docs describe local files as “suited to development and small applications”.
To show you the full setup, I will build a small blog database with User and Post models. Then a TypeScript script will query and change its rows.
Prerequisites
| Requirement | Version used here |
|---|---|
| Node.js | 24.18.0 |
| prisma (CLI) and @prisma/client | 7.10.0 |
| @prisma/adapter-better-sqlite3 | 7.10.0 (installs better-sqlite3 12.11.1) |
| TypeScript runner | tsx |
The project uses ES modules ("type": "module" in package.json), as the Prisma SQLite quickstart sets up.
Building the blog database
The setup goes from an empty folder to a script that queries the database.
Step 1: Create the project and install Prisma
These install commands follow the quickstart, with prisma@7 instead of its prisma@prev tag. I ran them to set up the test folder, but they aren’t part of the recorded run:
npm init -y
npm install --save-dev prisma@7 typescript tsx @types/node @types/better-sqlite3
npm install @prisma/client@7 @prisma/adapter-better-sqlite3 dotenv
Then set "type": "module" in package.json.
Step 2: Initialise Prisma for SQLite
$ npx prisma init --datasource-provider sqlite --output ../generated/prisma 2>&1 | grep -v '^$' | grep -v -i 'skills' | head -12
Initialized Prisma in your project
prisma/
schema.prisma
prisma7.config.ts
.env
.gitignore
Next, set up your database:
1. Configure your DATABASE_URL in prisma7.config.ts
2. Run prisma db pull to introspect your database.
Then, define your models in prisma/schema.prisma and run prisma migrate dev to apply your schema.
Learn more: https://pris.ly/getting-started
$ ls -a | grep -E 'prisma|\.env'
.env
prisma
prisma7.config.ts
$ cat .env | grep DATABASE_URL
DATABASE_URL="file:./dev.db"
Prisma 7.10 names its config file prisma7.config.ts. It reads the database URL from .env:
$ cat prisma7.config.ts 2>/dev/null || cat prisma.config.ts
// This file was generated by Prisma, and assumes you have installed the following:
// npm install --save-dev prisma dotenv
import "dotenv/config";
import { defineConfig } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: process.env["DATABASE_URL"],
},
});
Step 3: Define the models
Replace prisma/schema.prisma with the models. onDelete: Cascade removes a user’s posts when the user is deleted:
generator client {
provider = "prisma-client"
output = "../generated/prisma"
}
datasource db {
provider = "sqlite"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId Int
}
Step 4: Create the database with a migration
$ npx prisma migrate dev --name init 2>&1 | grep -v '^$' | grep -v -i 'update available\|npm i\|major update\|prisma.io/' | head -20
Loaded Prisma config from prisma7.config.ts.
Prisma schema loaded from prisma/schema.prisma.
Datasource "db": SQLite database "dev.db" at "file:./dev.db"
SQLite database dev.db created at file:./dev.db
Applying migration `20261007082645_init`
The following migration(s) have been created and applied from new schema changes:
prisma/migrations/
└─ 20261007082645_init/
└─ migration.sql
Your database is now in sync with your schema.
$ npx prisma generate 2>&1 | grep -E 'Generated|✔'
✔ Generated Prisma Client (7.10.0) to ./generated/prisma in 17ms
The migration is plain SQLite SQL. @id @default(autoincrement()) becomes INTEGER PRIMARY KEY AUTOINCREMENT, and @unique becomes a unique index:
$ cat prisma/migrations/*_init/migration.sql
-- CreateTable
CREATE TABLE "User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"email" TEXT NOT NULL,
"name" TEXT
);
-- CreateTable
CREATE TABLE "Post" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" TEXT NOT NULL,
"published" BOOLEAN NOT NULL DEFAULT false,
"authorId" INTEGER NOT NULL,
CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
Step 5: Create the client with the SQLite adapter
In Prisma 7, the client needs a driver adapter. Put it in its own module, lib/prisma.ts. Import it from there everywhere else:
import "dotenv/config";
import { PrismaBetterSqlite3 } from "@prisma/adapter-better-sqlite3";
import { PrismaClient } from "../generated/prisma/client";
const adapter = new PrismaBetterSqlite3({ url: process.env.DATABASE_URL ?? "file:./dev.db" });
export const prisma = new PrismaClient({ adapter });
Step 6: Write the queries
import { prisma } from "./lib/prisma";
async function main() {
// Create a user and two posts in one call.
const ada = await prisma.user.create({
data: {
email: "ada@example.com",
name: "Ada",
posts: { create: [{ title: "Notes on the Analytical Engine", published: true }, { title: "Draft" }] },
},
});
await prisma.user.create({ data: { email: "linus@example.com", name: "Linus" } });
// Read with a filter and a relation.
const published = await prisma.post.findMany({
where: { published: true },
select: { title: true, author: { select: { name: true } } },
});
console.log("published:", JSON.stringify(published));
// Update one row, then count.
await prisma.post.updateMany({ where: { title: "Draft" }, data: { published: true } });
console.log("published posts:", await prisma.post.count({ where: { published: true } }));
// A duplicate email breaks the @unique constraint.
try {
await prisma.user.create({ data: { email: "ada@example.com" } });
} catch (e: any) {
console.log("duplicate:", e.code, e.message.split("\n").at(-1));
}
// A post for Linus, kept after Ada is deleted.
await prisma.post.create({ data: { title: "Kernel notes", author: { connect: { email: "linus@example.com" } } } });
// Deleting a user deletes their posts (onDelete: Cascade).
await prisma.user.delete({ where: { id: ada.id } });
console.log("posts left:", await prisma.post.count());
}
main().finally(() => prisma.$disconnect());
Running it
$ npx tsx script.ts
published: [{"title":"Notes on the Analytical Engine","author":{"name":"Ada"}}]
published posts: 2
duplicate: P2002 Unique constraint failed on the fields: (`email`)
posts left: 1
Each line matches a step in the script:
- published: the filtered query returns only Ada’s published post, with her name from the relation.
- published posts:
updateManypublished the draft, so the count is 2. - duplicate: the second
ada@example.comfailed with Prisma errorP2002, from the unique index. - posts left: deleting Ada removed both of her posts through the cascade. Linus’s post, added right before, is the one left.
The data lives in dev.db, an ordinary SQLite file. The sqlite3 shell can read it, including Prisma’s own _prisma_migrations table. The published column holds an integer:
$ sqlite3 -header -column dev.db 'SELECT id, email, name FROM User;'
id email name
-- ----------------- -----
2 linus@example.com Linus
$ sqlite3 dev.db '.tables'
Post User _prisma_migrations
$ sqlite3 -header -column dev.db 'SELECT id, title, published, typeof(published) AS stored_as FROM Post;'
id title published stored_as
-- ------------ --------- ---------
3 Kernel notes 0 integer
The script also type-checks:
$ npx tsc --noEmit -p tsconfig.json && echo 'no type errors'
no type errors
Common problems
The first two problems below showed up in the test run. The others come from the Prisma docs and weren’t reproduced here.
PrismaClient was instantiated without any options. A driver adapter is required to connect to your database.In Prisma 7, create the client withnew PrismaClient({ adapter }), as in Step 5. This script creates one without an adapter:
import { PrismaClient } from "./generated/prisma/client";
// Prisma 7: a client created without a driver adapter.
try {
new PrismaClient();
} catch (e: any) {
console.log(e.constructor.name + ": " + e.message);
}
$ npx tsx no_adapter.ts
PrismaClientInitializationError: PrismaClient was instantiated without any options. A driver adapter is required to connect to your database.
Pass a driver adapter to the PrismaClient constructor, for example:
import { PrismaPg } from '@prisma/adapter-pg'
import { PrismaClient } from './generated/prisma/client'
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL })
const prisma = new PrismaClient({ adapter })
Learn more about driver adapters: https://pris.ly/d/driver-adapters
If you use Prisma Accelerate instead of connecting to your database directly, pass `accelerateUrl` to the PrismaClient constructor instead of `adapter`.
- Booleans look like numbers in SQL: SQLite has no boolean type, so a Prisma
Booleanis stored as 0 or 1, as the last query above shows. See SQLite boolean. - Running on Bun: the quickstart says Bun doesn’t support better-sqlite3, and recommends
@prisma/adapter-libsqlthere. database is locked: two processes wrote todev.dbat once, for example a dev server and Prisma Studio. See database is locked.
Conclusion
Prisma with SQLite gives you typed queries and versioned migrations on top of a single database file. Keep the client in one module, change the schema with prisma migrate dev, and remember Prisma 7 needs the better-sqlite3 adapter. For a web framework, the same setup drops into Next.js.
Further reading
- Next.js SQLite
- SQLite in Node.js
- SQLite foreign keys
- SQLite AUTOINCREMENT
- Prisma docs: SQLite quickstart, SQLite connector
Tested with Prisma ORM 7.10.0, better-sqlite3 12.11.1 and Node.js 24.18.0 on 7 October 2026.
