Skip to content

Plugins Guide

Learn how to use and create plugins for Veloce.

Veloce’s plugin system allows you to extend the framework with additional functionality. Plugins can add routes, middleware, modify the application, and integrate with external services.

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

Official plugins ship as classes and register with app.usePlugin(new PluginClass(...)) (there is no string-based app.install('name') API).

JWT-based authentication with refresh tokens and user management.

import { VeloceTS, AuthPlugin } from 'veloce-ts';
const app = new VeloceTS();
app.usePlugin(new AuthPlugin({
jwt: {
secret: 'your-secret-key',
expiresIn: '1h',
refreshExpiresIn: '7d',
},
userProvider: {
findByCredentials: async (username: string, password: string) => {
// Your user lookup logic
return null;
},
findById: async (id: string) => {
// Your user lookup logic
return null;
},
hashPassword: async (password: string) => {
return password; // replace with real hash
},
verifyPassword: async (password: string, hash: string) => {
return password === hash; // replace with real verify
},
},
}));

Role-Based Access Control with hierarchical roles and permissions (Role includes permissions: string[] per role).

import { VeloceTS, RBACPlugin } from 'veloce-ts';
const app = new VeloceTS();
app.usePlugin(new RBACPlugin({
roles: [
{
name: 'admin',
level: 100,
permissions: ['users.create', 'users.read', 'users.update', 'users.delete'],
},
{
name: 'viewer',
level: 20,
permissions: ['users.read'],
},
],
}));

Schema generation and Playground—see also the GraphQL guide (experimental areas).

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

Real-time WebSocket routes from @WebSocket classes. Not supported on Node.js upgrades (501)—use Bun or Deno; see WebSockets and Limitations & roadmap.

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

OpenAPI 3 spec + Swagger UI from route metadata (merges title / version / description from VeloceTS config when set).

import { VeloceTS, OpenAPIPlugin } from 'veloce-ts';
const app = new VeloceTS({
title: 'My API',
version: '1.0.0',
description: 'API description',
});
app.usePlugin(new OpenAPIPlugin({
path: '/openapi.json',
docsPath: '/docs',
docs: true,
}));

Once you’ve installed the auth and RBAC plugins, you can use the decorators:

import {
Controller,
Get,
Post,
Auth,
CurrentUser,
MinimumRole,
Permissions
} from 'veloce-ts';
@Controller('/api/users')
export class UserController {
// Require authentication
@Get('/profile')
@Auth()
async getProfile(@CurrentUser() user: any) {
return user;
}
// Require specific role level
@Get('/admin-only')
@MinimumRole('admin')
async adminOnly() {
return { message: 'Admin access granted' };
}
// Require specific permissions
@Post('/')
@Permissions('users.create')
async createUser(@Body() userData: any) {
return { message: 'User created', user: userData };
}
// Multiple permissions (user needs ALL)
@Put('/:id')
@Permissions('users.update', 'users.read')
async updateUser(@Param('id') id: string, @Body() userData: any) {
return { message: 'User updated', id, user: userData };
}
}

Available Decorators:

  • @Auth() - Requires valid JWT token
  • @CurrentUser() - Injects current authenticated user
  • @MinimumRole(roleName) - Requires minimum role level
  • @Permissions(...perms) - Requires specific permissions

Registers /health, /ready, and /live. Custom checks are passed as an array via 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),
],
}));

Sample response (/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 }
}
}

HealthCheckers helpers:

  • HealthCheckers.alwaysHealthy() — returns a static healthy result (testing)
  • HealthCheckers.database(pingFn)pingFn returns boolean or Promise<boolean>; unhealthy if false or throws
  • HealthCheckers.memory(maxUsageMB?) — heap vs limit (default 512 MB)
  • HealthCheckers.disk(path?, maxUsagePercent?) — uses fs.statfs where available (Node 18+ / Bun)
import { VeloceTS, OpenAPIPlugin, GraphQLPlugin } from 'veloce-ts';
const app = new VeloceTS();
app.usePlugin(new OpenAPIPlugin());
app.usePlugin(new OpenAPIPlugin());
app.usePlugin(new GraphQLPlugin());

VeloceTS does not take a plugins: [] option—register each plugin with usePlugin:

import { VeloceTS, OpenAPIPlugin, GraphQLPlugin } from 'veloce-ts';
const app = new VeloceTS();
app.usePlugin(new OpenAPIPlugin({
path: '/api-docs.json',
docsPath: '/api-docs',
}));
app.usePlugin(new GraphQLPlugin({
path: '/gql',
playground: process.env.NODE_ENV !== 'production',
}));
import { Plugin, VeloceTS } from 'veloce-ts';
class HelloPlugin implements Plugin {
name = 'hello-plugin';
version = '1.0.0';
install(app: VeloceTS) {
// Add a route
app.get('/hello', {
handler: async (c) => {
return { message: 'Hello from plugin!' };
},
});
console.log('HelloPlugin installed');
}
}
// Use the 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) {
// Add logging middleware
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})`);
}
});
}
}
// Use with configuration
app.usePlugin(new LoggerPlugin({
level: 'info',
format: 'json',
}));
class DatabasePlugin implements Plugin {
name = 'database-plugin';
async install(app: VeloceTS) {
// Async initialization
const db = await this.connectDatabase();
// Register as singleton
app.getContainer().register(DatabaseService, {
scope: 'singleton',
factory: () => db,
});
console.log('Database connected');
}
private async connectDatabase() {
// Connect to database
return new DatabaseService();
}
}
class AnalyticsPlugin implements Plugin {
name = 'analytics-plugin';
version = '1.0.0';
dependencies = ['logger-plugin'];
install(app: VeloceTS) {
// This plugin requires logger-plugin to be installed first
app.use(async (c, next) => {
// Track request
await next();
// Send analytics
});
}
}
interface AuthPluginConfig {
secret: string;
expiresIn: string;
}
class AuthPlugin implements Plugin {
name = 'auth-plugin';
constructor(private config: AuthPluginConfig) {}
install(app: VeloceTS) {
// Add auth service
class AuthService {
generateToken(userId: string) {
// Generate JWT token
return 'token';
}
verifyToken(token: string) {
// Verify JWT token
return { userId: '1' };
}
}
app.getContainer().register(AuthService, { scope: 'singleton' });
// Add auth routes
app.post('/auth/login', {
handler: async (c) => {
const { email, password } = await c.req.json();
// Validate credentials
const token = 'generated-token';
return { token };
},
});
app.post('/auth/logout', {
handler: async (c) => {
return { success: true };
},
});
}
}
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();
// Get request timestamps for this IP
const timestamps = this.requests.get(ip) || [];
// Remove old timestamps
const validTimestamps = timestamps.filter(
t => now - t < this.config.windowMs
);
// Check if limit exceeded
if (validTimestamps.length >= this.config.max) {
return c.json({ error: 'Too many requests' }, 429);
}
// Add current timestamp
validTimestamps.push(now);
this.requests.set(ip, validTimestamps);
await next();
});
}
}
app.usePlugin(new RateLimitPlugin({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
}));
interface CachePluginConfig {
ttl: number; // Time to live in seconds
maxSize: number; // Maximum cache size
}
class CachePlugin implements Plugin {
name = 'cache-plugin';
private cache = new Map<string, { data: any; expires: number }>();
constructor(private config: CachePluginConfig) {}
install(app: VeloceTS) {
class CacheService {
constructor(private cache: Map<string, any>) {}
get(key: string) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expires) {
this.cache.delete(key);
return null;
}
return item.data;
}
set(key: string, data: any, ttl?: number) {
const expires = Date.now() + (ttl || this.config.ttl) * 1000;
this.cache.set(key, { data, expires });
}
delete(key: string) {
this.cache.delete(key);
}
clear() {
this.cache.clear();
}
}
app.getContainer().register(CacheService, {
scope: 'singleton',
factory: () => new CacheService(this.cache),
});
}
}
class MonitoringPlugin implements Plugin {
name = 'monitoring-plugin';
private metrics = {
requests: 0,
errors: 0,
totalDuration: 0,
};
install(app: VeloceTS) {
// Track metrics
app.use(async (c, next) => {
this.metrics.requests++;
const start = Date.now();
try {
await next();
} catch (error) {
this.metrics.errors++;
throw error;
} finally {
this.metrics.totalDuration += Date.now() - start;
}
});
// Expose metrics endpoint
app.get('/metrics', {
handler: async (c) => {
return {
...this.metrics,
averageDuration: this.metrics.totalDuration / this.metrics.requests,
errorRate: this.metrics.errors / this.metrics.requests,
};
},
});
}
}
// ✓ Good
class AuthenticationPlugin implements Plugin {
name = 'authentication-plugin';
}
// ✗ Bad
class MyPlugin implements Plugin {
name = 'plugin1';
}
class MyPlugin implements Plugin {
name = 'my-plugin';
version = '1.0.0';
}
/**
* Configuration options for CachePlugin
*/
interface CachePluginConfig {
/** Time to live in seconds */
ttl: number;
/** Maximum number of items in cache */
maxSize: number;
/** Cache key prefix */
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(`Failed to initialize ${this.name}:`, error);
throw error;
}
}
private async initialize() {
// Initialization logic
}
}
class DatabasePlugin implements Plugin {
name = 'database-plugin';
private connection: any;
async install(app: VeloceTS) {
this.connection = await this.connect();
// Register cleanup handler
process.on('SIGTERM', () => this.cleanup());
process.on('SIGINT', () => this.cleanup());
}
private async cleanup() {
if (this.connection) {
await this.connection.close();
}
}
}
// ✓ Good - Configurable
class LoggerPlugin implements Plugin {
constructor(private config: LoggerConfig) {}
}
// ✗ Bad - Hardcoded
class LoggerPlugin implements Plugin {
private level = 'info'; // Hardcoded
}
import { describe, it, expect } from 'bun:test';
import { createTestApp } from 'veloce/testing';
describe('MyPlugin', () => {
it('should add route', async () => {
const app = createTestApp();
app.usePlugin(new MyPlugin());
const response = await app.request('/plugin-route');
expect(response.status).toBe(200);
});
});
{
"name": "veloce-plugin-myfeature",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"peerDependencies": {
"veloce-ts": "^0.1.0"
}
}
Terminal window
npm install veloce-plugin-myfeature
import { Veloce } from 'veloce-ts';
import { MyFeaturePlugin } from 'veloce-plugin-myfeature';
const app = new Veloce();
app.usePlugin(new MyFeaturePlugin());