Prisma SQLite: Set Up Prisma ORM 7 with an SQLite Database (Tested)

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 is file:./dev.db.
  • Create the tables with npx prisma migrate dev --name init, then npx 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 sqlite3 shell 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

RequirementVersion used here
Node.js24.18.0
prisma (CLI) and @prisma/client7.10.0
@prisma/adapter-better-sqlite37.10.0 (installs better-sqlite3 12.11.1)
TypeScript runnertsx

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: updateMany published the draft, so the count is 2.
  • duplicate: the second ada@example.com failed with Prisma error P2002, 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 with new 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 Boolean is 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-libsql there.
  • database is locked: two processes wrote to dev.db at 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

Tested with Prisma ORM 7.10.0, better-sqlite3 12.11.1 and Node.js 24.18.0 on 7 October 2026.