Checklist para colocar uma API em produção

Antes de publicar uma API, valide a configuração por variáveis de ambiente, exponha um health check, trate o SIGTERM, padronize erros, autentique, limite requisições e registre logs em JSON.

Atualizado em 6 min de leitura

Objetivos de aprendizado

Depois de ler este artigo, você vai conseguir:

  • Revisar os itens que separam uma API de testes de uma API em produção
  • Implementar health check, shutdown limpo, erros padronizados e rate limit em Node.js
  • Verificar com curl que cada item da checklist funciona
Nesta página

Uma API que funciona na sua máquina ainda não está pronta para receber tráfego real. Em produção, chegam requisições malformadas, clientes que repetem chamadas sem parar, deploys no meio de um pedido e erros que ninguém vê se não houver log. Cada item abaixo resolve uma dessas falhas.

O código usa Express 5 e a versão LTS ativa do Node.js, mas a checklist vale para qualquer linguagem. Se você ainda não sabe onde a API vai rodar, veja antes como hospedar uma API.

POST /itemsCORSsó a origem permitidaRate limitpor IP429Autenticaçãotoken Bearer401Validaçãotipo e tamanho400Handlera sua rota201Tratamento de errosem stack trace500Log{"method":"POST","status":201,"ms":12}
Cada camada pode encerrar a requisição com o status certo, e o log registra uma linha por resposta.

Quais itens a checklist cobre?#

  1. Configuração por variáveis de ambiente, com falha rápida se faltar algo.
  2. HTTPS e um endpoint de health check.
  3. Shutdown limpo ao receber SIGTERM.
  4. Erros padronizados, sem vazar detalhes internos.
  5. Autenticação e validação de entrada.
  6. Rate limit e limite de tamanho do corpo.
  7. CORS restrito.
  8. Logs estruturados.
  9. Timeouts.
  10. Verificação final com curl.

Os itens 5 a 7 cobrem boa parte dos riscos que o OWASP API Security Top 10 de 2023 lista, como autenticação quebrada, consumo irrestrito de recursos e configuração insegura. Para o lado de infraestrutura, veja o checklist de segurança de deploy.

Como configurar a API sem segredos no código?#

Leia tudo de variáveis de ambiente e pare o processo na inicialização se algo obrigatório estiver faltando. Assim, um erro silencioso em produção (um token undefined que aceita qualquer coisa) vira uma falha óbvia logo no primeiro start. Nunca coloque segredos no repositório. Se o conceito é novo para você, leia o que são variáveis de ambiente.

Defina também NODE_ENV=production. Com ele, o Express gera mensagens de erro menos verbosas e faz cache de templates, o que a documentação associa a um ganho de desempenho.

Como fica uma API mínima com a checklist aplicada?#

Instale a dependência e crie o server.mjs:

bash
npm install express@5
js
import { timingSafeEqual } from "node:crypto";
import { STATUS_CODES } from "node:http";
import express from "express";

const { API_TOKEN, ALLOWED_ORIGIN } = process.env;
if (!API_TOKEN) {
  console.error("API_TOKEN ausente");
  process.exit(1);
}
const PORT = Number(process.env.PORT ?? 3000);

const problem = (res, status, title, detail) =>
  res.status(status).type("application/problem+json").json({ type: "about:blank", title, status, detail });

const app = express();
app.disable("x-powered-by");
app.set("trust proxy", 1);
app.use(express.json({ limit: "100kb" }));

app.use((req, res, next) => {
  const start = Date.now();
  res.on("finish", () =>
    console.log(JSON.stringify({ method: req.method, path: req.path, status: res.statusCode, ms: Date.now() - start })),
  );
  next();
});

app.use((req, res, next) => {
  if (ALLOWED_ORIGIN && req.headers.origin === ALLOWED_ORIGIN) {
    res.set("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
    res.set("Access-Control-Allow-Headers", "Authorization, Content-Type");
    res.set("Vary", "Origin");
  }
  if (req.method === "OPTIONS") return res.sendStatus(204);
  next();
});

const hits = new Map();
setInterval(() => hits.clear(), 60_000).unref();
app.use((req, res, next) => {
  const n = (hits.get(req.ip) ?? 0) + 1;
  hits.set(req.ip, n);
  if (n > 100) {
    res.set("Retry-After", "60");
    return problem(res, 429, "Too Many Requests", "Limite de 100 requisições por minuto.");
  }
  next();
});

app.get("/health", (_req, res) => res.json({ status: "ok" }));

const safeEqual = (a, b) => {
  const x = Buffer.from(a);
  const y = Buffer.from(b);
  return x.length === y.length && timingSafeEqual(x, y);
};
app.use((req, res, next) => {
  const token = (req.headers.authorization ?? "").replace(/^Bearer /, "");
  if (!safeEqual(token, API_TOKEN)) return problem(res, 401, "Unauthorized", "Token ausente ou inválido.");
  next();
});

app.post("/items", async (req, res) => {
  const { name } = req.body ?? {};
  if (typeof name !== "string" || name.length === 0 || name.length > 100) {
    return problem(res, 400, "Bad Request", "name deve ser uma string de 1 a 100 caracteres.");
  }
  res.status(201).json({ name });
});

app.use((err, _req, res, _next) => {
  if (err.status >= 400 && err.status < 500) return problem(res, err.status, STATUS_CODES[err.status], "Requisição inválida.");
  console.error(err);
  problem(res, 500, "Internal Server Error", "Erro inesperado.");
});

const server = app.listen(PORT, "0.0.0.0", () => console.log(`API na porta ${PORT}`));
server.requestTimeout = 30_000;

process.on("SIGTERM", () => {
  server.close(() => process.exit(0));
  setTimeout(() => {
    server.closeAllConnections();
    process.exit(1);
  }, 10_000).unref();
});

O que cada bloco faz:

  • Config: sem API_TOKEN o processo encerra na hora. ALLOWED_ORIGIN é opcional.
  • trust proxy: atrás de um proxy reverso, req.ip seria o IP do proxy. Com trust proxy em 1, o Express usa o IP do cliente que o proxy informa. Ajuste o valor à quantidade de proxies na frente da API.
  • Corpo limitado: express.json({ limit: "100kb" }) recusa corpos grandes antes que consumam memória.
  • Log em JSON: uma linha por requisição, fácil de filtrar. Nunca registre tokens, senhas ou o corpo inteiro.
  • CORS: só a origem configurada recebe os cabeçalhos. Evite * em API autenticada. O Vary: Origin impede que um cache sirva a resposta errada.
  • Rate limit: 100 requisições por minuto por IP, em memória. Serve para uma instância só; com várias instâncias, guarde os contadores em Redis.
  • Autenticação: comparação em tempo constante com timingSafeEqual, que exige buffers do mesmo tamanho (a checagem de tamanho revela só o comprimento do token). O /health fica antes dela, para o monitor funcionar sem credencial.
  • Validação: nunca confie no que o cliente manda. Valide tipo e tamanho; para esquemas maiores, use uma biblioteca de validação.
  • Handler de erros: no Express 5, erros lançados em funções async chegam automaticamente ao middleware de erro, sem try/catch em todo handler.

Por que padronizar os erros?#

O formato application/problem+json da RFC 9457 define campos comuns: type, title, status, detail e instance. Com eles, clientes e SDKs tratam qualquer erro da sua API do mesmo jeito, e você nunca devolve um stack trace: o erro completo vai para o log e o cliente recebe só o resumo.

Para o 429, mande o cabeçalho Retry-After com os segundos de espera. Clientes bem escritos respeitam esse valor em vez de insistir.

Como encerrar a API sem derrubar requisições em andamento?#

Em um deploy ou restart, o processo costuma receber SIGTERM. Sem tratamento, ele morre no meio das requisições. No código acima, server.close() para de aceitar conexões novas e fecha as ociosas, deixando as ativas terminarem. Se passarem 10 segundos, closeAllConnections() força o fim. Os dois métodos existem em todas as versões do Node.js que ainda recebem suporte.

Se a sua API usa banco de dados ou fila, feche essas conexões dentro do callback do close().

Timeouts. Defina um limite para a requisição inteira (server.requestTimeout) e um timeout próprio para cada chamada que a API faz a outros serviços. Sem isso, um serviço lento segura conexões abertas até a API ficar sem recursos. Nas chamadas de saída, fetch(url, { signal: AbortSignal.timeout(5000) }) cancela a requisição após 5 segundos.

Como verificar que tudo funciona?#

Rode com API_TOKEN=segredo node server.mjs e teste cada item:

bash
curl -i localhost:3000/health
curl -i localhost:3000/items -X POST -H "Content-Type: application/json" -d '{"name":"a"}'
curl -i localhost:3000/items -X POST -H "Authorization: Bearer segredo" -H "Content-Type: application/json" -d '{"name":""}'
curl -i localhost:3000/items -X POST -H "Authorization: Bearer segredo" -H "Content-Type: application/json" -d '{"name":"caneta"}'

Esperado: 200, 401, 400 e 201. Para o rate limit, repita uma chamada mais de 100 vezes no mesmo minuto e confira o 429 com Retry-After. Para o shutdown, rode kill -TERM <pid> com uma requisição lenta em andamento e confira que ela termina antes de o processo sair.

Quais erros são mais comuns?#

  • Health check que sempre responde 200: se a API depende de banco, teste a conexão no /health para ele refletir a realidade.
  • CORS com * e credenciais: o navegador bloqueia, e liberar tudo "para funcionar" expõe a API.
  • Rate limit por IP atrás de proxy sem trust proxy: todos os clientes parecem ter o mesmo IP e são limitados juntos.
  • Logar o corpo da requisição: senhas e tokens acabam em texto puro nos logs.
  • Backup do banco nunca testado: teste a restauração, não só a criação do backup.
  • Nenhum monitor externo: sem alguém olhando /health, você descobre a queda pelos usuários.

Como fazer isso na Square Cloud#

Na Square Cloud você faz o deploy da API por ZIP, CLI, GitHub ou extensão do VS Code, e parte da checklist já vem resolvida pela plataforma. O resto continua no seu código. Item por item:

  1. HTTPS: toda aplicação web ganha um subdomínio *.squareweb.app com TLS automático, em todos os planos. Domínio próprio começa no Standard.
  2. Configuração: API_TOKEN e ALLOWED_ORIGIN entram como variáveis de ambiente no painel, na API ou na CLI e valem no próximo restart, sem rebuild. A plataforma define PORT, e o server.mjs escuta em 0.0.0.0 nessa porta.
  3. Disponibilidade: com o reinício automático ligado (o padrão no deploy pelo painel), a API volta sozinha quando o processo encerra com erro. Um estouro de memória, ao contrário, para a aplicação até você aumentar a RAM e iniciá-la de novo.
  4. Logs e métricas: as linhas JSON do log aparecem em tempo real no painel, na CLI e no VS Code, e as métricas de CPU, RAM e rede ficam guardadas por 24 horas. No Pro e no Enterprise, você ganha latência p50/p95/p99 e logs HTTP.
  5. Proteção: todos os planos incluem WAF e proteção DDoS L3/L4/L7, mas o rate limit por cliente continua sendo da API.
  6. Várias instâncias: o load balancing distribui o domínio entre aplicações sem sticky session e sem health check ativo. Leve os contadores do rate limit para o Redis e mantenha um monitor externo no /health. Load balancing e hospedagem de Redis começam no Standard, a partir de R$ 44,99 por mês.
  7. Banco: bancos hospedados ganham snapshot automático diário, e um snapshot restaura num banco novo, onde você testa a restauração sem tocar no de produção.

Não há autoscaling: para mais capacidade, você aumenta a RAM ou adiciona aplicações ao load balancing. Veja os planos em hospedagem de APIs e use os SDKs oficiais para automatizar deploy, restart e variáveis de ambiente.

Hospedagem de APIs

APIs em containers sempre ligados, sem cold start

Saiba mais

Perguntas frequentes

O que não pode faltar em uma API em produção?

Configuração por variáveis de ambiente, HTTPS, autenticação, validação de entrada, limite de requisições, tratamento de erros sem vazar detalhes internos, logs e um endpoint de health check.

Preciso de um health check mesmo em uma API pequena?

Sim. Um endpoint simples que responde 200 permite que um monitor externo detecte quando a API parou, sem depender de um usuário reclamar.

Qual status devo devolver quando o cliente passa do limite de requisições?

429 Too Many Requests, com o cabeçalho Retry-After indicando em quantos segundos o cliente pode tentar de novo.

Posso devolver a mensagem de erro do servidor para o cliente?

Não. Stack traces e mensagens internas revelam estrutura e dependências. Registre o erro completo no log e devolva ao cliente só um título e um status.

Próximo passo · Passo 5 de 5Como ler os logs da sua aplicação (e achar o erro)Para achar o erro nos logs, comece pelo fim, procure a primeira linha de erro e leia o stack trace. Veja passo a passo em Node.js e Python.