Node.js: SQLite e Prisma ORM

Última atualização: 2026-08-26

Charlie está desenvolvendo um aplicativo para desktop destinado à criação de anotações que requer armazenamento local de dados, mas ele não quer que os usuários tenham que instalar um serviço MySQL separado. O SQLite é um banco de dados incorporado que não requer configuração, no qual um único arquivo constitui todo o banco de dados; quando combinado com o ORM Prisma, ele oferece segurança de tipos completa para operações SQL.

1. better-sqlite3: Um banco de dados incorporado com uma API síncrona

(1) Por que escolher o better-sqlite3?

Existem várias bibliotecas SQLite no ecossistema do Node.js, e a better-sqlite3 é conhecida por sua API síncrona — ela retorna imediatamente após a invocação, sem a necessidade de await ou callbacks aninhados. Ela compila o SQLite em C++ em nível baixo, resultando em um desempenho que supera em muito o das bibliotecas wrapper assíncronas.

Recurso Descrição
API de sincronização Sem o “inferno dos callbacks”, código linear e legível
Configuração zero Não é preciso instalar um serviço de banco de dados — o npm install já está pronto para uso
Armazenamento em um único arquivo Todo o banco de dados é um único arquivo .db
Suporte a transações Transações aninhadas e instruções preparadas — tudo incluído
Alto desempenho Ligações em C++, 2 a 3 vezes mais rápidas que o node-sqlite3
Multiplataforma Pode ser compilado no Windows, macOS e Linux

(2) Instalação e conexões básicas

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 Version:', db.prepare('SELECT sqlite_version()').get());

(3) Guia de referência rápida aos métodos mais comuns

Método Objetivo Exemplo
db.prepare(sql) Criar uma instrução pré-compilada const stmt = db.prepare('SELECT * FROM users WHERE id = ?')
stmt.run(...params) Executar INSERT/UPDATE/DELETE stmt.run(1, 'Charlie')
stmt.get(...params) Voltar ao objeto de linha única stmt.get(1)
stmt.all(...params) Retorna a matriz com todas as linhas stmt.all()
stmt.values(...params) Matriz de valores de retorno (sem nomes de chaves) stmt.values()
db.exec(sql) Executar várias instruções SQL db.exec(schemaSql)
db.transaction(fn) Criar função de transação const insertMany = db.transaction((items) => {...})
db.pragma(cmd) Definir/Consultar PRAGMA db.pragma('journal_mode = WAL')

▶ Exemplo: Fluxo de trabalho CRUD completo com o better-sqlite3

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('First Note', 'Hello SQLite!');
console.log('Insert a row ID:', result.lastInsertRowid);

const find = db.prepare('SELECT * FROM notes WHERE id = ?');
console.log('Search Results:', find.get(1));

const update = db.prepare('UPDATE notes SET title = ? WHERE id = ?');
update.run('Revised Title', 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('All Notes:', listAll.all());

db.close();
▶ Experimente

(4) Uso transacional

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

insertMany([
  { title: 'Notes A', body: 'Content A' },
  { title: 'Notes B', body: 'Content B' },
]);


2. Revisão dos conceitos básicos das instruções SQL

(1) As quatro operações principais: CRUD

Operação SQL Palavra-chave
Criar INSERIR INSERT INTO table (col) VALUES (val)
Ler SELECT SELECT col FROM table WHERE cond
Atualização ATUALIZAÇÃO UPDATE table SET col=val WHERE cond
Excluir EXCLUIR DELETE FROM table WHERE cond

(2) Cláusulas comuns de consulta

TEXT 📖 Somente leitura
SELECT Listed
FROM Table Name
WHERE Conditions
GROUP BY Grouping Column
HAVING Grouping Criteria
ORDER BY Sorted List ASC|DESC
LIMIT Quantity OFFSET offset

(3) Consultas com junção

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. Instalação e inicialização do Prisma

(1) O que é o Prisma?

O Prisma é um ORM de última geração para Node.js/TypeScript. Seu fluxo de trabalho principal é o seguinte:

100%
flowchart LR
    A["schema.prisma"] -->|"prisma migrate dev"| B["Migration SQL"]
    A -->|"prisma generate"| C["Prisma Client"]
    C -->|"Type-Safe Queries"| D[("Database")]
    B --> D

(2) Inicialização do projeto

BASH
mkdir prisma-notes && cd prisma-notes
npm init -y
npm install prisma --save-dev
npm install @prisma/クライアント
npx prisma init --datasource-provider sqlite

Gerado após a inicialização:

TEXT 📖 Somente leitura
prisma-notes/
├── prisma/
│   └── schema.prisma
├── .env
└── package.json

.env Conteúdo do arquivo:

TEXT 📖 Somente leitura
DATABASE_URL="file:./dev.db"

(3) Referência rápida do Prisma Command

Comando Finalidade
npx prisma init Inicializando um projeto Prisma
npx prisma migrate dev Criar e aplicar uma migração de desenvolvimento
npx prisma migrate deploy Migração de aplicativos no ambiente de produção
npx prisma generate Gerar o cliente Prisma
npx prisma studio Abrir a interface de gerenciamento visual
npx prisma db push Enviar o esquema diretamente durante a fase de protótipo (sem gerar arquivos de migração)
npx prisma db seed Executar o script de dados iniciais


4. schema.prisma: Definindo o modelo

(1) Estrutura básica

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) Referência rápida aos tipos de campo

Tipo de Prisma Mapeamento para SQLite Descrição
String TEXTO string
Int INTEGER inteiro de 32 bits
BigInt INTEGER inteiro de 64 bits
Float REAL Número de ponto flutuante
Boolean INTEGER 0 ou 1
DateTime TEXTO sequência ISO 8601
Json TEXTO string JSON
Bytes BLOB Dados binários
Decimal TEXTO Decimais de alta precisão

Observação: O sistema de tipos do SQLite difere do do PostgreSQL; o Prisma se encarrega da adaptação subjacente. Ao mudar para provider, os mapeamentos de tipos de campo serão ajustados automaticamente.

(3) Propriedades e modificadores

Modificador Finalidade Exemplo
@id chave primária id Int @id
@default Valor padrão @default(autoincrement()) / @default(now()) / @default("active")
@unique Restrição exclusiva email String @unique
@relation Definição de relação @relation(fields: [authorId], references: [id])
@map / @@map Mapeamento de nomes de colunas/tabelas @map("created_at")
@@unique Único composto @@unique([firstName, lastName])
@@index Índice Composto @@index([categoryId, createdAt])
? Campo opcional bio String?

(4) Definições de relações

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)
}

▶ Exemplo: Definindo Relacionamentos Multimodelo

JAVASCRIPT
// prisma/schema.prisma — relacionamentos entre User, Note e Tag
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])
}
▶ Experimente

Este exemplo define três modelos com dois tipos de relacionamentos:



5. Migração com o Prisma Migrate

(1) Criar e aplicar uma migração

BASH
npx prisma migrate dev --name init

Após a execução:

TEXT 📖 Somente leitura
prisma/
├── schema.prisma
└── migrations/
    └── 20260703_init/
        └── migration.sql

Gerado 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) Fluxo de trabalho de migração

TEXT 📖 Somente leitura
Development Phase:  schema Edit → npx prisma migrate dev --name Description
Testing Phase:  npx prisma migrate deploy(App Only,Do not create a new migration)
Prototyping Phase:  npx prisma db push(Skip migrating files,Rapid Iteration)
Reset Data:  npx prisma migrate reset(Clear the database and replay all migrations)

(3) Implantação em produção

BASH
npx prisma migrate deploy

migrate deploy Executa apenas migrações não aplicadas; não cria novas migrações nem reinicializa dados — ideal para pipelines de CI/CD.



6. CRUD do Prisma Client

(1) Inicializar o cliente

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

async function main() {
  // CRUD Instructions are written here
}

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

(2) Criar — Criar

JAVASCRIPT
const note = await prisma.note.create({
  data: {
    title: 'Study Prisma',
    body: 'Prisma Making Database Operations Type-Safe',
    pinned: true,
  },
});

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

(3) Ler — Consulta

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) Atualização — Atualização

JAVASCRIPT
const updated = await prisma.note.update({
  where: { id: 1 },
  data: { title: 'Updated Title', pinned: false },
});

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

(5) Excluir — Excluir

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

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

(6) Lista de filtros de consulta

Filtro Significado Exemplo
equals é igual a { title: { equals: 'Hello' } }
not não é igual a { id: { not: 1 } }
contains incluir { title: { contains: 'Prisma' } }
startsWith Prefixo { title: { startsWith: 'Learn' } }
endsWith sufixo { email: { endsWith: '@test.com' } }
in Na lista { id: { in: [1, 2, 3] } }
notIn Não consta na lista { id: { notIn: [4, 5] } }
lt / lte Menor que / Menor ou igual a { id: { lte: 10 } }
gt / gte Maior que / Maior ou igual a { id: { gte: 5 } }
AND E { AND: [{ pinned: true }, { title: { contains: 'A' } }] }
OR ou { OR: [{ pinned: true }, { pinned: false }] }
NOT Não { NOT: { title: 'Hello' } }

▶ Exemplo: Incluindo Dados Relacionados e Filtragem

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

async function queryNotes() {
  // Buscar notas publicadas com autor e tags
  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(`Encontradas ${notes.length} notas`);
  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());
▶ Experimente

Este exemplo usa include para carregar antecipadamente os author e tags relacionados, e select para limitar os campos apenas ao necessário. O resultado inclui dados aninhados sem consultas adicionais.



7. Ordenação e paginação

(1) Classificação

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

(2) Paginação

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) Paginação baseada no cursor (recomendada para conjuntos de dados grandes)

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' },
});

▶ Exemplo: Pesquisa e Paginação por Cursor

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(`Página ${page}: ${items.length} resultados`);
    items.forEach((n) => console.log(`  ${n.id}: ${n.title}`));
    if (!hasMore) break;
    cursor = nextCursor;
  }
}

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

Combina pesquisa de texto completo nos campos title e body com paginação baseada em cursor. Busca um registro extra para determinar se existe uma próxima página e o remove antes de retornar.



8. Comparação na seleção de bancos de dados

(1) SQLite x MySQL x PostgreSQL x MongoDB

Dimensão SQLite MySQL PostgreSQL MongoDB
Tipo Incorporado Cliente-servidor Cliente-servidor Baseado em documentos
Instalação Não requer instalação (incluído no pacote npm) Requer a instalação de um serviço Requer a instalação de um serviço Requer a instalação de um serviço
Gravações simultâneas Um gravador Vários gravadores Vários gravadores Vários gravadores
Tamanho dos dados Pequeno a médio (escala de GB) Grande (escala de TB) Grande (escala de TB) Grande (escala de TB)
Casos de uso Aplicativos de desktop / Protótipos / Testes Aplicativos web / Projetos de médio porte Consultas complexas / Dados geoespaciais Esquema flexível / Registros
Suporte a JSON Limitado (extensões JSON-1) Compatível JSONB nativo Documentação nativa
Pesquisa de texto completo Extensão FTS5 Índice de texto completo tsvector Índice de texto
Transações ACID completo ACID completo ACID completo Transações com múltiplos documentos (4.0+)
Licença Domínio Público GPL / Comercial PostgreSQL SSPL

(2) Comparação de estruturas de ORM

Dimensão Mongoose Prisma Sequelize TypeORM
Idioma JavaScript TypeScript (preferencial) JavaScript TypeScript (preferencial)
Banco de dados Apenas MongoDB SQLite/MySQL/PostgreSQL/MongoDB MySQL/PostgreSQL/SQLite/MSSQL MySQL/PostgreSQL/SQLite/MSSQL
Definição de esquema Objeto JS .prisma Arquivo declarativo Definição de modelo JS Classe decoradora / entidade
Segurança de tipos Fraca (manual) Forte (gerada automaticamente) Fraca Média (tipos decoradores)
Ferramenta de migração Não integrada prisma migrate sequelize-cli Integrada
Método de consulta API encadeada Objeto encadeado SQL encadeado / nativo QueryBuilder / nativo
Problema N+1 Precisa ser preenchido Inclui automaticamente Precisa de preenchimento antecipado/pós-carregamento Precisa de relações
Tamanho da comunidade Grande Em rápido crescimento Grande Grande
Projetos adequados Projetos com MongoDB TypeScript full-stack Node.js tradicional Ecossistema NestJS


9. Exemplo abrangente: Camada de dados de gerenciamento de notas

Crie uma camada de dados CRUD completa para gerenciamento de notas usando o Prisma e o SQLite.

▶ Exemplo: Prisma + Gerenciamento de notas com SQLite

Etapa 1 — Definição do esquema

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")
}
▶ Experimente

Etapa 2 — Migração

BASH
npx prisma migrate dev --name notes_init

Etapa 3 — Camada de acesso aos dados

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('The note does not exist.');
  return prisma.note.update({
    where: { id },
    data: { pinned: !note.pinned },
  });
}

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

Etapa 4 — Exemplo de uso

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

async function main() {
  const n1 = await db.createNote({ title: 'Study Prisma', body: 'Type Safety ORM', pinned: true });
  const n2 = await db.createNote({ title: 'SQLite Key Points', body: 'Zero-Configuration Embedded Database' });
  const n3 = await db.createNote({ title: 'Prisma Migration', body: 'migrate dev Driver' });

  console.log('Single-Record Query:', await db.getNoteById(n1.id));

  await db.updateNote(n2.id, { body: 'What's New' });
  await db.togglePin(n3.id);

  const result = await db.listNotes({ page: 1, pageSize: 10, keyword: 'Prisma' });
  console.log('Search Results:', result);

  await db.deleteNote(n2.id);

  const all = await db.listNotes({ page: 1, pageSize: 10 });
  console.log('Remaining Notes:', all);
}

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

Executar:

BASH
node index.js

Etapa 5 — Gestão visual

BASH
npx prisma studio

Abra http://localhost:5555 no seu navegador para visualizar e editar os dados de forma interativa.



10. Resumo desta aula


❓ Perguntas Frequentes

P What is the difference between Prisma and Sequelize?
R Prisma uses a declarative schema to generate type-safe clients, while Sequelize uses decorators or defineModel. Prisma offers better type safety, while Sequelize has a more mature ecosystem.

📖 Resumo

📝 Exercícios

  1. Use better-sqlite3 para criar uma tabela users, implemente operações de inserção, consulta por e-mail, atualização e exclusão, e utilize transações para garantir a atomicidade.
  2. Inicialize um projeto Prisma, defina dois modelos — User e Post (uma relação um-para-muitos) — e, após executar as migrações, use o Prisma Client para criar usuários, publicar artigos e consultar usuários e todos os seus artigos.
  3. Com base no exemplo de gerenciamento de notas, adicione o modelo Category para permitir a filtragem de notas por categoria e inclua suporte à filtragem por ID de categoria no listNotes.
  4. Compare a implementação do mesmo conjunto de operações CRUD usando o SQL nativo better-sqlite3 e o Prisma Client, observando as diferenças no número de linhas e na legibilidade.
  5. Escreva um script usando prisma.note.findMany para implementar a pesquisa por palavra-chave, priorizar os principais resultados e a paginação, e compare as diferenças de desempenho entre a paginação por deslocamento e a paginação por cursor ao processar 10.000 registros.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%