Saltearse al contenido

Guía de Plugins

Aprende a usar y crear plugins para Veloce.

El sistema de plugins de Veloce te permite extender el framework con funcionalidad adicional. Los plugins pueden agregar rutas, middleware, modificar la aplicación e integrarse con servicios externos.

import type { VeloceTS } from 'veloce-ts';
interface Plugin {
name: string;
version?: string;
dependencies?: string[];
install(app: VeloceTS): void | Promise<void>;
}

Genera documentación OpenAPI automáticamente desde tus rutas.

import { Veloce, OpenAPIPlugin } from 'veloce-ts';
const app = new Veloce();
app.usePlugin(new OpenAPIPlugin({
path: '/openapi.json',
docsPath: '/docs',
title: 'Mi API',
version: '1.0.0',
description: 'Descripción de la API',
}));

Opciones:

  • path: Ruta para servir la especificación JSON de OpenAPI (por defecto: /openapi.json)
  • docsPath: Ruta para servir Swagger UI (por defecto: /docs)
  • title: Título de la API
  • version: Versión de la API
  • description: Descripción de la API

Acceso:

  • Swagger UI: http://localhost:3000/docs
  • Especificación OpenAPI: http://localhost:3000/openapi.json

Habilita soporte para GraphQL con generación automática de esquemas.

import { Veloce, GraphQLPlugin } from 'veloce-ts';
const app = new Veloce();
app.usePlugin(new GraphQLPlugin({
path: '/graphql',
playground: true,
}));

Opciones (constructor actual):

  • path: Ruta del endpoint GraphQL (por defecto: /graphql)
  • playgroundPath: Ruta del Playground (por defecto: /graphql/playground)
  • playground: Habilitar GraphQL Playground (por defecto: true)
  • context: Fábrica de contexto por petición

La introspección y CORS se gestionan según el plugin y el middleware global; ver la guía GraphQL.

Registra rutas definidas con @WebSocket. El constructor no acepta opciones; la ruta es el argumento del decorador. En Node.js el upgrade WebSocket devuelve 501 — usa Bun o Deno; ver WebSockets y Limitaciones y hoja de ruta.

import { Veloce, WebSocketPlugin } from 'veloce-ts';
const app = new Veloce();
app.usePlugin(new WebSocketPlugin());

Registra /health, /ready y /live. Los checks personalizados van en un array checks.

import { Veloce, HealthCheckPlugin, HealthCheckers } from 'veloce-ts';
import { sql } from 'drizzle-orm';
const app = new Veloce();
app.usePlugin(new HealthCheckPlugin({
path: '/health',
readyPath: '/ready',
livePath: '/live',
checks: [
HealthCheckers.database(async () => {
await db.execute(sql`SELECT 1`);
return true;
}),
HealthCheckers.disk(process.cwd(), 90),
HealthCheckers.memory(512),
],
}));

Respuesta de ejemplo (/health):

{
"status": "healthy",
"uptime": 3600,
"timestamp": "2026-03-27T12:00:00.000Z",
"checks": {
"database": { "status": "healthy", "message": "Database connection OK" },
"disk": { "status": "healthy", "message": "Disk usage: 45.0% (threshold: 90%)", "usagePercent": 45 },
"memory": { "status": "healthy", "message": "Heap usage: 62.00MB / 512MB", "heapUsedMB": "62.00", "maxUsageMB": 512 }
}
}

Helpers de HealthCheckers:

  • HealthCheckers.alwaysHealthy() — resultado fijo healthy (pruebas)
  • HealthCheckers.database(pingFn)pingFn devuelve boolean o Promise<boolean>; unhealthy si es false o lanza
  • HealthCheckers.memory(maxUsageMB?) — heap frente al límite (por defecto 512 MB)
  • HealthCheckers.disk(path?, maxUsagePercent?) — usa fs.statfs donde exista (Node 18+ / Bun)
import { Veloce, OpenAPIPlugin } from 'veloce-ts';
const app = new Veloce();
// Usar un solo plugin
app.usePlugin(new OpenAPIPlugin());
// Usar múltiples plugins
app.usePlugin(new OpenAPIPlugin());
app.usePlugin(new GraphQLPlugin());
const app = new Veloce({
plugins: [
new OpenAPIPlugin({
path: '/api-docs.json',
docsPath: '/api-docs',
}),
new GraphQLPlugin({
path: '/gql',
playground: process.env.NODE_ENV !== 'production',
}),
],
});
import { Plugin, Veloce } from 'veloce-ts';
class HelloPlugin implements Plugin {
name = 'hello-plugin';
version = '1.0.0';
install(app: VeloceTS) {
// Agregar una ruta
app.get('/hello', {
handler: async (c) => {
return { message: '¡Hola desde el plugin!' };
},
});
console.log('HelloPlugin instalado');
}
}
// Usar el plugin
const app = new Veloce();
app.usePlugin(new HelloPlugin());
interface LoggerPluginConfig {
level: 'debug' | 'info' | 'warn' | 'error';
format: 'json' | 'text';
}
class LoggerPlugin implements Plugin {
name = 'logger-plugin';
version = '1.0.0';
constructor(private config: LoggerPluginConfig) {}
install(app: VeloceTS) {
// Agregar middleware de logging
app.use(async (c, next) => {
const start = Date.now();
await next();
const duration = Date.now() - start;
const log = {
method: c.req.method,
url: c.req.url,
status: c.res.status,
duration: `${duration}ms`,
};
if (this.config.format === 'json') {
console.log(JSON.stringify(log));
} else {
console.log(`${log.method} ${log.url} - ${log.status} (${log.duration})`);
}
});
}
}
// Usar con configuración
app.usePlugin(new LoggerPlugin({
level: 'info',
format: 'json',
}));
interface AuthPluginConfig {
secret: string;
expiresIn: string;
}
class AuthPlugin implements Plugin {
name = 'auth-plugin';
constructor(private config: AuthPluginConfig) {}
install(app: VeloceTS) {
// Agregar servicio de autenticación
class AuthService {
generateToken(userId: string) {
// Generar token JWT
return 'token';
}
verifyToken(token: string) {
// Verificar token JWT
return { userId: '1' };
}
}
app.getContainer().register(AuthService, { scope: 'singleton' });
// Agregar rutas de autenticación
app.post('/auth/login', {
handler: async (c) => {
const { email, password } = await c.req.json();
// Validar credenciales
const token = 'token-generado';
return { token };
},
});
}
}
interface RateLimitConfig {
windowMs: number;
max: number;
}
class RateLimitPlugin implements Plugin {
name = 'rate-limit-plugin';
private requests = new Map<string, number[]>();
constructor(private config: RateLimitConfig) {}
install(app: VeloceTS) {
app.use(async (c, next) => {
const ip = c.req.header('x-forwarded-for') || 'unknown';
const now = Date.now();
// Obtener timestamps de peticiones para esta IP
const timestamps = this.requests.get(ip) || [];
// Remover timestamps antiguos
const validTimestamps = timestamps.filter(
t => now - t < this.config.windowMs
);
// Verificar si se excedió el límite
if (validTimestamps.length >= this.config.max) {
return c.json({ error: 'Demasiadas peticiones' }, 429);
}
// Agregar timestamp actual
validTimestamps.push(now);
this.requests.set(ip, validTimestamps);
await next();
});
}
}
app.usePlugin(new RateLimitPlugin({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 100, // 100 peticiones por ventana
}));
// ✓ Bueno
class AuthenticationPlugin implements Plugin {
name = 'authentication-plugin';
}
// ✗ Malo
class MyPlugin implements Plugin {
name = 'plugin1';
}
class MyPlugin implements Plugin {
name = 'my-plugin';
version = '1.0.0';
}
/**
* Opciones de configuración para CachePlugin
*/
interface CachePluginConfig {
/** Tiempo de vida en segundos */
ttl: number;
/** Número máximo de elementos en caché */
maxSize: number;
/** Prefijo de clave de caché */
prefix?: string;
}
class CachePlugin implements Plugin {
constructor(private config: CachePluginConfig) {}
}
class MyPlugin implements Plugin {
name = 'my-plugin';
async install(app: VeloceTS) {
try {
await this.initialize();
} catch (error) {
console.error(`Falló la inicialización de ${this.name}:`, error);
throw error;
}
}
private async initialize() {
// Lógica de inicialización
}
}
// ✓ Bueno - Configurable
class LoggerPlugin implements Plugin {
constructor(private config: LoggerConfig) {}
}
// ✗ Malo - Hardcoded
class LoggerPlugin implements Plugin {
private level = 'info'; // Hardcoded
}