Se você precisa de um banco de dados simples para protótipos, aplicações desktop ou guardar configurações, o lowdb resolve. Ele salva tudo em JSON, não precisa de servidor, e funciona direto no Node.js. Vamos ver como usar com Express na prática.
O que é o lowdb
lowdb é um banco de dados JSON local para Node.js e Electron. Ele salva os dados em um arquivo .json no disco e permite fazer buscas, inserções e atualizações usando uma API JavaScript simples.
É ideal para:
- Protótipos e MVPs rápidos
- Aplicações desktop (Electron)
- Guardar configurações e preferências
- Projetos pequenos que não justificam PostgreSQL ou MongoDB
Não é para:
- Aplicações com muitos usuários simultâneos (não tem controle de concorrência)
- Arquivos muito grandes (carrega tudo em memória)
- Produção com alta carga (SQLite já seria melhor)
Instalando o lowdb
O lowdb funciona apenas com ESM (módulos ES). Se o seu projeto usa require(), você precisa migrar para import.
npm install lowdb
Requisitos:
- Node.js 18 ou superior
"type": "module"nopackage.json
{
"type": "module"
}
Setup inicial: criando o banco
Vamos criar um arquivo db.js que inicializa o lowdb com JSONFilePreset:
import { JSONFilePreset } from 'lowdb/node'
const defaultData = { posts: [], users: [] }
const db = await JSONFilePreset('db.json', defaultData)
export default db
O que está acontecendo aqui:
- JSONFilePreset é a forma recomendada de criar um banco JSON
- O segundo argumento (
defaultData) é obrigatório e define a estrutura inicial - Se
db.jsonnão existir, ele é criado com os dados padrão - Se existir, o conteúdo é carregado em
db.data
Sempre que você alterar db.data, chame db.write() para persistir. Ou use db.update() para modificar e salvar em uma operação.
CRUD com Express
Agora vamos criar uma API REST completa usando Express + lowdb. Crie um arquivo server.js:
import express from 'express'
import { JSONFilePreset } from 'lowdb/node'
const defaultData = { posts: [] }
const db = await JSONFilePreset('db.json', defaultData)
const app = express()
app.use(express.json())
// GET - Listar todos os posts
app.get('/posts', (req, res) => {
res.json(db.data.posts)
})
// GET - Buscar post por ID
app.get('/posts/:id', (req, res) => {
const post = db.data.posts.find(p => p.id === req.params.id)
if (!post) {
return res.status(404).json({ error: 'Post não encontrado' })
}
res.json(post)
})
// POST - Criar novo post (usando db.update)
app.post('/posts', async (req, res) => {
const newPost = {
id: Date.now().toString(),
title: req.body.title,
content: req.body.content,
createdAt: new Date().toISOString()
}
await db.update(({ posts }) => posts.push(newPost))
res.status(201).json(newPost)
})
// PUT - Atualizar post
app.put('/posts/:id', async (req, res) => {
const post = db.data.posts.find(p => p.id === req.params.id)
if (!post) {
return res.status(404).json({ error: 'Post não encontrado' })
}
post.title = req.body.title || post.title
post.content = req.body.content || post.content
post.updatedAt = new Date().toISOString()
await db.write()
res.json(post)
})
// DELETE - Remover post
app.delete('/posts/:id', async (req, res) => {
const index = db.data.posts.findIndex(p => p.id === req.params.id)
if (index === -1) {
return res.status(404).json({ error: 'Post não encontrado' })
}
db.data.posts.splice(index, 1)
await db.write()
res.status(204).send()
})
app.listen(3000, () => {
console.log('Servidor rodando em http://localhost:3000')
})
A estrutura é a mesma de qualquer API Express. A diferença é que, em vez de usar User.create() ou db.query(), a gente manipula db.data diretamente.
Você pode usar db.update() para modificar e salvar em uma operação (como no POST), ou modificar db.data e chamar db.write() separadamente.
Buscas e filtros
Para buscar dados no lowdb, você usa métodos JavaScript normais:
// Buscar por título
app.get('/posts/search', (req, res) => {
const { q } = req.query
const results = db.data.posts.filter(post =>
post.title.toLowerCase().includes(q.toLowerCase())
)
res.json(results)
})
// Ordenar por data
app.get('/posts/recent', (req, res) => {
const recent = [...db.data.posts]
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
.slice(0, 10)
res.json(recent)
})
Não tem query builder nem SQL. É tudo JavaScript puro: filter, find, map, sort.
Limites do lowdb
O lowdb é ótimo para começar, mas tem restrições importantes:
1. Concorrência
Se dois processos ou requisições modificarem db.data ao mesmo tempo, uma das escritas pode ser perdida. O lowdb não tem locks ou transações.
Solução: use SQLite quando precisar de múltiplos processos ou alta concorrência.
2. Tamanho do arquivo
O lowdb carrega o arquivo JSON inteiro na memória. Se o db.json crescer demais (centenas de MB), vai consumir muita RAM e ficar lento.
Solução: quando passar de ~10.000 registros, considere migrar para SQLite ou PostgreSQL.
3. Performance em buscas
Não tem índices. Buscar em 100.000 posts significa percorrer um array com 100.000 elementos.
Solução: se precisa de buscas rápidas em grandes volumes, use um banco indexado (SQLite, MongoDB, etc.).
Quando migrar para SQLite
Se o lowdb começar a travar ou se você perceber que precisa de:
- Múltiplos processos acessando o banco ao mesmo tempo
- Mais de 10.000 registros
- Buscas complexas ou joins
- Controle de transações
É hora de migrar para SQLite. Ele continua sendo um arquivo local (como o lowdb), mas tem índices, transações, locks e SQL. A boa notícia é que a migração é tranquila — você pode ler o db.json e inserir tudo no SQLite com um script simples.
Se quiser aprender mais sobre como estruturar rotas e middlewares no Express, confira o que é o app.use no Express. E se quiser entender melhor como funcionam requisições HTTP, veja nosso guia sobre request e response.
Curta o DevPleno no Facebook, inscreva-se no canal e não se esqueça de cadastrar seu e-mail para não perder as novidades. Deixe suas dúvidas nos comentários!
Mestre em Sistemas de Informação pela USP e criador do DevPleno. Iniciou sua carreira como professor com apenas 18 anos em um curso técnico, foram 11 anos em sala de aula formando desenvolvedores fullstack no sul de Minas Gerais.