A powerful, plugin-based Express server framework with decorator-based routing, middleware presets, and lifecycle events.
- 🔌 Plugin Architecture - Extensible plugin system with lifecycle hooks
- 🎯 Decorator-based Routing - Define routes using
@Router,@Get,@Post, etc. - ⚙️ Middleware Engine - Category-based middleware with priority ordering and presets
- 📡 Event System - Lifecycle events for server operations
- ✅ Zod Validation - Type-safe request validation out of the box
- 🔒 Built-in Auth - Authentication middleware helpers
- 🚀 Auto Route Discovery - Automatically discovers and registers routes
npm install @anandamide/magik
# or
yarn add @anandamide/magik
# or
pnpm add @anandamide/magikimport { MagikServer } from '@anandamide/magik';
const server = await MagikServer.init({
name: 'my-api',
port: 3000,
});
console.log(`Server running on port ${server.port}`);Define routes using familiar decorators:
import { Router, Get, Post, Delete } from '@anandamide/magik/decorators';
import { createRoute } from '@anandamide/magik/factories';
import { z } from 'zod';
@Router('/users')
export default class UserRouter {
@Get('/')
public listUsers() {
return createRoute({
handler: async (req, res) => {
const users = await UserService.findAll();
res.json(users);
},
});
}
@Get('/:id')
public getUser() {
return createRoute({
auth: 'ensureAuthenticated',
handler: async (req, res) => {
const user = await UserService.findById(req.params.id);
res.json(user);
},
});
}
@Post('/')
public createUser() {
return createRoute({
auth: 'ensureAdmin',
schema: z.object({
email: z.string().email(),
name: z.string().min(2),
role: z.enum(['user', 'admin']).optional(),
}),
handler: async (req, res) => {
// req.body is fully typed from the schema
const user = await UserService.create(req.body);
res.status(201).json(user);
},
});
}
@Delete('/:id')
public deleteUser() {
return createRoute({
auth: 'ensureAdmin',
handler: async (req, res) => {
await UserService.delete(req.params.id);
res.status(204).send();
},
});
}
}Extend Magik with plugins:
import { MagikPlugin, IMagikServer } from '@anandamide/magik/types';
export class MyPlugin implements MagikPlugin {
config = {
name: 'my-plugin',
version: '1.0.0',
pluginDependencies: [], // Optional: other plugins this depends on
requiredMiddleware: [], // Optional: middleware that must be registered
};
// Called when plugin is installed
async onInstall(server: IMagikServer) {
console.log('Plugin installed!');
}
// Called before server starts
async beforeStart(server: IMagikServer) {
console.log('Server about to start...');
}
// Called after server starts
async afterStart(server: IMagikServer) {
console.log('Server started!');
}
// Called before server shuts down
async beforeShutdown(server: IMagikServer) {
console.log('Server shutting down...');
}
// Register custom middleware
registerMiddleware() {
return [
{
name: 'my-middleware',
category: 'custom',
priority: 50,
handler: (req, res, next) => {
req.customData = 'hello';
next();
},
},
];
}
// Register plugin routes
registerRoutes() {
return {
'/my-plugin': [
{
path: '/status',
method: 'get',
handler: (req, res) => res.json({ status: 'ok' }),
},
],
};
}
// React to server events
onEvent = {
routesLoaded: (server: IMagikServer) => {
console.log('All routes loaded!');
},
};
}
// Use the plugin
const server = await MagikServer.init({ name: 'my-api' });
await server.use(new MyPlugin());Magik organizes middleware into categories with priority ordering:
| Category | Priority Range | Purpose |
|---|---|---|
security |
90-100 | Helmet, CORS, rate limiting |
session |
70-89 | Session management, cookies |
parser |
80-89 | Body parsing, JSON, URL-encoded |
compression |
60-69 | Response compression |
logging |
50-59 | Request logging, Morgan |
static |
40-49 | Static file serving |
custom |
0-39 | Custom application middleware |
// Magik comes with these presets pre-configured:
// Security Preset
// - Helmet (security headers)
// - CORS
// Parser Preset
// - JSON parser (20mb limit)
// - URL-encoded parser
// - Cookie parser
// - Method override
// Session Preset (when using PassportAuthenticationPlugin)
// - Express session
// - Passport initializationserver.middlewareEngine.register({
name: 'request-timer',
category: 'logging',
priority: 55,
handler: (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
console.log(`${req.method} ${req.path} - ${Date.now() - start}ms`);
});
next();
},
});Built-in authentication middleware:
@Router('/admin')
export default class AdminRouter {
@Get('/dashboard')
public dashboard() {
return createRoute({
// Require any authenticated user
auth: 'ensureAuthenticated',
handler: (req, res) => {
res.render('dashboard', { user: req.user });
},
});
}
@Post('/settings')
public updateSettings() {
return createRoute({
// Require admin role
auth: 'ensureAdmin',
handler: (req, res) => {
// Only admins can access this
},
});
}
@Get('/system')
public systemStatus() {
return createRoute({
// Require IT role
auth: 'ensureIT',
handler: (req, res) => {
// Only IT staff can access this
},
});
}
@Post('/reports')
public generateReport() {
return createRoute({
// Require specific roles (array)
auth: ['manager', 'analyst'],
handler: (req, res) => {
// Only managers or analysts can access this
},
});
}
}| Type | Description |
|---|---|
ensureAuthenticated |
Any logged-in user |
ensureAdmin |
Admin role required |
ensureIT |
IT role required |
ensureIsEmployee |
Employee role required |
ensureAccessGranted |
Custom access check |
['role1', 'role2'] |
Any of the specified roles |
Type-safe validation with automatic TypeScript inference:
import { z } from 'zod';
@Router('/orders')
export default class OrderRouter {
@Post('/')
public createOrder() {
return createRoute({
auth: 'ensureAuthenticated',
schema: z.object({
items: z.array(z.object({
productId: z.string(),
quantity: z.number().int().positive(),
})).min(1),
shippingAddress: z.object({
street: z.string(),
city: z.string(),
state: z.string(),
zip: z.string().regex(/^\d{5}(-\d{4})?$/),
}),
notes: z.string().optional(),
}),
handler: async (req, res) => {
// req.body is fully typed:
// {
// items: Array<{ productId: string; quantity: number }>;
// shippingAddress: { street: string; city: string; state: string; zip: string };
// notes?: string;
// }
const order = await OrderService.create(req.body);
res.status(201).json(order);
},
});
}
}React to server lifecycle events:
// In a plugin
onEvent = {
beforeStart: async (server) => {
await initializeExternalConnections();
},
afterStart: async (server) => {
console.log(`Server listening on ${server.port}`);
},
beforeStop: async (server) => {
await closeExternalConnections();
},
afterStop: async (server) => {
console.log('Server stopped');
},
routesLoaded: (server) => {
const { total } = server.routerManager.getRouteCount();
console.log(`Loaded ${total} routes`);
},
error: (server, error) => {
console.error('Server error:', error);
},
};
// Or directly on the event engine
server.eventEngine.on('routesLoaded', () => {
console.log('Routes are ready!');
});| Event | Description |
|---|---|
beforeStart |
Before server starts listening |
afterStart |
After server is listening |
beforeStop |
Before graceful shutdown |
afterStop |
After server has stopped |
routesLoaded |
All routes registered |
error |
Unhandled error occurred |
request |
Incoming request |
response |
Outgoing response |
serverError |
Server-level error |
serverListening |
Server bound to port |
serverPortError |
Port permission error |
serverPortInUse |
Port already in use |
Handle file uploads with Multer integration:
@Router('/uploads')
export default class UploadRouter {
@Post('/avatar')
public uploadAvatar() {
return createRoute({
auth: 'ensureAuthenticated',
upload: {
field: 'avatar',
multer: 'imageUpload', // Configured multer instance
multi: false,
},
handler: async (req, res) => {
const file = req.file;
const url = await StorageService.upload(file);
res.json({ url });
},
});
}
@Post('/documents')
public uploadDocuments() {
return createRoute({
auth: 'ensureAuthenticated',
upload: {
field: 'documents',
multer: 'documentUpload',
multi: true, // Multiple files
},
handler: async (req, res) => {
const files = req.files;
const urls = await Promise.all(
files.map(f => StorageService.upload(f))
);
res.json({ urls });
},
});
}
}Magik includes several built-in plugins:
Global error handling with pretty error pages in development:
import { ErrorHandlingPlugin } from '@anandamide/magik/plugins';
await server.use(new ErrorHandlingPlugin());Handles SIGTERM/SIGINT for graceful shutdown:
import { GracefulShutdownPlugin } from '@anandamide/magik/plugins';
await server.use(new GracefulShutdownPlugin());Rate limiting for API endpoints:
import { RateLimiterPlugin } from '@anandamide/magik/plugins';
await server.use(new RateLimiterPlugin({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
}));Debug logging and route inspection:
import { DebugPlugin } from '@anandamide/magik/plugins';
await server.use(new DebugPlugin());interface MagikServerConfig {
name: string; // Server name (required)
port?: number; // Port (default: process.env.PORT || 5000)
debug?: boolean; // Enable debug logging
mode?: 'development' | 'production';
}
const server = await MagikServer.init({
name: 'my-api',
port: 3000,
debug: true,
mode: 'development',
});class MagikServer {
// Static initializer
static async init(config: MagikServerConfig): Promise<MagikServer>;
// Properties
name: string;
app: Express;
server: http.Server;
port: number;
status: 'ONLINE' | 'OFFLINE' | 'SHUTTING DOWN';
DEBUG: boolean;
DevMode: boolean;
// Engines
routerManager: RouterManager;
middlewareEngine: MiddlewareEngine;
eventEngine: EventEngine;
// Methods
use(plugin: MagikPlugin): Promise<this>;
listen(): Promise<void>;
close(): Promise<void>;
}class RouterManager {
register(prefix: PathSegment): RouteEngine;
getRoute(prefix: PathSegment): RouteEngine | undefined;
getRouteCount(): { total: number; byPrefix: Record<string, number> };
getRouteCountByMethod(): Record<'get' | 'post' | 'put' | 'delete', number>;
installRoutes(): void;
}class MiddlewareEngine {
register(config: MiddlewareConfig): this;
registerBulk(configs: MiddlewareConfig[]): this;
hasMiddleware(name: string): boolean;
applyCategory(category: MiddlewareCategory): this;
getAuthMiddleware(auth: AuthTypes): RequestHandler;
}class EventEngine {
on(event: ServerEvent, handler: Function): this;
off(event: ServerEvent, handler: Function): this;
emit(event: ServerEvent, ...args: any[]): boolean;
emitAsync(event: ServerEvent, ...args: any[]): Promise<void>;
clearHandlers(event?: ServerEvent): this;
}src/
├── routes/ # Route files (auto-discovered)
│ ├── userRoute.ts
│ ├── orderRoute.ts
│ └── admin/
│ └── adminRoute.ts
├── controllers/ # Business logic
├── middleware/ # Custom middleware
├── plugins/ # Custom plugins
└── index.ts # Entry point
Magik is written in TypeScript and provides full type definitions:
import type {
MagikPlugin,
MagikPluginConfig,
IMagikServer,
RouteDefinition,
MiddlewareConfig,
MiddlewareCategory,
AuthTypes,
ServerEvent,
PathSegment,
MagikRequest,
MagikGetRequest,
} from '@anandamide/magik/types';Contributions are welcome! Please read our Contributing Guide for details.
MIT © MagikIO
Made with 🪄 by the MagikIO team