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
- Quais itens a checklist cobre?
- Como configurar a API sem segredos no código?
- Como fica uma API mínima com a checklist aplicada?
- Por que padronizar os erros?
- Como encerrar a API sem derrubar requisições em andamento?
- Como verificar que tudo funciona?
- Quais erros são mais comuns?
- Como fazer isso na Square Cloud
- Perguntas frequentes
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.
Quais itens a checklist cobre?#
- Configuração por variáveis de ambiente, com falha rápida se faltar algo.
- HTTPS e um endpoint de health check.
- Shutdown limpo ao receber SIGTERM.
- Erros padronizados, sem vazar detalhes internos.
- Autenticação e validação de entrada.
- Rate limit e limite de tamanho do corpo.
- CORS restrito.
- Logs estruturados.
- Timeouts.
- 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:
npm install express@5import { 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_TOKENo processo encerra na hora.ALLOWED_ORIGINé opcional. trust proxy: atrás de um proxy reverso,req.ipseria o IP do proxy. Comtrust proxyem 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. OVary: Originimpede 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/healthfica 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
asyncchegam automaticamente ao middleware de erro, semtry/catchem 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:
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
/healthpara 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:
- HTTPS: toda aplicação web ganha um subdomínio
*.squareweb.appcom TLS automático, em todos os planos. Domínio próprio começa no Standard. - Configuração:
API_TOKENeALLOWED_ORIGINentram como variáveis de ambiente no painel, na API ou na CLI e valem no próximo restart, sem rebuild. A plataforma definePORT, e oserver.mjsescuta em0.0.0.0nessa porta. - 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.
- 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.
- Proteção: todos os planos incluem WAF e proteção DDoS L3/L4/L7, mas o rate limit por cliente continua sendo da API.
- 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. - 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
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.
Mais em APIs
Ver todos os 15 artigos- Como hospedar uma APIHospedar uma API é rodar o servidor HTTP 24 horas num lugar com IP público e HTTPS. Veja o passo a passo com Node.js e Python, testes e erros comuns.
- Como hospedar uma aplicação Node.jsPara hospedar uma aplicação Node.js, prepare o package.json, leia porta e segredos do ambiente, escolha uma versão LTS e rode com node. Veja o passo a passo.
- Como hospedar FastAPI, Flask ou DjangoPara hospedar FastAPI, Flask ou Django, rode a aplicação com um servidor de produção (Uvicorn ou Gunicorn) em 0.0.0.0. Veja comandos, ajustes do Django e erros.
- Como hospedar um script ou API em PythonPara hospedar um script ou API em Python, fixe a versão, isole dependências em um venv, leia segredos do ambiente e trate o SIGTERM. Passo a passo com código.