npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@smdv/middleware

v1.1.4

Published

Middleware de autenticación estandarizado para microservicios Smart Sale (AdonisJS v5/v6, NestJS)

Readme

@smdv/middleware

Estandarización de autenticación, formato de respuesta y manejo de errores para microservicios Node.js (AdonisJS v5/v6, NestJS).

Dos modos de operación

gateway-trust (default)

Client → API Gateway → Auth Service (jwt.verify aquí)
                    → inyecta headers: owner-id, user-id, role-id…
                    → Microservicio lee headers (ya verificados)

El gateway es la única capa de verificación. Correcto cuando los microservicios son inaccesibles desde fuera del cluster. No requiere distribuir JWT_KEY a cada servicio.

jwt-verify (defensa en profundidad)

Client → API Gateway → [verifica JWT] → Microservicio → [verifica JWT de nuevo]
                                                         ↑ detecta tokens forjados
                                                           aunque eviten el gateway

Verifica la firma del JWT con jsonwebtoken.verify() en cada servicio. Protege contra llamadas internas que eluden el gateway.

Cuál usar: empieza con gateway-trust. Migra a jwt-verify en servicios críticos (pagos, órdenes) o si los microservicios son accesibles desde dentro del cluster sin pasar por el gateway.


Instalación

npm install @smdv/middleware

Uso

AdonisJS v5

1. app/Middleware/Authorization.ts:

// Modo gateway-trust (default)
export { AuthorizationMiddleware as default } from '@smdv/middleware'

// --- O con jwt-verify ---
import Env from '@ioc:Adonis/Core/Env'
import { AuthorizationMiddleware } from '@smdv/middleware'

export default new AuthorizationMiddleware({
  mode: 'jwt-verify',
  jwtKey: Env.get('JWT_KEY'),
})

2. Registro en start/kernel.ts (sin cambios si ya tienes authorization registrado):

Server.middleware.registerNamed({
  authorization: () => import('App/Middleware/Authorization'),
})

3. Type augmentation en contracts/request.ts (opcional pero recomendado):

import '@ioc:Adonis/Core/Request'
import { AuthContext } from '@smdv/middleware'

declare module '@ioc:Adonis/Core/Request' {
  interface RequestContract {
    authContext: AuthContext
  }
}

4. Acceso en managers/controllers:

const { ownerId, userId, roleId, employeeId } = (request as any).authContext

AdonisJS v6

// app/middleware/authorization_middleware.ts
export { AuthorizationMiddlewareV6 as default } from '@smdv/middleware'
import { AuthContext } from '@smdv/middleware'

async index({ request }: HttpContext) {
  const { ownerId } = (request as any).authContext as AuthContext
}

NestJS

// src/auth/auth.guard.ts
export { AuthGuard } from '@smdv/middleware'
@UseGuards(AuthGuard)
@Controller('items')
export class ItemsController {
  @Get()
  findAll(@Req() req: Request & { authContext: AuthContext }) {
    const { ownerId } = req.authContext
  }
}

AuthContext — campos disponibles

| Campo | Header origen | Descripción | |---|---|---| | ownerId | owner-id | ID del tenant/dueño del recurso | | userId | user-id | ID del usuario autenticado | | roleId | role-id | ID del rol del usuario | | employeeId | employee-id | ID del empleado (puede ser vacío) | | enterpriseId | enterprise-id | ID de la empresa en la que opera | | fullUserName | full-user-name | Nombre completo del usuario | | ownerName | owner-name | Nombre del tenant | | roleName | role-name | Nombre del rol |


Llamadas servicio a servicio

Cuando un servicio llama a otro, propaga todos los headers de autenticación:

import { AuthContext } from '@smdv/middleware'

export function forwardAuthHeaders(ctx: AuthContext): Record<string, string> {
  return {
    'owner-id':       ctx.ownerId,
    'user-id':        ctx.userId,
    'role-id':        ctx.roleId,
    'employee-id':    ctx.employeeId,
    'enterprise-id':  ctx.enterpriseId,
    'full-user-name': ctx.fullUserName,
    'owner-name':     ctx.ownerName,
    'role-name':      ctx.roleName,
  }
}
const { authContext } = request as any
await axios.post(OTHER_SERVICE_URL + '/api/resource', payload, {
  headers: forwardAuthHeaders(authContext),
})

Formato de respuesta — ApiResponse<T>

// Éxito
{ success: true,  message: string, response: T }

// Error
{ success: false, message: string, errors?: unknown }

Helpers

import {
  okResponse, createdResponse, updatedResponse, deletedResponse,
  badRequestResponse, notFoundResponse, unprocessableResponse,
  unauthorizedResponse, internalErrorResponse,
} from '@smdv/middleware'

return response.ok(okResponse(items))
return response.created(createdResponse(item))
return response.notFound(notFoundResponse('Recurso no encontrado'))
return response.badRequest(badRequestResponse('Error de validación', errors))

Exception Handler

AdonisJS v5

// app/Exceptions/Handler.ts
import { ExceptionHandlerV5 } from '@smdv/middleware'

export default class ExceptionHandler extends ExceptionHandlerV5 {
  constructor() {
    super(process.env.NODE_ENV !== 'production') // debug=true en dev
  }
}

Con logger personalizado:

import { createCustomLogger } from '@smdv/logwise'

const logger = createCustomLogger({ service: process.env.APP_NAME || 'my-service' })

export default class ExceptionHandler extends ExceptionHandlerV5 {
  constructor() {
    super(process.env.NODE_ENV !== 'production', logger)
  }
}

AdonisJS v6

// app/exceptions/handler.ts
import { ExceptionHandlerV6 } from '@smdv/middleware'
import app from '@adonisjs/core/services/app'

export default new ExceptionHandlerV6(!app.inProduction)

Qué captura automáticamente

| Código | HTTP | Respuesta | |---|---|---| | Instancia de ApiError | error.statusCode | { success: false, message, errors } | | E_VALIDATION_FAILURE | 422 | { success: false, message: 'Error de validación', errors: [...] } | | E_ROW_NOT_FOUND | 404 | { success: false, message: 'Recurso no encontrado' } | | E_UNAUTHORIZED_ACCESS | 401 | { success: false, message: 'No autorizado' } | | Errores 4xx explícitos | 4xx | { success: false, message: error.message } | | Cualquier otro | 500 | { success: false, message: 'Error interno del servidor' } |


Errores tipados

import {
  ApiError,
  ValidationError,
  NotFoundError,
  UnauthorizedError,
  ForbiddenError,
  ConflictError,
  UnprocessableEntityError,
  TooManyRequestsError,
  InternalServerError,
  ServiceUnavailableError,
  DatabaseError,
  ExternalServiceError,
} from '@smdv/middleware'

// Lanzar y dejar que ExceptionHandler responda
if (!item) throw new NotFoundError('Recurso no encontrado')
if (item.ownerId !== ctx.ownerId) throw new ForbiddenError()
if (errors.length) throw new ValidationError('Datos inválidos', errors)

handleError — framework-agnostic

import { handleError } from '@smdv/middleware'

try {
  await processPayment(payload)
} catch (err) {
  const { status, body } = handleError(err, logger, 'my-service')
  return response.status(status).json(body)
}

Detecta: ApiError, MySQL (ER_DUP_ENTRY, ER_NO_REFERENCED_ROW), Adonis (ValidationException, ModelNotFoundException), Mongoose, JWT.

Constantes HTTP y helpers de mensajes

import {
  HTTP_OK, HTTP_CREATED, HTTP_BAD_REQUEST, HTTP_NOT_FOUND,
  HTTP_INTERNAL_SERVER_ERROR,
  isSuccessCode, isClientError, isServerError,
  ERROR_CODES,
  Messages, getMessage,
  HttpStatus, SupportedLang,
} from '@smdv/middleware'

getMessage(SupportedLang.ES, 'NOT_FOUND') // → mensaje localizado (es/en)

Helpers Express

import { createErrorHandler, asyncHandler, notFoundHandler } from '@smdv/middleware'

app.use(notFoundHandler())
app.use(createErrorHandler({ logger }))
app.get('/items', asyncHandler(async (req, res) => { ... }))

Build

npm run build   # genera dist/
npm pack        # genera .tgz para instalación local