Library with koajs middlewares
npm install rivia-koa-middlewaresBiblioteca de middlewares para aplicações Koa.js que fornece funcionalidades essenciais como autenticação, validação, logging, rastreamento, idempotência e formatação de respostas JSON.
``bash`
npm install rivia-koa-middlewares
Middleware para autenticação e autorização usando AWS Cognito JWT tokens.
#### Funcionamento
- Verifica a presença do header Authorization na requisiçãoctx.state.user
- Valida o token JWT usando AWS Cognito JWT Verifier
- Extrai informações do usuário do token (sub, cognito:username, cognito:groups)
- Armazena as informações do usuário em
- Suporta validação de grupos do Cognito
- Permite extrair campos customizados do token
#### Variáveis de Ambiente Necessárias
`bash`
COGNITO_USER_POOL_ID=seu-user-pool-id
COGNITO_CLIENT_ID=seu-client-id
#### Exemplo de Uso
`javascript
import Koa from 'koa';
import Router from 'koa-router';
import { authorizer } from 'rivia-koa-middlewares';
const app = new Koa();
const router = new Router();
// Uso básico - apenas valida o token
router.get('/protected', authorizer(), async (ctx) => {
// ctx.state.user contém: { sub, cognito:username, cognito:groups }
ctx.body = { message: 'Acesso autorizado', user: ctx.state.user };
});
// Com validação de grupos
router.get('/admin', authorizer({ groups: ['admin'] }), async (ctx) => {
ctx.body = { message: 'Acesso de administrador' };
});
// Com múltiplos grupos permitidos
router.get('/staff', authorizer({ groups: ['user', 'admin'] }), async (ctx) => {
ctx.body = { message: 'Acesso de staff' };
});
// Com campos customizados do token
router.get('/custom', authorizer({
customFieldNames: ['email', 'custom:role']
}), async (ctx) => {
// ctx.state.user agora inclui também email e custom:role
ctx.body = { user: ctx.state.user };
});
// Com tipo de token diferente (access token ao invés de id token)
router.get('/access', authorizer({ tokenType: 'access' }), async (ctx) => {
ctx.body = { message: 'Usando access token' };
});
app.use(router.routes());
app.listen(3000);
`
#### Opções de Configuração
- groups (array): Lista de grupos do Cognito permitidos. Se especificado, o usuário deve pertencer a pelo menos um dos grupos.tokenType
- (string): Tipo de token a ser validado. Padrão: 'id'. Pode ser 'id' ou 'access'.customFieldNames
- (array): Lista de campos adicionais do token JWT a serem extraídos e incluídos em ctx.state.user.
#### Respostas de Erro
- 401: Quando o header Authorization está ausente403
- : Quando o token é inválido ou o usuário não pertence aos grupos requeridos
---
Middleware que garante idempotência de requisições, evitando processamento duplicado.
#### Funcionamento
- Gera um hash único baseado no método HTTP, URL, body da requisição e ID do usuário (se autenticado)
- Armazena a resposta da primeira requisição no DynamoDB
- Para requisições idênticas dentro do período de expiração, retorna a resposta armazenada sem processar novamente
- Usa o DynamoDB para armazenar as respostas com TTL configurável
#### Exemplo de Uso
`javascript
import Koa from 'koa';
import Router from 'koa-router';
import bodyParser from 'koa-bodyparser';
import { idempotence } from 'rivia-koa-middlewares';
const app = new Koa();
const router = new Router();
app.use(bodyParser());
// Idempotência com tempo padrão de 30 segundos
router.post('/create-order', idempotence(), async (ctx) => {
// Esta lógica só será executada na primeira requisição
// Requisições idênticas nos próximos 30 segundos retornarão a mesma resposta
const order = await createOrder(ctx.request.body);
ctx.body = { orderId: order.id, status: 'created' };
});
// Idempotência com tempo customizado (10 segundos)
router.post('/payment', idempotence(10), async (ctx) => {
const payment = await processPayment(ctx.request.body);
ctx.body = { paymentId: payment.id, status: 'processed' };
});
app.use(router.routes());
app.listen(3000);
`
#### Parâmetros
- secondsToExpire (number, opcional): Tempo em segundos para expirar o cache da resposta. Padrão: 30.
#### Como Funciona
1. Primeira requisição: Processa normalmente e armazena a resposta no DynamoDB
2. Requisições subsequentes idênticas: Retorna a resposta armazenada sem processar
3. Após expiração: A próxima requisição idêntica será processada novamente
Nota: O hash considera método, URL, body e o sub do usuário (se ctx.state.user.sub existir), garantindo que diferentes usuários tenham caches separados.
---
Middleware que formata respostas como JSON e adiciona tratamento de erros padronizado.
#### Funcionamento
- Converte objetos JavaScript em strings JSON
- Adiciona requestId ao corpo da resposta (se disponível em ctx.requestId)application/json
- Define o content-type como
- Captura erros e formata respostas de erro de forma consistente
- Preserva strings simples sem conversão
#### Exemplo de Uso
`javascript
import Koa from 'koa';
import Router from 'koa-router';
import bodyParser from 'koa-bodyparser';
import { jsonResponse, trace } from 'rivia-koa-middlewares';
const app = new Koa();
const router = new Router();
app.use(bodyParser());
app.use(trace); // Necessário para gerar ctx.requestId
app.use(jsonResponse);
router.get('/success', async (ctx) => {
ctx.body = { message: 'Sucesso', data: { id: 1 } };
// Resposta será: {"message":"Sucesso","data":{"id":1},"requestId":"uuid-gerado"}
});
router.get('/error', async (ctx) => {
throw new Error('Algo deu errado');
// Resposta será: {"message":"Algo deu errado","requestId":"uuid-gerado"}
// Status: 500
});
router.get('/custom-error', async (ctx) => {
const error = new Error('Não encontrado');
error.statusCode = 404;
throw error;
// Resposta será: {"message":"Não encontrado","requestId":"uuid-gerado"}
// Status: 404
});
router.get('/string', async (ctx) => {
ctx.body = 'Resposta simples';
// Resposta será: "Resposta simples" (sem conversão JSON)
});
app.use(router.routes());
app.listen(3000);
`
#### Comportamento
- Objetos: Convertidos para JSON string e recebem requestId se disponível
- Strings: Mantidas como estão, sem conversão
- Erros: Capturados automaticamente, status code do erro é preservado (padrão: 500)
---
Middleware para logging de requisições e respostas HTTP.
#### Funcionamento
- Registra a requisição completa no início
- Calcula o tempo de processamento
- Registra método, URL, tempo de resposta e status HTTP
- Registra a resposta completa ao final
#### Exemplo de Uso
`javascript
import Koa from 'koa';
import Router from 'koa-router';
import { inOutLogger } from 'rivia-koa-middlewares';
const app = new Koa();
const router = new Router();
app.use(inOutLogger);
router.get('/users', async (ctx) => {
ctx.body = { users: [] };
});
router.post('/users', async (ctx) => {
ctx.body = { id: 1, name: 'John' };
});
app.use(router.routes());
app.listen(3000);
`
#### Logs Gerados
O middleware gera os seguintes logs (usando rivia-logs):
1. Request - Objeto completo da requisiçãoGET /users - 15ms
2. - Método, URL e tempo de processamentoStatus: 200
3. - Status HTTP da respostaResponse
4. - Objeto completo da resposta
---
Middleware que gera um ID único de rastreamento para cada requisição.
#### Funcionamento
- Gera um UUID v4 único para cada requisição
- Armazena o ID em ctx.requestId
- Útil para rastreamento e correlação de logs
#### Exemplo de Uso
`javascript
import Koa from 'koa';
import Router from 'koa-router';
import { trace, jsonResponse } from 'rivia-koa-middlewares';
const app = new Koa();
const router = new Router();
app.use(trace);
app.use(jsonResponse); // jsonResponse usa ctx.requestId
router.get('/users', async (ctx) => {
// ctx.requestId contém um UUID único
console.log('Request ID:', ctx.requestId);
ctx.body = { users: [], requestId: ctx.requestId };
});
app.use(router.routes());
app.listen(3000);
`
#### Integração com Outros Middlewares
O trace é frequentemente usado em conjunto com jsonResponse, que automaticamente adiciona o requestId às respostas JSON.
---
Middleware para validação de requisições usando schemas Joi.
#### Funcionamento
- Valida o body da requisição contra um schema Joi
- Retorna erro 400 com detalhes da validação se houver falha
- Permite que a requisição prossiga apenas se a validação passar
#### Exemplo de Uso
`javascript
import Koa from 'koa';
import Router from 'koa-router';
import bodyParser from 'koa-bodyparser';
import Joi from 'joi';
import { validator } from 'rivia-koa-middlewares';
const app = new Koa();
const router = new Router();
app.use(bodyParser());
// Schema de validação
const userSchema = Joi.object({
username: Joi.string().min(3).max(30).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(18).max(120).optional()
}).unknown(false); // Rejeita campos não definidos no schema
const loginSchema = Joi.object({
username: Joi.string().required(),
password: Joi.string().min(6).required()
});
// Aplicando validação
router.post('/users', validator(userSchema), async (ctx) => {
// ctx.request.body já está validado aqui
const user = await createUser(ctx.request.body);
ctx.body = { id: user.id, message: 'Usuário criado' };
});
router.post('/login', validator(loginSchema), async (ctx) => {
const token = await authenticate(ctx.request.body);
ctx.body = { token };
});
app.use(router.routes());
app.listen(3000);
`
#### Resposta de Erro
Quando a validação falha, o middleware retorna:
`json`
{
"message": "Invalid request",
"details": [
{
"message": "\"username\" is required",
"path": ["username"],
"type": "any.required"
}
]
}
Status HTTP: 400 Bad Request
---
Aqui está um exemplo completo usando múltiplos middlewares juntos:
`javascript
import Koa from 'koa';
import Router from 'koa-router';
import bodyParser from 'koa-bodyparser';
import Joi from 'joi';
import {
trace,
inOutLogger,
authorizer,
validator,
idempotence,
jsonResponse
} from 'rivia-koa-middlewares';
const app = new Koa();
const router = new Router();
// Middlewares globais (ordem importa!)
app.use(bodyParser());
app.use(trace); // 1. Gera requestId
app.use(inOutLogger); // 2. Logs de requisição/resposta
app.use(jsonResponse); // 3. Formatação JSON e tratamento de erros
// Rota pública com validação
const loginSchema = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().required()
});
router.post('/login', validator(loginSchema), async (ctx) => {
ctx.body = { token: 'jwt-token-here' };
});
// Rota protegida com autenticação
router.get('/profile', authorizer(), async (ctx) => {
ctx.body = {
user: ctx.state.user,
message: 'Perfil do usuário'
};
});
// Rota protegida com autenticação e grupo específico
router.get('/admin/users', authorizer({ groups: ['admin'] }), async (ctx) => {
ctx.body = { users: [] };
});
// Rota com idempotência (útil para pagamentos, criação de recursos)
const orderSchema = Joi.object({
items: Joi.array().items(Joi.object({
productId: Joi.string().required(),
quantity: Joi.number().integer().min(1).required()
})).min(1).required()
});
router.post('/orders',
authorizer(), // Requer autenticação
validator(orderSchema), // Valida o body
idempotence(60), // Idempotência de 60 segundos
async (ctx) => {
const order = await createOrder({
userId: ctx.state.user.sub,
items: ctx.request.body.items
});
ctx.body = { orderId: order.id, status: 'created' };
}
);
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000, () => {
console.log('Servidor rodando na porta 3000');
});
`
A ordem dos middlewares é importante. Segue uma ordem recomendada:
1. bodyParser (do koa-bodyparser) - Parse do bodytrace
2. - Gera requestIdinOutLogger
3. - Logging (deve vir antes de outros middlewares que podem modificar ctx)authorizer
4. - Autenticação (se necessário)validator
5. - Validação (se necessário)idempotence
6. - Idempotência (se necessário)jsonResponse
7. Rotas da aplicação
8. - Formatação final (pode vir antes ou depois das rotas, mas geralmente depois)
- aws-jwt-verify: Validação de tokens JWT do Cognitojoi
- : Validação de schemaslodash
- : Utilitáriosluxon
- : Manipulação de datasobject-hash
- : Geração de hash para idempotênciarivia-dynamodb
- : Cliente DynamoDBrivia-logs
- : Sistema de logginguuid`: Geração de UUIDs
-
ISC