O localStorage no Node.js oferece uma implementação compatível com a Web Storage API para guardar pares de chave e valor em um arquivo local. A proposta parece simples: usar a mesma interface conhecida do navegador para persistir pequenas configurações, estados de ferramentas, caches leves e preferências em aplicações executadas fora do browser.
Apesar da familiaridade, o comportamento no servidor exige cuidado. Os dados são compartilhados por todo o processo, não por usuário ou requisição, ficam armazenados sem criptografia e possuem limite de tamanho. Em APIs multiusuário, usar localStorage como se cada cliente tivesse seu próprio espaço pode causar vazamento de dados entre requisições.
Neste guia, você aprenderá como habilitar localStorage, definir o arquivo de persistência, usar a interface Storage, organizar chaves, lidar com quota, concorrência, testes, segurança e decidir quando preferir SQLite ou outro armazenamento.
Disponibilidade
Em versões atuais do Node.js, a implementação é uma API em fase de estabilização. Consulte a documentação oficial de localStorage e fixe uma versão mínima do runtime antes de depender do recurso.
O armazenamento é habilitado apontando um arquivo na linha de comando:
node --localstorage-file=.data/local-storage.json app.jsSem o arquivo configurado, versões recentes podem lançar uma DOMException ao acessar o global. Isso evita a falsa impressão de que os dados seriam persistidos automaticamente.
Primeiro uso
localStorage.setItem('theme', 'dark');
console.log(localStorage.getItem('theme')); // darkOs valores sempre são armazenados como strings. Objetos precisam ser serializados explicitamente.
const settings = {
language: 'pt-BR',
pageSize: 25
};
localStorage.setItem('settings', JSON.stringify(settings));Lendo JSON com segurança
function readJson(key, fallback) {
const raw = localStorage.getItem(key);
if (raw === null) return fallback;
try {
return JSON.parse(raw);
} catch {
return fallback;
}
}Não assuma que o arquivo nunca será corrompido. Uma atualização incompleta, edição manual ou versão antiga do formato pode tornar o JSON inválido.
Interface Storage
A API possui os métodos clássicos:
getItem(key);setItem(key, value);removeItem(key);clear();key(index);- propriedade
length.
for (let index = 0; index < localStorage.length; index++) {
const key = localStorage.key(index);
console.log(key, localStorage.getItem(key));
}Organizando chaves
Use namespaces para evitar colisões:
const PREFIX = 'codigofacil:cli:v1:';
function storageKey(name) {
return `${PREFIX}${name}`;
}
localStorage.setItem(storageKey('lastProject'), '/home/user/app');Inclua versão no prefixo quando o formato pode evoluir. Assim, uma migração consegue diferenciar dados antigos.
Migração de esquema
function loadConfig() {
const current = readJson('app:config:v2', null);
if (current) return current;
const legacy = readJson('app:config:v1', null);
if (!legacy) return { theme: 'system' };
const migrated = {
theme: legacy.darkMode ? 'dark' : 'light'
};
localStorage.setItem('app:config:v2', JSON.stringify(migrated));
return migrated;
}Migrações devem ser idempotentes. Se o processo cair no meio, a próxima execução precisa continuar sem perder o estado válido.
Limite de quota
A implementação possui quota limitada. Ao ultrapassar o máximo, setItem() pode lançar QuotaExceededError.
try {
localStorage.setItem(key, largeValue);
} catch (error) {
if (error.name === 'QuotaExceededError') {
console.error('Quota do localStorage excedida');
} else {
throw error;
}
}Não use localStorage para blobs, logs extensos, respostas de API ou filas. O limite existe para pequenos estados.
Dados sem criptografia
O arquivo é gravado em texto legível. Nunca armazene:
- senhas;
- refresh tokens;
- chaves privadas;
- segredos de API;
- dados pessoais sensíveis;
- cookies de sessão;
- credenciais de banco.
Para segredos, consulte Gestão de Segredos no Node.js.
Permissões do arquivo
Mesmo para configurações não sensíveis, restrinja o diretório:
mkdir -p .data
chmod 700 .data
touch .data/local-storage.json
chmod 600 .data/local-storage.jsonAs permissões do sistema operacional são parte da proteção. Em containers, use volume dedicado e usuário sem privilégios.
Compartilhamento entre requisições
No servidor, existe um único localStorage para o processo. Este código é perigoso:
app.post('/preferences', async (req, res) => {
localStorage.setItem('current-user', req.user.id);
res.sendStatus(204);
});Duas requisições concorrentes sobrescrevem a mesma chave. Nunca use o recurso para estado de sessão ou dados por cliente.
Para contexto por requisição, veja AsyncLocalStorage no Node.js. Apesar do nome parecido, as APIs resolvem problemas totalmente diferentes.
Concorrência
Operações de leitura e escrita podem parecer atômicas individualmente, mas sequências não são transações:
const current = Number(localStorage.getItem('counter') ?? '0');
localStorage.setItem('counter', String(current + 1));Execuções concorrentes podem perder incrementos. Para dados que exigem consistência, use SQLite ou banco com transações.
Consulte SQLite no Node.js.
Múltiplos processos
Não trate o mesmo arquivo como banco compartilhado por vários processos. PM2, cluster, containers ou jobs simultâneos podem disputar o recurso. A API não substitui locks interprocessos e banco transacional.
Worker Threads
Verifique o comportamento da versão usada antes de compartilhar o recurso com workers. Mesmo quando o arquivo é o mesmo, coordenação de leitura e escrita exige uma estratégia explícita.
Para paralelismo, veja Worker Threads no Node.js.
Uso em CLI
Uma ferramenta de linha de comando pode guardar:
- último workspace utilizado;
- preferência de saída;
- data da última verificação;
- versão de migração;
- flags não sensíveis.
const DEFAULTS = {
output: 'table',
color: true
};
const preferences = {
...DEFAULTS,
...readJson('cli:preferences:v1', {})
};Cache leve
Para cache, inclua expiração:
function setCache(key, value, ttlMs) {
localStorage.setItem(key, JSON.stringify({
value,
expiresAt: Date.now() + ttlMs
}));
}
function getCache(key) {
const entry = readJson(key, null);
if (!entry) return null;
if (entry.expiresAt <= Date.now()) {
localStorage.removeItem(key);
return null;
}
return entry.value;
}Não use como cache distribuído. Cada instância terá seu próprio arquivo.
Limpeza
Remova chaves antigas durante inicialização:
function removeExpired(prefix) {
const keys = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key?.startsWith(prefix)) keys.push(key);
}
for (const key of keys) {
const entry = readJson(key, null);
if (entry?.expiresAt && entry.expiresAt <= Date.now()) {
localStorage.removeItem(key);
}
}
}Evite modificar o armazenamento enquanto percorre os índices, porque a ordem pode mudar. Primeiro copie as chaves.
sessionStorage
Node.js também oferece sessionStorage em versões compatíveis. Ele fica em memória e dura apenas enquanto o processo está ativo. Use para estado temporário, lembrando que não é compartilhado entre workers.
Testes isolados
Execute testes com arquivo temporário diferente por processo:
node --localstorage-file=.tmp/test-storage.json --testLimpe antes e depois:
import test from 'node:test';
import assert from 'node:assert/strict';
test.beforeEach(() => localStorage.clear());
test('salva preferência', () => {
localStorage.setItem('theme', 'dark');
assert.equal(localStorage.getItem('theme'), 'dark');
});Não edite o arquivo manualmente
A documentação não garante suporte a alterações externas. Trate o arquivo como implementação interna. Se precisa interoperar com outras ferramentas, use JSON próprio com escrita atômica ou banco.
Backup
Para preferências simples, backup pode não ser necessário. Se o estado é importante para negócio, isso já indica que localStorage provavelmente é a ferramenta errada.
Observabilidade
Registre apenas eventos gerais:
- falha ao abrir armazenamento;
- quota excedida;
- migração executada;
- entrada corrompida;
- limpeza realizada.
Não registre valores completos quando podem conter caminhos, preferências ou informações privadas.
Quando não usar
Evite localStorage para:
- sessões HTTP;
- estado por usuário;
- filas;
- auditoria;
- contadores concorrentes;
- transações;
- dados maiores que poucos megabytes;
- compartilhamento entre pods.
Alternativas
- arquivo JSON próprio: quando precisa controlar formato e escrita;
- SQLite: consultas, transações e consistência local;
- Redis: cache e estado compartilhado;
- PostgreSQL: dados duráveis multiusuário;
- variáveis de ambiente: configuração imutável;
- secret manager: credenciais.
Erros comuns
- usar sem configurar arquivo;
- armazenar objetos sem JSON;
- guardar segredos;
- usar em requisições multiusuário;
- ignorar quota;
- compartilhar arquivo entre processos;
- confiar em leitura-modificação-escrita;
- editar o arquivo manualmente;
- não versionar o formato;
- usar como banco de produção.
Checklist
- runtime compatível;
- arquivo configurado;
- diretório protegido;
- somente dados não sensíveis;
- chaves com namespace;
- formato versionado;
- tratamento de quota;
- JSON validado;
- testes com arquivo isolado;
- alternativa transacional avaliada.
Conclusão
O localStorage no Node.js é conveniente para pequenas preferências e estados locais de ferramentas. A interface familiar reduz código e facilita compartilhar conceitos com aplicações web.
No servidor, porém, os dados são globais ao processo, não possuem criptografia e não oferecem transações. Use apenas para estado pequeno, não sensível e sem concorrência crítica. Quando os requisitos crescerem, migre para SQLite, Redis ou banco relacional.
Como referência de comportamento, consulte também a especificação Web Storage.



