@groundbrick/express-adapter
v0.5.1
Published
Express.js integration adapter with middleware, controllers, and error handling
Maintainers
Readme
@groundbrick/express-adapter
Express.js integration adapter para o microframework TypeScript com middleware estruturado, controllers base e tratamento de erros.
Features Implementadas
- 🚀 ExpressApp Class - Classe principal com configuração Express pré-configurada
- 📋 BaseController - Controller base com async handlers e utilitários
- 🛡️ Middleware Stack - Error handling, request logging e response formatting
- 🔐 JWT Authentication - Middleware de autenticação JWT com role-based access
- 📊 Response Helpers - Formatação padronizada de respostas da API
- ✅ Validation Utilities - Helpers de validação e sanitização
- 📄 Pagination Support - Utilitários de paginação e ordenação
- 📡 Sentry (Error Tracking + APM) - Observabilidade opt-in, ativada por variáveis de ambiente
Instalação
npm install @groundbrick/express-adapterPeer Dependencies
npm install express cors helmet compression express-rate-limit jsonwebtoken
npm install --save-dev @types/express @types/cors @types/compression @types/jsonwebtokenQuick Start
1. Configuração Básica da Aplicação
// src/server.ts
import { ExpressApp } from '@groundbrick/express-adapter';
import { createLogger } from '@groundbrick/logger';
const app = new ExpressApp({
port: 3000,
host: '0.0.0.0',
cors: {
origin: process.env.CORS_ORIGIN || 'http://localhost:5173',
credentials: true
},
trustProxy: true,
security: {
helmet: true,
rateLimit: {
windowMs: 15 * 60 * 1000,
max: 100
}
}
});
app.start().then(() => {
console.log('Server started successfully');
});2. JWT Authentication Setup
// src/middleware/auth.ts
import { JwtMiddleware } from '@groundbrick/express-adapter';
import { createLogger } from '@groundbrick/logger';
const logger = createLogger({ context: 'auth' });
export const jwtAuth = new JwtMiddleware({
secret: process.env.JWT_SECRET || 'your-secret-key',
algorithms: ['HS256'],
skipPaths: ['/auth/login', '/auth/register', '/health'],
headerName: 'authorization',
cookieName: 'token'
}, logger);
// Usage in routes
import { Router } from 'express';
const router = Router();
// Protected route
router.get('/profile', jwtAuth.authenticate, userController.getProfile);
// Optional auth (user context if token present)
router.get('/public', jwtAuth.optional, contentController.getPublicContent);
// Role-based access
router.delete('/admin/users/:id',
jwtAuth.authenticate,
jwtAuth.requireRole(['admin', 'super-admin']),
adminController.deleteUser
);3. Controller Implementation
// src/controllers/UserController.ts
import { Request, Response, NextFunction } from 'express';
import { BaseController, AuthenticatedRequest } from '@groundbrick/express-adapter';
import { Logger } from '@groundbrick/logger';
import { ServiceResult } from '@groundbrick/service-base';
export class UserController extends BaseController {
constructor(logger: Logger, private userService: UserService) {
super(logger);
}
getProfile = this.asyncHandler(async (req: AuthenticatedRequest, res: Response): Promise<ServiceResult<User>> => {
const userId = req.user!.userId; // Safe because of JWT middleware
const user = await this.userService.getUserById(userId);
return ServiceResult.success(user);
});
login = this.asyncHandler(async (req: Request, res: Response): Promise<ServiceResult<LoginResponse>> => {
const { email, password } = req.body;
const user = await this.userService.authenticateUser(email, password);
const token = JwtMiddleware.generateToken(
{ userId: user.id, email: user.email, roles: user.roles },
process.env.JWT_SECRET!,
'24h'
);
return ServiceResult.success({ user, token });
});
}4. Login/Logout Implementation
// src/controllers/AuthController.ts
export class AuthController extends BaseController {
login = this.asyncHandler(async (req: Request, res: Response): Promise<void> => {
const { email, password } = req.body;
const user = await this.authService.login(email, password);
const token = JwtMiddleware.generateToken(
{
userId: user.id,
email: user.email,
roles: user.roles || []
},
process.env.JWT_SECRET!,
'24h'
);
// Set as cookie (optional)
res.cookie('token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 24 * 60 * 60 * 1000 // 24 hours
});
res.json({
success: true,
data: { user, token },
meta: { timestamp: new Date().toISOString() }
});
});
logout = this.asyncHandler(async (req: Request, res: Response): Promise<void> => {
res.clearCookie('token');
res.json({
success: true,
message: 'Logged out successfully',
meta: { timestamp: new Date().toISOString() }
});
});
}API Reference
JwtMiddleware
interface JwtMiddlewareConfig {
secret?: string;
algorithms?: string[];
skipPaths?: string[];
cookieName?: string;
headerName?: string;
onTokenExpired?: (req: Request, res: Response) => void;
onTokenInvalid?: (req: Request, res: Response) => void;
}
class JwtMiddleware {
constructor(config: JwtMiddlewareConfig, logger: Logger);
// Required authentication
authenticate: RequestHandler;
// Optional authentication
optional: RequestHandler;
// Role-based access control
requireRole(roles: string | string[]): RequestHandler;
// Static helpers
static generateToken(payload: JwtPayload, secret: string, expiresIn?: string): string;
static decodeToken(token: string): JwtPayload | null;
}JWT Payload Structure
interface JwtPayload {
userId: number;
email: string;
roles?: string[];
[key: string]: unknown;
}Authentication Patterns
// 1. Basic protected route
router.get('/protected', jwtAuth.authenticate, controller.method);
// 2. Optional authentication
router.get('/mixed', jwtAuth.optional, (req: AuthenticatedRequest, res) => {
if (req.user) {
// User is authenticated
} else {
// Anonymous access
}
});
// 3. Role-based access
router.post('/admin',
jwtAuth.authenticate,
jwtAuth.requireRole('admin'),
adminController.method
);
// 4. Multiple roles
router.delete('/resource/:id',
jwtAuth.authenticate,
jwtAuth.requireRole(['admin', 'moderator']),
controller.method
);ExpressApp
interface ExpressConfig {
port?: number;
host?: string;
cors?: {
origin?: string | string[] | boolean;
credentials?: boolean;
methods?: string[];
allowedHeaders?: string[];
};
security?: {
helmet?: boolean;
rateLimit?: {
windowMs?: number;
max?: number;
message?: string;
};
};
compression?: boolean;
bodyParser?: {
json?: { limit?: string };
urlencoded?: { limit?: string; extended?: boolean };
};
logger?: Partial<LoggerConfig>;
trustProxy?: boolean;
}BaseController
abstract class BaseController {
constructor(logger: Logger);
protected asyncHandler(handler: RouteHandler): RouteHandler;
protected getIdFromParams(req: Request, paramName?: string): string;
protected getQueryParam(req: Request, paramName: string, defaultValue?: string): string | undefined;
protected getQueryParamAsNumber(req: Request, paramName: string, defaultValue?: number): number | undefined;
protected getPaginationParams(req: Request): { page: number; limit: number; offset: number };
}Response Helpers
// Success responses
ResponseHelper.success(res, data, 'Operation successful');
ResponseHelper.created(res, newUser, 'User created');
ResponseHelper.noContent(res);
// Error responses
ResponseHelper.error(res, 'Something went wrong', 400);
ResponseHelper.notFound(res, 'User not found');
ResponseHelper.unauthorized(res, 'Invalid credentials');
ResponseHelper.forbidden(res, 'Access denied');
// Authentication responses
ResponseHelper.unauthorized(res); // 401
ResponseHelper.forbidden(res); // 403Authentication Error Responses
JWT middleware returns standardized error responses:
// No token provided
{
"success": false,
"error": {
"message": "No token provided",
"code": "NO_TOKEN"
},
"meta": {
"timestamp": "2024-01-15T10:30:00.000Z",
"requestId": "abc123"
}
}
// Token expired
{
"success": false,
"error": {
"message": "Token has expired",
"code": "TOKEN_EXPIRED"
}
}
// Invalid token
{
"success": false,
"error": {
"message": "Invalid token",
"code": "TOKEN_INVALID"
}
}
// Insufficient permissions
{
"success": false,
"error": {
"message": "Insufficient permissions",
"code": "INSUFFICIENT_PERMISSIONS",
"details": {
"requiredRoles": ["admin"],
"userRoles": ["user"]
}
}
}Middleware Stack Automático
A classe ExpressApp configura automaticamente:
- Security: Helmet para headers de segurança
- Rate Limiting: Proteção contra ataques de força bruta
- CORS: Configuração de Cross-Origin Resource Sharing
- Compression: Compressão gzip/deflate
- Body Parsing: JSON e URL-encoded parsing
- Request Logging: Log automático de requests com timing
- Response Formatting: Formatação padronizada de respostas
- Error Handling: Tratamento global de erros
- Sentry: Error handler do Sentry (quando ativado por
SENTRY_DSN), antes do handler global
Sentry (Error Tracking + APM)
O adapter integra o Sentry Node SDK
como uma capacidade opt-in, ativada simplesmente pela presença da variável SENTRY_DSN.
Cobre captura de erros, tracing (HTTP, Express e PostgreSQL pg) e profiling de CPU, dando
visibilidade sobre erros e gargalos de performance (endpoints e queries lentas). Se SENTRY_DSN
não estiver definida, é um no-op completo (apps sem DSN e testes não são afetados).
Como funciona
Em ESM, o Sentry.init() tem de correr antes de qualquer módulo instrumentado (http,
express, pg) ser carregado. Por isso o package expõe um módulo side-effecting que deve ser
pré-carregado via a flag --import do Node, e o ExpressApp regista automaticamente o error
handler do Sentry (antes do error handler global) quando o SDK foi inicializado.
Ativação
- Pré-carregar o módulo de instrumentação nos scripts do app:
// package.json do app
{
"scripts": {
"dev": "tsx watch --import @groundbrick/express-adapter/instrument src/server.ts",
"start": "node --import @groundbrick/express-adapter/instrument dist/src/server.js"
}
}- Definir as variáveis de ambiente (DSN vazio = Sentry desativado):
# Sentry (error tracking + APM). SENTRY_DSN vazio = desativado.
SENTRY_DSN=https://<key>@<org>.ingest.sentry.io/<project>
SENTRY_ENVIRONMENT=development
# Opcional: release (ex.: SHA do commit)
SENTRY_RELEASE=
# Tracing: % de transações capturadas. Padrão 0.1 (10%); subir para diagnosticar.
SENTRY_TRACES_SAMPLE_RATE=0.1
# Profiling: % das transações amostradas que também são profiled. Padrão 0 (desligado).
SENTRY_PROFILES_SAMPLE_RATE=0Dados pessoais
Nada de dados pessoais ou segredos sai da aplicação (@groundbrick/logger/privacy):
- o corpo dos pedidos nunca é capturado (
maxIncomingRequestBodySize: 'none') esendDefaultPiifica desligado; - cookies, query strings e cabeçalhos de autenticação (
Authorization,Cookie,X-API-Key) são removidos ou mascarados; - e-mails, telefones, tokens de bot e query strings de URLs são mascarados em mensagens,
exceções, breadcrumbs, spans e logs; chaves sensíveis (
password,token,secret,apiKey,email,phone,to,body...) são filtradas emextra/contexts; - breadcrumbs de consola (linhas de log em bruto) não são enviados.
Profiling
O @sentry/profiling-node (módulo nativo) só é carregado quando
SENTRY_PROFILES_SAMPLE_RATE > 0. Se não carregar (ex.: sem binário para a plataforma), o
Sentry arranca sem profiling e é registado um aviso — a aplicação nunca deixa de arrancar.
Não é preciso código no server.ts: o error handler do Sentry é montado pelo ExpressApp
(depois das rotas, antes do error handler global), pelo que as respostas de erro continuam a sair
no formato padrão { success: false, error: { ... } }. Em testes (sem o preload --import) o
Sentry nunca inicializa, logo a integração fica inerte.
Exemplo Completo com Autenticação
// src/server.ts
import { ExpressApp, JwtMiddleware } from '@groundbrick/express-adapter';
import { createLogger } from '@groundbrick/logger';
import { UserController } from './controllers/UserController';
import { AuthController } from './controllers/AuthController';
const logger = createLogger({ context: 'api-server' });
// JWT Setup
const jwtAuth = new JwtMiddleware({
secret: process.env.JWT_SECRET!,
skipPaths: ['/auth/login', '/auth/register', '/health'],
algorithms: ['HS256']
}, logger);
// Express App
const app = new ExpressApp({
port: 3000,
cors: { origin: true, credentials: true },
trustProxy: true
});
// Auth routes (no JWT required)
app.addRoutes([
{ method: 'post', path: '/auth/login', handler: authController.login },
{ method: 'post', path: '/auth/register', handler: authController.register },
{ method: 'post', path: '/auth/logout', handler: authController.logout }
]);
// Protected routes
app.addRoutes([
{
method: 'get',
path: '/api/profile',
handler: userController.getProfile,
middleware: [jwtAuth.authenticate]
},
{
method: 'get',
path: '/api/admin/users',
handler: userController.getAllUsers,
middleware: [jwtAuth.authenticate, jwtAuth.requireRole('admin')]
}
]);
app.start();Integração com Outros Packages
Este adapter funciona perfeitamente com:
@groundbrick/logger- Sistema de logging (obrigatório)@groundbrick/service-base- Base classes para services (obrigatório)@groundbrick/db-core- Abstração de banco de dados
Dependencies
Required
@groundbrick/logger- Logging functionality@groundbrick/service-base- Service base classes@groundbrick/db-core- Database core abstractions@sentry/node- Error tracking + APM (Sentry Node SDK)@sentry/profiling-node- CPU profiling integration para o Sentrydotenv- Carrega.envno preload de instrumentação do Sentry
Peer Dependencies
express- Express.js frameworkcors- CORS middlewarehelmet- Security middlewarecompression- Response compressionexpress-rate-limit- Rate limitingjsonwebtoken- JWT token handling
License
MIT
