Node.js: SQLite 与 Prisma

最后更新:2026-08-26

Charlie 正在开发一款桌面笔记应用,需要本地存储数据,但不想让用户额外安装 MySQL 服务。SQLite 是零配置的嵌入式数据库,一个文件就是整个数据库;再搭配 Prisma ORM,让 SQL 操作拥有完整的类型安全。

1. 1 better-sqlite3:同步 API 的嵌入式数据库

(1)为什么选 better-sqlite3

Node.js 生态中有多个 SQLite 库,better-sqlite3同步 API著称——调用即返回,无需 await 或回调嵌套。它在底层用 C++ 编译 SQLite,性能远超异步封装库。

特点 说明
同步 API 无回调地狱,代码线性可读
零配置 无需安装数据库服务,npm install 即用
单文件存储 整个数据库就是一个 .db 文件
事务支持 嵌套事务、预编译语句一应俱全
高性能 C++ 绑定,比 node-sqlite3 快 2-3 倍
跨平台 Windows / macOS / Linux 均可编译

(2)安装与基本连接

BASH
npm init -y
npm install better-sqlite3
JAVASCRIPT
const Database = require('better-sqlite3');
const db = new Database('myapp.db');

db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');

console.log('SQLite 版本:', db.prepare('SELECT sqlite_version()').get());

(3)常用方法速查

方法 用途 示例
db.prepare(sql) 创建预编译语句 const stmt = db.prepare('SELECT * FROM users WHERE id = ?')
stmt.run(...params) 执行 INSERT/UPDATE/DELETE stmt.run(1, 'Charlie')
stmt.get(...params) 返回单行对象 stmt.get(1)
stmt.all(...params) 返回所有行数组 stmt.all()
stmt.values(...params) 返回值数组(无键名) stmt.values()
db.exec(sql) 执行多条 SQL db.exec(schemaSql)
db.transaction(fn) 创建事务函数 const insertMany = db.transaction((items) => {...})
db.pragma(cmd) 设置/查询 PRAGMA db.pragma('journal_mode = WAL')

▶ 示例:better-sqlite3 CRUD 完整流程

JAVASCRIPT
const Database = require('better-sqlite3');
const db = new Database('notes.db');

db.exec(`
  CREATE TABLE IF NOT EXISTS notes (
    id    INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    body  TEXT DEFAULT '',
    created_at TEXT DEFAULT (datetime('now'))
  )
`);

const insert = db.prepare('INSERT INTO notes (title, body) VALUES (?, ?)');
const result = insert.run('第一篇笔记', 'Hello SQLite!');
console.log('插入行 ID:', result.lastInsertRowid);

const find = db.prepare('SELECT * FROM notes WHERE id = ?');
console.log('查询结果:', find.get(1));

const update = db.prepare('UPDATE notes SET title = ? WHERE id = ?');
update.run('修改后的标题', 1);

const remove = db.prepare('DELETE FROM notes WHERE id = ?');
remove.run(1);

const listAll = db.prepare('SELECT * FROM notes ORDER BY created_at DESC');
console.log('全部笔记:', listAll.all());

db.close();
▶ 试一试

(4)事务用法

JAVASCRIPT
const insertMany = db.transaction((notes) => {
  for (const n of notes) {
    insert.run(n.title, n.body);
  }
});

insertMany([
  { title: '笔记 A', body: '内容 A' },
  { title: '笔记 B', body: '内容 B' },
]);


2. 2 SQL 语句基础回顾

(1)四大操作 CRUD

操作 SQL 关键字
Create INSERT INSERT INTO 表 (列) VALUES (值)
Read SELECT SELECT 列 FROM 表 WHERE 条件
Update UPDATE UPDATE 表 SET 列=值 WHERE 条件
Delete DELETE DELETE FROM 表 WHERE 条件

(2)常用查询子句

TEXT 📖 仅展示
SELECT 列名
FROM 表名
WHERE 条件
GROUP BY 分组列
HAVING 分组条件
ORDER BY 排序列 ASC|DESC
LIMIT 数量 OFFSET 偏移

(3)连接查询

JAVASCRIPT
db.exec(`
  CREATE TABLE IF NOT EXISTS authors (
    id   INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL
  )
`);

db.exec(`
  CREATE TABLE IF NOT EXISTS books (
    id        INTEGER PRIMARY KEY AUTOINCREMENT,
    title     TEXT NOT NULL,
    author_id INTEGER REFERENCES authors(id)
  )
`);

const joinQuery = db.prepare(`
  SELECT books.title, authors.name AS author
  FROM books
  JOIN authors ON books.author_id = authors.id
`);


3. 3 Prisma 安装与初始化

(1)Prisma 是什么

Prisma 是下一代 Node.js / TypeScript ORM,核心流程:

100%
flowchart LR
    A["schema.prisma"] -->|"prisma migrate dev"| B["迁移 SQL"]
    A -->|"prisma generate"| C["Prisma Client"]
    C -->|"类型安全查询"| D[("数据库")]
    B --> D

(2)项目初始化

BASH
mkdir prisma-notes && cd prisma-notes
npm init -y
npm install prisma --save-dev
npm install @prisma/client
npx prisma init --datasource-provider sqlite

初始化后生成:

TEXT 📖 仅展示
prisma-notes/
├── prisma/
│   └── schema.prisma
├── .env
└── package.json

.env 文件内容:

TEXT 📖 仅展示
DATABASE_URL="file:./dev.db"

(3)Prisma 命令速查

命令 用途
npx prisma init 初始化 Prisma 项目
npx prisma migrate dev 创建并应用开发迁移
npx prisma migrate deploy 生产环境应用迁移
npx prisma generate 生成 Prisma Client
npx prisma studio 打开可视化管理界面
npx prisma db push 原型阶段直接推送 schema(不生成迁移文件)
npx prisma db seed 运行种子数据脚本


4. 4 schema.prisma 定义模型

(1)基础结构

JAVASCRIPT
// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

model Note {
  id        Int      @id @default(autoincrement())
  title     String
  body      String   @default("")
  pinned    Boolean  @default(false)
  tags      String   @default("")
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

(2)字段类型速查

Prisma 类型 SQLite 映射 说明
String TEXT 字符串
Int INTEGER 32 位整数
BigInt INTEGER 64 位整数
Float REAL 浮点数
Boolean INTEGER 0 或 1
DateTime TEXT ISO 8601 字符串
Json TEXT JSON 字符串
Bytes BLOB 二进制数据
Decimal TEXT 高精度小数

注意:SQLite 类型系统与 PostgreSQL 不同,Prisma 在底层做适配。切换 provider 时字段类型映射会自动调整。

(3)属性与修饰符

修饰符 用途 示例
@id 主键 id Int @id
@default 默认值 @default(autoincrement()) / @default(now()) / @default("active")
@unique 唯一约束 email String @unique
@relation 关系定义 @relation(fields: [authorId], references: [id])
@map / @@map 列/表名映射 @map("created_at")
@@unique 复合唯一 @@unique([firstName, lastName])
@@index 复合索引 @@index([categoryId, createdAt])
? 可选字段 bio String?

(4)关系定义

JAVASCRIPT
model User {
  id    Int    @id @default(autoincrement())
  email String @unique
  name  String
  notes Note[]
}

model Note {
  id       Int   @id @default(autoincrement())
  title    String
  body     String @default("")
  authorId Int
  author   User   @relation(fields: [authorId], references: [id], onDelete: Cascade)
}

▶ 示例:定义多模型关系

JAVASCRIPT
// prisma/schema.prisma — 用户、笔记和标签模型之间的关系
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String
  notes     Note[]
  createdAt DateTime @default(now())
}

model Note {
  id        Int        @id @default(autoincrement())
  title     String
  body      String     @default("")
  published Boolean    @default(false)
  authorId  Int
  author    User       @relation(fields: [authorId], references: [id], onDelete: Cascade)
  tags      NoteTag[]
  createdAt DateTime   @default(now())
  updatedAt DateTime   @updatedAt
}

model Tag {
  id    Int       @id @default(autoincrement())
  name  String    @unique
  notes NoteTag[]
}

model NoteTag {
  noteId Int
  tagId  Int
  note   Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
  tag    Tag  @relation(fields: [tagId], references: [id], onDelete: Cascade)

  @@id([noteId, tagId])
  @@index([tagId])
}
▶ 试一试

本例定义了三种模型和两类关系:



5. 5 Prisma Migrate 迁移

(1)创建并应用迁移

BASH
npx prisma migrate dev --name init

执行后:

TEXT 📖 仅展示
prisma/
├── schema.prisma
└── migrations/
    └── 20260703_init/
        └── migration.sql

生成的 migration.sql

SQL
CREATE TABLE "Note" (
    "id"        INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    "title"     TEXT NOT NULL,
    "body"      TEXT NOT NULL DEFAULT '',
    "pinned"    BOOLEAN NOT NULL DEFAULT false,
    "tags"      TEXT NOT NULL DEFAULT '',
    "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "updatedAt" DATETIME NOT NULL
);

(2)迁移工作流

TEXT 📖 仅展示
开发阶段:  schema 修改 → npx prisma migrate dev --name 描述
测试阶段:  npx prisma migrate deploy(仅应用,不创建新迁移)
原型阶段:  npx prisma db push(跳过迁移文件,快速迭代)
重置数据:  npx prisma migrate reset(清空数据库并重放所有迁移)

(3)生产部署

BASH
npx prisma migrate deploy

migrate deploy 只执行未应用的迁移,不会创建新迁移,也不会重置数据——适合 CI/CD 流水线。



6. 6 Prisma Client CRUD

(1)初始化 Client

JAVASCRIPT
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function main() {
  // CRUD 操作写在这里
}

main()
  .catch(console.error)
  .finally(() => prisma.$disconnect());

(2)Create — 创建

JAVASCRIPT
const note = await prisma.note.create({
  data: {
    title: '学习 Prisma',
    body: 'Prisma 让数据库操作类型安全',
    pinned: true,
  },
});

const notes = await prisma.note.createMany({
  data: [
    { title: '笔记 A', body: '内容 A' },
    { title: '笔记 B', body: '内容 B', pinned: true },
  ],
});

(3)Read — 查询

JAVASCRIPT
const one = await prisma.note.findUnique({ where: { id: 1 } });

const first = await prisma.note.findFirst({
  where: { pinned: true },
  orderBy: { createdAt: 'desc' },
});

const all = await prisma.note.findMany();

const filtered = await prisma.note.findMany({
  where: {
    pinned: true,
    title: { contains: 'Prisma' },
  },
});

(4)Update — 更新

JAVASCRIPT
const updated = await prisma.note.update({
  where: { id: 1 },
  data: { title: '更新后的标题', pinned: false },
});

const count = await prisma.note.updateMany({
  where: { pinned: false },
  data: { tags: 'archived' },
});

(5)Delete — 删除

JAVASCRIPT
const deleted = await prisma.note.delete({ where: { id: 1 } });

const deleteCount = await prisma.note.deleteMany({
  where: { pinned: false },
});

(6)查询过滤器一览

过滤器 含义 示例
equals 等于 { title: { equals: 'Hello' } }
not 不等于 { id: { not: 1 } }
contains 包含 { title: { contains: 'Prisma' } }
startsWith 前缀 { title: { startsWith: '学习' } }
endsWith 后缀 { email: { endsWith: '@test.com' } }
in 在列表中 { id: { in: [1, 2, 3] } }
notIn 不在列表中 { id: { notIn: [4, 5] } }
lt / lte 小于 / 小于等于 { id: { lte: 10 } }
gt / gte 大于 / 大于等于 { id: { gte: 5 } }
AND { AND: [{ pinned: true }, { title: { contains: 'A' } }] }
OR { OR: [{ pinned: true }, { pinned: false }] }
NOT { NOT: { title: 'Hello' } }

▶ 示例:关联数据查询与过滤

JAVASCRIPT
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function queryNotes() {
  // 查询已发布的笔记,同时加载作者和标签
  const notes = await prisma.note.findMany({
    where: {
      published: true,
      title: { contains: 'Prisma' },
    },
    include: {
      author: {
        select: { id: true, name: true, email: true },
      },
      tags: {
        include: {
          tag: { select: { id: true, name: true } },
        },
      },
    },
    orderBy: { createdAt: 'desc' },
    take: 10,
  });

  console.log(`找到 ${notes.length} 条笔记`);
  for (const note of notes) {
    const tagNames = note.tags.map((nt) => nt.tag.name).join(', ');
    console.log(`${note.title} — ${note.author.name} [${tagNames}]`);
  }
}

queryNotes()
  .catch(console.error)
  .finally(() => prisma.$disconnect());
▶ 试一试

本示例使用 include 预加载关联的 authortags,再用 select 限制返回字段。结果中包含嵌套数据,无需额外查询。



7. 7 排序与分页

(1)排序

JAVASCRIPT
const sorted = await prisma.note.findMany({
  orderBy: [
    { pinned: 'desc' },
    { createdAt: 'desc' },
  ],
});

(2)分页

JAVASCRIPT
const PAGE_SIZE = 10;

const page1 = await prisma.note.findMany({
  skip: 0,
  take: PAGE_SIZE,
  orderBy: { createdAt: 'desc' },
});

const page2 = await prisma.note.findMany({
  skip: PAGE_SIZE,
  take: PAGE_SIZE,
  orderBy: { createdAt: 'desc' },
});

(3)游标分页(大数据集推荐)

JAVASCRIPT
const first = await prisma.note.findMany({
  take: 10,
  orderBy: { id: 'asc' },
});

const cursor = first[first.length - 1].id;

const next = await prisma.note.findMany({
  take: 10,
  skip: 1,
  cursor: { id: cursor },
  orderBy: { id: 'asc' },
});

▶ 示例:搜索与游标分页

JAVASCRIPT
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function searchNotes(searchTerm, cursorId = null) {
  const PAGE_SIZE = 5;
  const results = await prisma.note.findMany({
    take: PAGE_SIZE + 1,
    skip: cursorId ? 1 : 0,
    cursor: cursorId ? { id: cursorId } : undefined,
    where: {
      OR: [
        { title: { contains: searchTerm } },
        { body: { contains: searchTerm } },
      ],
    },
    include: { author: { select: { name: true } } },
    orderBy: { id: 'asc' },
  });

  const hasMore = results.length > PAGE_SIZE;
  if (hasMore) results.pop();

  return {
    items: results,
    nextCursor: hasMore ? results[results.length - 1].id : null,
    hasMore,
  };
}

async function main() {
  let cursor = null;
  for (let page = 1; page <= 3; page++) {
    const { items, nextCursor, hasMore } = await searchNotes('Prisma', cursor);
    console.log(`第 ${page} 页: ${items.length} 条结果`);
    items.forEach((n) => console.log(`  ${n.id}: ${n.title}`));
    if (!hasMore) break;
    cursor = nextCursor;
  }
}

main()
  .catch(console.error)
  .finally(() => prisma.$disconnect());
▶ 试一试

本例在 titlebody 中实现全文搜索,并结合游标分页。每次多取一条来判断是否有下一页,返回前移除多余记录。



8. 8 数据库选型对比

(1)SQLite vs MySQL vs PostgreSQL vs MongoDB

维度 SQLite MySQL PostgreSQL MongoDB
类型 嵌入式 客户端-服务器 客户端-服务器 文档型
安装 零安装(随 npm 包) 需安装服务 需安装服务 需安装服务
并发写入 单写入者 多写入者 多写入者 多写入者
数据规模 中小(GB 级) 大(TB 级) 大(TB 级) 大(TB 级)
适用场景 桌面应用 / 原型 / 测试 Web 应用 / 中型项目 复杂查询 / 地理数据 灵活 Schema / 日志
JSON 支持 有限(JSON1 扩展) 支持 原生 JSONB 原生文档
全文搜索 FTS5 扩展 全文索引 tsvector 文本索引
事务 完整 ACID 完整 ACID 完整 ACID 4.0+ 多文档事务
许可证 公有领域 GPL / 商业 PostgreSQL SSPL

(2)ORM 框架对比

维度 Mongoose Prisma Sequelize TypeORM
语言 JavaScript TypeScript 优先 JavaScript TypeScript 优先
数据库 MongoDB only SQLite/MySQL/PostgreSQL/MongoDB MySQL/PostgreSQL/SQLite/MSSQL MySQL/PostgreSQL/SQLite/MSSQL
Schema 定义 JS 对象 .prisma 声明式文件 JS 模型定义 装饰器 / 实体类
类型安全 弱(需手动) 强(自动生成) 中(装饰器类型)
迁移工具 无内置 prisma migrate sequelize-cli 内置
查询方式 链式 API 链式对象 链式 / 原生 SQL QueryBuilder / 原生
N+1 问题 需 populate 自动 includes 需 eager/lazy 需 relations
社区规模 快速增长
适合项目 MongoDB 项目 全栈 TypeScript 传统 Node.js NestJS 生态


9. 9 综合示例:笔记管理数据层

用 Prisma + SQLite 构建完整的笔记管理 CRUD 数据层。

▶ 示例:Prisma + SQLite 笔记管理

Step 1 — Schema 定义

JAVASCRIPT
// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

model Note {
  id        Int      @id @default(autoincrement())
  title     String
  body      String   @default("")
  pinned    Boolean  @default(false)
  tags      String   @default("")
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt      @map("updated_at")

  @@map("notes")
}
▶ 试一试

Step 2 — 迁移

BASH
npx prisma migrate dev --name notes_init

Step 3 — 数据访问层

JAVASCRIPT
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function createNote(data) {
  return prisma.note.create({ data });
}

async function getNoteById(id) {
  return prisma.note.findUnique({ where: { id } });
}

async function updateNote(id, data) {
  return prisma.note.update({ where: { id }, data });
}

async function deleteNote(id) {
  return prisma.note.delete({ where: { id } });
}

async function listNotes({ page = 1, pageSize = 10, pinned, keyword } = {}) {
  const where = {};
  if (pinned !== undefined) where.pinned = pinned;
  if (keyword) where.title = { contains: keyword };

  const [items, total] = await Promise.all([
    prisma.note.findMany({
      where,
      orderBy: [{ pinned: 'desc' }, { createdAt: 'desc' }],
      skip: (page - 1) * pageSize,
      take: pageSize,
    }),
    prisma.note.count({ where }),
  ]);

  return { items, total, page, pageSize, totalPages: Math.ceil(total / pageSize) };
}

async function togglePin(id) {
  const note = await prisma.note.findUnique({ where: { id } });
  if (!note) throw new Error('笔记不存在');
  return prisma.note.update({
    where: { id },
    data: { pinned: !note.pinned },
  });
}

module.exports = {
  createNote,
  getNoteById,
  updateNote,
  deleteNote,
  listNotes,
  togglePin,
};

Step 4 — 使用示例

JAVASCRIPT
const db = require('./note-service');

async function main() {
  const n1 = await db.createNote({ title: '学习 Prisma', body: '类型安全 ORM', pinned: true });
  const n2 = await db.createNote({ title: 'SQLite 要点', body: '零配置嵌入式数据库' });
  const n3 = await db.createNote({ title: 'Prisma 迁移', body: 'migrate dev 驱动' });

  console.log('单条查询:', await db.getNoteById(n1.id));

  await db.updateNote(n2.id, { body: '更新内容' });
  await db.togglePin(n3.id);

  const result = await db.listNotes({ page: 1, pageSize: 10, keyword: 'Prisma' });
  console.log('搜索结果:', result);

  await db.deleteNote(n2.id);

  const all = await db.listNotes({ page: 1, pageSize: 10 });
  console.log('剩余笔记:', all);
}

main()
  .catch(console.error)
  .finally(() => require('@prisma/client').PrismaClient &&
    require('./node_modules/.prisma/client').$disconnect?.());

运行:

BASH
node index.js

Step 5 — 可视化管理

BASH
npx prisma studio

浏览器打开 http://localhost:5555,即可可视化浏览和编辑数据。



10. 10 本课小结


❓ 常见问题

Q Prisma 和 Sequelize 有什么区别?
A Prisma 用声明式 schema 生成类型安全的客户端,Sequelize 用装饰器或 defineModel。Prisma 类型安全更好,Sequelize 生态更成熟。
Q SQLite 适合生产环境吗?
A 适合低并发场景(读多写少、单机部署),如个人项目、内部工具、嵌入式应用。高并发写入选 PostgreSQL 或 MySQL。
Q Prisma migrate 和 db push 有什么区别?
A migrate 生成迁移文件,适合团队协作和生产部署;db push 直接同步 schema 到数据库,适合原型开发。
Q 如何切换到 PostgreSQL?
A 修改 schema.prisma 的 provider 为 postgresql,更新 DATABASE_URL 连接字符串,运行 prisma migrate reset 重建数据库。
Q Prisma 的 N+1 问题怎么解决?
A 使用 include 预加载关联数据,或用 select 精确选择字段,避免在循环中逐条查询关联记录。

📖 小节

📝 作业

  1. better-sqlite3 创建一个 users 表,实现插入、按 email 查询、更新和删除操作,并用事务保证原子性。
  2. 初始化一个 Prisma 项目,定义 UserPost 两个模型(一对多关系),执行迁移后用 Prisma Client 完成创建用户、发布文章、查询用户及其所有文章。
  3. 在笔记管理示例基础上,添加 Category 模型,实现笔记按分类过滤,并在 listNotes 中支持按分类 ID 筛选。
  4. 对比 better-sqlite3 原生 SQL 和 Prisma Client 实现同一组 CRUD 操作,记录代码行数和可读性差异。
  5. 写一个脚本,用 prisma.note.findMany 实现关键词搜索 + 置顶优先 + 分页,对比 offset 分页和游标分页在 10000 条数据下的性能差异。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏