Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Prepared Statements no Node.js

Atualizado em: 27 de agosto de 2026

Terminal Linux usado em desenvolvimento e administração de aplicações Node.js

Os Prepared Statements no Node.js permitem reutilizar uma consulta parametrizada em uma conexão PostgreSQL. A primeira execução envia o texto SQL, o servidor prepara a instrução e as execuções seguintes informam apenas os valores, mantendo código e dados separados.

Prepared statements podem reduzir trabalho de parsing em consultas frequentes e reforçam o uso correto de parâmetros. Porém, não devem ser confundidos com uma solução automática de performance. Planos genéricos, pools, nomes duplicados, alterações de schema e excesso de instruções preparadas podem causar comportamento inesperado.

Neste guia, você aprenderá a usar consultas nomeadas com pg, entender o escopo por conexão, comparar planos customizados e genéricos, lidar com pools, invalidar statements, medir desempenho e evitar erros de segurança.

O que é um Prepared Statement?

Uma instrução preparada separa o texto SQL dos valores. A documentação oficial de PREPARE do PostgreSQL descreve o recurso. A documentação de queries do node-postgres mostra consultas nomeadas e parametrizadas.

Para evitar vulnerabilidades, consulte SQL Injection no Node.js. Para gerenciar conexões, veja Pool PostgreSQL no Node.js.

Query parametrizada simples

const result = await pool.query(`
  SELECT id, email
  FROM users
  WHERE email = $1
`, [email]);

Essa consulta já é segura contra injection quando o valor é enviado por parâmetro. Ela não precisa necessariamente de um nome persistente.

Consulta nomeada com pg

const query = {
  name: 'find-user-by-email-v1',
  text: `
    SELECT id, email, status
    FROM users
    WHERE email = $1
  `,
  values: [email]
};

const result = await pool.query(query);

O driver prepara a consulta na conexão quando o nome é usado pela primeira vez.

Escopo por conexão

Prepared statements pertencem à sessão PostgreSQL. Em um pool com dez conexões, o mesmo statement pode ser preparado separadamente em cada uma.

Pool e aquecimento

A primeira execução em cada conexão ainda precisa preparar. Não espere que preparar em uma conexão aqueça todo o pool.

Nome único e estável

O nome identifica o texto na sessão:

name: 'orders-list-by-customer-v2'

Inclua uma versão quando o SQL mudar.

Mesmo nome com SQL diferente

Reutilizar um nome para outro texto na mesma conexão causa erro. Não gere nomes genéricos como query para consultas diferentes.

Não use valor no nome

name: `find-user-${email}`

Isso cria uma instrução por valor, aumenta memória da sessão e pode expor dados. O nome deve representar a estrutura da query.

Parâmetros continuam obrigatórios

const query = {
  name: 'find-user-unsafe',
  text: `SELECT * FROM users WHERE email = '${email}'`
};

Dar nome a uma consulta vulnerável não a torna segura. Continue usando placeholders.

Prepared statement explícito em SQL

PREPARE find_order (bigint) AS
SELECT id, status, total_cents
FROM orders
WHERE id = $1;

Depois:

EXECUTE find_order(842);

Com node-postgres, consultas nomeadas normalmente dispensam o PREPARE manual.

Parsing, planejamento e execução

Uma consulta passa por parsing, análise, planejamento e execução. Preparar pode economizar parte do trabalho nas repetições, mas o ganho depende da complexidade e frequência.

Plano customizado

Um plano customizado considera os valores atuais. Isso é útil quando a seletividade muda muito, como uma coluna de status em que um valor possui milhões de linhas e outro possui poucas.

Plano genérico

Depois de execuções, o PostgreSQL pode escolher um plano genérico que não considera valores específicos, se estimar que é vantajoso.

Parameter sniffing

Embora o termo seja mais associado a outros bancos, o problema conceitual existe: um plano que funciona para um valor pode ser ruim para outro. Meça consultas com distribuições reais.

plan_cache_mode

PostgreSQL oferece configuração para influenciar planos customizados ou genéricos:

SET LOCAL plan_cache_mode = force_custom_plan;

Não altere globalmente sem benchmark. Use apenas em casos diagnosticados.

EXPLAIN EXECUTE

EXPLAIN (ANALYZE, BUFFERS)
EXECUTE find_order(842);

Isso ajuda a observar o plano usado por uma instrução explícita.

Consultas muito simples

Em uma query por chave primária, o custo de parsing pode ser pequeno. Prepared statements talvez não alterem perceptivelmente a latência.

Consultas frequentes

Queries executadas milhares de vezes por segundo podem se beneficiar, desde que o plano permaneça adequado.

Consultas raras

Preparar uma consulta executada uma vez adiciona complexidade sem ganho relevante.

Quantidade de statements

Cada instrução preparada consome memória da sessão. Gerar nomes dinâmicos para filtros opcionais pode criar milhares de entradas.

SQL dinâmico

Quando a estrutura muda conforme filtros, existem opções:

  • poucas consultas nomeadas predefinidas;
  • query parametrizada sem nome;
  • query builder;
  • um statement com condições opcionais, após medir o plano.

Filtro opcional em uma query

SELECT id, status
FROM orders
WHERE tenant_id = $1
  AND ($2::text IS NULL OR status = $2);

Esse formato reduz variações, mas o OR pode prejudicar índices. Compare com consultas distintas.

Identificadores dinâmicos

Prepared statements parametrizam valores, não nomes de coluna. Use allowlist para ORDER BY e identificadores.

Listas

const query = {
  name: 'products-by-ids-v1',
  text: `
    SELECT id, name
    FROM products
    WHERE tenant_id = $1
      AND id = ANY($2::uuid[])
  `,
  values: [tenantId, ids]
};

Um array mantém a estrutura estável.

Tipos explícitos

Cast pode evitar ambiguidade:

WHERE id = $1::uuid

Use tipos coerentes com o schema e não faça casts que inutilizam índices.

Alterações de schema

DDL pode invalidar planos preparados. O PostgreSQL replaneja ou retorna erro conforme a mudança e o contexto.

Deploy com SQL alterado

Se o texto de uma query nomeada muda, altere também o nome:

find-user-by-email-v2

Isso evita conflito com conexões antigas durante deploy gradual.

Blue-Green e Canary

Versões diferentes podem compartilhar o pool ou banco. Use nomes versionados e schema compatível. Consulte Canary Deploy no Node.js.

DEALLOCATE

DEALLOCATE find_order;

Remove uma instrução da sessão. DEALLOCATE ALL remove todas, mas não deve ser executado indiscriminadamente em conexões compartilhadas.

Conexões renovadas

Quando uma conexão encerra, seus prepared statements desaparecem. Novas conexões começam vazias.

PgBouncer

Em transaction pooling, a sessão do cliente pode mudar entre transações. Prepared statements de sessão exigem compatibilidade e configuração específica da versão do PgBouncer.

Transaction pooling

Não assuma que a próxima query usa o mesmo backend. Verifique a documentação do proxy e do driver antes de habilitar consultas nomeadas.

Session pooling

Quando a conexão cliente permanece associada a uma sessão backend, statements funcionam de forma mais natural, ao custo de menos multiplexação.

ORMs

Alguns ORMs e drivers usam prepared statements automaticamente; outros usam protocolo parametrizado sem manter statements nomeados. Entenda o comportamento real.

Prisma

O engine gerencia consultas e conexões. Não tente adicionar nomes de statements sem suporte oficial.

Drizzle e Kysely

Esses builders geram SQL parametrizado. O suporte a preparação depende do driver e da API usada. Consulte Drizzle ORM com PostgreSQL e Kysely com TypeScript.

Transações

Um statement preparado pode ser usado dentro de transações, desde que a conexão seja a mesma.

Veja Transações PostgreSQL no Node.js.

RLS

Políticas de Row-Level Security continuam sendo avaliadas. Teste planos e contexto quando o tenant é definido por sessão.

Segurança

Prepared statements ajudam a separar valores, mas não protegem:

  • identificadores concatenados;
  • raw SQL vulnerável;
  • autorização ausente;
  • tenant omitido;
  • funções dinâmicas inseguras;
  • privilégios excessivos.

Erros

Não envie nome do statement, SQL e valores sensíveis ao cliente. Registre apenas informações necessárias internamente.

Logs

Registre:

  • nome lógico da query;
  • duração;
  • rowCount;
  • SQLSTATE;
  • timeout;
  • conexão ou pool, quando necessário.

Métricas

Compare latência, CPU do banco, chamadas por segundo e cache hit de planos. Consulte Métricas Prometheus no Node.js.

pg_prepared_statements

SELECT name,
       statement,
       prepare_time,
       parameter_types,
       from_sql,
       generic_plans,
       custom_plans
FROM pg_prepared_statements;

A view mostra statements da sessão atual.

Benchmark

Compare:

  • query parametrizada sem nome;
  • consulta nomeada;
  • plano customizado;
  • plano genérico;
  • distribuições diferentes de valores;
  • com e sem proxy de conexões.

Teste de nome estável

test('reutiliza consulta nomeada', async () => {
  const first = await findUserByEmail('a@example.com');
  const second = await findUserByEmail('b@example.com');

  assert.ok(first);
  assert.ok(second);
});

O teste funcional não prova ganho; use métricas e inspeção da sessão.

Teste de SQL alterado

Simule duas versões usando o mesmo nome e textos diferentes. O teste deve impedir essa prática no código.

Teste com pool

Execute em várias conexões e confirme que cada sessão prepara quando necessário.

Teste com PgBouncer

Use a mesma configuração da produção. Um teste direto no PostgreSQL não representa transaction pooling.

Erros comuns

  • Nome dinâmico por valor: memória cresce.
  • Mesmo nome, SQL diferente: ocorre conflito.
  • Assumir escopo global: cada conexão possui seu statement.
  • Usar com transaction pooling sem validar: a sessão muda.
  • Esperar ganho automático: plano pode ficar pior.
  • Concatenar identificadores: injection continua possível.
  • Não versionar no deploy: conexões antigas recebem outro texto.

Boas práticas

  • Parametrize valores.
  • Use nome estável e versionado.
  • Prepare apenas consultas frequentes.
  • Evite nomes por valor.
  • Meça planos customizados e genéricos.
  • Considere o pool e o proxy.
  • Use allowlists para identificadores.
  • Monitore latência e CPU.
  • Teste deploy gradual.
  • Não trate preparação como autorização.

Conclusão

Os Prepared Statements no Node.js permitem reutilizar consultas parametrizadas por conexão e podem reduzir trabalho repetitivo no PostgreSQL. Com node-postgres, uma consulta nomeada é suficiente para ativar o mecanismo.

O benefício precisa ser medido. Pools, proxies, planos genéricos e alterações de schema influenciam o resultado. Quando nomes são estáveis, valores permanecem parametrizados e a equipe acompanha o plano real, prepared statements podem melhorar consultas frequentes sem comprometer segurança ou previsibilidade.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita