@ecdt/server-common
v2.1.1
Published
Conjunto de ferramentas e configurações comuns nos servidores da Econodata
Keywords
Readme
@ecdt/server-common
Conjunto de ferramentas e configurações comuns para servidores Express da Econodata.
O que esta biblioteca faz
@ecdt/server-common fornece um conjunto de middlewares pré-configurados para aplicações Express, eliminando a necessidade de configurar manualmente o parsing de requisições e a sanitização de tokens em cada serviço.
expressCommonMiddlewares(options?)
Retorna um array com os seguintes middlewares já configurados, prontos para serem aplicados no Express via app.use():
| Middleware | Descrição |
|---|---|
| bodyParser.text() | Faz o parse de corpos de requisição como texto puro |
| bodyParser.json() | Faz o parse de corpos de requisição no formato JSON |
| bodyParser.urlencoded({ extended: false }) | Faz o parse de corpos no formato application/x-www-form-urlencoded |
| devMktTokenSanitaze | Sanitiza o token de autenticação recebido no header Authorization ou no cookie de sessão |
Sobre o devMktTokenSanitaze
Este middleware normaliza tokens de autenticação que chegam codificados na URL (substituindo + e %2B por espaço). Ele verifica:
- O header
Authorization— se presente, sanitiza o valor diretamente. - O cookie da requisição — se não houver
Authorization, extrai o token do cookie de sessão e o define emreq.headers.authorization.
Resolução do nome do cookie de sessão
Por padrão, o nome do cookie é resolvido por requisição, a partir da origem (Origin > Referer > Host):
- Origem com subdomínio
hml(ex:hml.econodata.com.br,api.hml.econodata.com.br,hml-site.econodata.com.br) → cookiehml-ecdt_token_site - Demais origens → cookie
ecdt_token_site
Em ambos os casos, se o cookie preferido não existir na requisição, o outro nome é usado como fallback. A busca é por match exato do nome do cookie.
Também é possível fixar o nome explicitamente via options.cookieName (string ou array em ordem de preferência), ignorando a detecção por origem:
// Nome fixo
app.use(...expressCommonMiddlewares({ cookieName: "hml-ecdt_token_site" }));
// Múltiplos nomes, em ordem de preferência
app.use(...expressCommonMiddlewares({ cookieName: ["hml-ecdt_token_site", "ecdt_token_site"] }));expressCors(options)
Retorna o middleware de CORS padrão dos servidores Econodata (baseado no pacote cors).
| Opção | Tipo | Descrição |
|---|---|---|
| origins | string[] | Origins permitidas. Requisições de outras origins não recebem os headers CORS (o navegador bloqueia a resposta). Obrigatório se econodataOrigins não for true |
| econodataOrigins | boolean | Permite todas as origins https do domínio econodata.com.br (qualquer subdomínio, ex: https://app.econodata.com.br, https://api.hml.econodata.com.br) |
| extraHeaders | string[] | Headers adicionais além dos padrões |
| headers | string[] | Lista exata de headers permitidos — substitui os padrões. Não combinar com extraHeaders |
| methods | string[] | Lista exata de métodos permitidos. Default: todos (GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS) |
- Headers padrões:
Authorization,Content-Type,X-Requested-With - Credentials: sempre habilitado (
Access-Control-Allow-Credentials: true), por isso a origin permitida é sempre ecoada — nunca*
const { expressCors } = require("@ecdt/server-common");
// Configuração mínima
app.use(expressCors({ origins: ["https://app.econodata.com.br"] }));
// Todas as origins do domínio econodata.com.br (qualquer subdomínio)
app.use(expressCors({ econodataOrigins: true }));
// Combinando econodataOrigins com origins extras
app.use(expressCors({ econodataOrigins: true, origins: ["http://localhost:3000"] }));
// Headers extras além dos padrões
app.use(expressCors({ origins: [...], extraHeaders: ["X-Custom-Header"] }));
// Lista exata de headers e métodos
app.use(expressCors({ origins: [...], headers: ["Content-Type"], methods: ["GET", "POST"] }));setupRedMetrics(app, options?)
Instrumenta o servidor com métricas RED (Rate, Errors, Duration) e expõe o endpoint /metrics no formato Prometheus, em uma linha. Todas as requisições passam a alimentar o histograma http_request_duration_seconds{method, route, status_code}.
Requer
prom-clientinstalado no microsserviço (é umapeerDependency).
| Opção | Tipo | Descrição |
|---|---|---|
| serviceName | string | Nome do MS; vira o label service em todas as métricas. Fallback: env SERVICE_NAME > npm_package_name |
| metricsPath | string | Caminho do endpoint de métricas. Default: /metrics |
| buckets | number[] | Buckets de latência (segundos). Default: [0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] |
| collectDefaultMetrics | boolean | Coletar métricas default do Node (heap, event loop, GC...). Default: true |
| registry | Registry | Registry alternativo do prom-client. Default: registry global |
const express = require("express");
const { setupRedMetrics } = require("@ecdt/server-common");
const app = express();
// ANTES de montar as rotas:
setupRedMetrics(app, { serviceName: "dev-mkt-busca" });
app.get("/users/:id", (req, res) => res.json({ id: req.params.id }));
app.listen(3000);O label route usa o template da rota (/users/:id), nunca a URL crua — isso mantém a cardinalidade (e o custo de ingestão) sob controle.
Consultas (PromQL) habilitadas
# Latência p95 por endpoint
histogram_quantile(0.95, sum by (le, route)(rate(http_request_duration_seconds_bucket{service="dev-mkt-busca"}[5m])))
# Taxa de requisições por segundo (RPS)
sum(rate(http_request_duration_seconds_count{service="dev-mkt-busca"}[1m]))
# Erros 5xx por segundo
sum(rate(http_request_duration_seconds_count{service="dev-mkt-busca",status_code=~"5.."}[5m]))
# Endpoints que mais geram erro
topk(10, sum by (route)(rate(http_request_duration_seconds_count{service="dev-mkt-busca",status_code=~"5.."}[5m])))Para wiring manual, a lib também exporta redMetricsMiddleware(options?) e metricsHandler(registry?).
Servidor que não é express (Nitro/h3, fastify, http puro)
setupRedMetrics precisa de um app do express. Fora dele, monte as três peças à mão:
initRedMetrics(options?)— labelservice, métricas default do Node e o histograma. Devolve{ registry, histogram }.redMetricsMiddleware({ routeResolver })— o middleware(req, res, next). Semreq.route(que é do express) o labelroutecai emunmatched, então informe um resolver. Nunca use a URL crua: cada id na rota vira uma série temporal nova e a métrica fica impagável.renderMetrics(registry?)—{ contentType, body }para servir o endpoint.
// Nitro: server/plugins/red-metrics.ts
import { initRedMetrics, redMetricsMiddleware } from "@ecdt/server-common";
initRedMetrics({ serviceName: "site-vue3" });
// Nitro: server/middleware/01.red-metrics.ts
export default fromNodeMiddleware(
redMetricsMiddleware({ routeResolver: (req) => templateDaRota(req.url) }),
);
// Nitro: server/routes/metrics.get.ts
import { renderMetrics } from "@ecdt/server-common";
export default defineEventHandler(async (event) => {
const { contentType, body } = await renderMetrics();
setResponseHeader(event, "content-type", contentType);
return body;
});healthHandler() e healthPayload()
Contrato padrão do /health de todos os serviços e frontends do site. Responde sem chamada de saída, então serve às probes do k8s e ao health check do LB.
{ "status": "ok", "version": "26.10.13-8bd1622", "uptime": "3h", "env": "hml" }| Campo | Origem |
|---|---|
| status | Sempre ok |
| version | Env VERSION: a tag da imagem gerada pelo build do ecdt-iac (AA.MM.DD-<commit>). Sem ela, ou vazia, responde null |
| uptime | process.uptime() na maior unidade inteira, até dias: 10s, 59m, 1h, 1dia, 14dias |
| env | Env ENVIRONMENT, com fallback para ENV |
O Dockerfile precisa receber a versão do build e repassá-la ao processo (nos multi-stage, no estágio final):
ARG VERSION
ENV VERSION=${VERSION}const { healthHandler } = require("@ecdt/server-common");
app.get("/health", healthHandler());Fora do express (rota Nitro, por exemplo), use o corpo direto:
// Nitro: server/routes/health.ts
import { healthPayload } from "@ecdt/server-common";
export default defineEventHandler(() => healthPayload());Instalação
npm install @ecdt/server-commonUso
const express = require("express");
const { expressCommonMiddlewares, healthHandler } = require("@ecdt/server-common");
const app = express();
app.use(...expressCommonMiddlewares());
app.get("/health", healthHandler());
app.listen(3000);TypeScript
O pacote publica os tipos em src/index.d.ts (declarado em types no package.json), tipados com express e http. Não é necessário declarar declare module '@ecdt/server-common' na aplicação — se você tem um arquivo desses em src/@types/, pode apagar: uma declaração ambiente local sombreia os tipos do pacote e passa a mentir em silêncio se as assinaturas divergirem.
import express from "express";
import http from "http";
import { expressCommonMiddlewares, expressCors, setupRedMetrics, setupGracefulShutdown, healthHandler } from "@ecdt/server-common";
const app = express();
app.use(...expressCommonMiddlewares()); // RequestHandler[]
app.use(expressCors({ econodataOrigins: true })); // RequestHandler
setupRedMetrics(app, { serviceName: "meu-ms" });
app.get("/health", healthHandler()); // RequestHandler
const server = http.createServer(app);
server.listen(3000);
setupGracefulShutdown(server, { onShutdown: () => pool.end() });As interfaces de options são exportadas para reuso: ExpressCommonMiddlewaresOptions, ExpressCorsOptions (e sua base ExpressCorsOptionsBase), GracefulShutdownOptions, RedMetricsOptions e MetricsRegistry. O corpo do /health é tipado por HealthPayload.
ExpressCorsOptions é uma união que exige origins e/ou econodataOrigins: true — sem nenhum dos dois o expressCors lança no boot, então o erro aparece já em compilação:
expressCors({ econodataOrigins: true }); // ok
expressCors({ methods: ["GET"] }); // erro TS2345 — lançaria no bootprom-client aparece nos tipos como a interface estrutural MetricsRegistry, e não como import do pacote — assim quem não usa métricas compila sem tê-lo instalado.
