Core Concepts
Middleware System
Middleware that orders itself. Type-safe context passing and automatic optimization. You write the middleware, Moro works out the order.
Overview
Write your middleware. We order it optimally. Authentication before authorization. Validation early. Caching smart.
Automatic Ordering
- 1Request parsing
- 2Authentication
- 3Authorization
- 4Validation
- 5Rate limiting
- 6Caching
- 7Handler execution
Benefits
- Automatic optimization
- Type-safe context
- Early exit on errors
- Composable design
- Performance optimized
Traditional frameworks make you manually order middleware. We do that automatically based on dependencies and performance. Without intelligent ordering, you're manually managing execution order, risking bugs, and missing optimization opportunities.
Traditional Frameworks
- Manual middleware ordering
- Easy to get order wrong
- No automatic optimization
- Type safety requires extra work
With MoroJS
- Automatic middleware ordering
- Optimal execution path
- Type-safe context passing
- Performance optimized automatically
Add middleware globally or to specific routes. Ordering is automatic.
1// Global middleware for logging, CORS, etc.
2app.use(async (req, res, next) => {
3 const start = Date.now();
4 console.log(`${req.method} ${req.url}`);
5
6 await next();
7
8 const duration = Date.now() - start;
9 console.log(`Request completed in ${duration}ms`);
10});
11
12// CORS middleware
13app.use(async (req, res, next) => {
14 res.headers.set('Access-Control-Allow-Origin', '*');
15 await next();
16});1app.post('/admin/users')
2 .use(authMiddleware)
3 .use(adminOnlyMiddleware)
4 .rateLimit({ requests: 10, window: 60000 })
5 .body(CreateUserSchema)
6 .handler((req) => {
7 // All middleware has run in optimal order
8 // req.context.user is available and typed
9 return { success: true, user: req.body };
10 });Automatic Ordering
Middleware runs in the right order automatically. No manual configuration needed.
Type Safe
Context is fully typed. TypeScript knows what's available in your handlers.
Optimized
Execution paths are optimized automatically. Maximum performance.
How It Works
MoroJS analyzes your middleware at startup and creates an optimized execution plan. Middleware is automatically ordered based on dependencies, type-safe context is passed between middleware, and execution paths are optimized for maximum performance.
Global Middleware
Global middleware runs on every request and is perfect for logging, CORS, and other cross-cutting concerns.
1import { createApp } from '@morojs/moro';
2
3const app = await createApp();
4
5// Logging middleware
6app.use(async (req, res, next) => {
7 const start = Date.now();
8 console.log(`${req.method} ${req.url}`);
9
10 await next();
11
12 const duration = Date.now() - start;
13 console.log(`Request completed in ${duration}ms`);
14});
15
16// CORS middleware
17app.use(async (req, res, next) => {
18 res.headers.set('Access-Control-Allow-Origin', '*');
19 res.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
20 res.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
21
22 await next();
23});
24
25// Error handling middleware
26app.use(async (err, req, res, next) => {
27 res.status = 500;
28 res.body = {
29 error: 'INTERNAL_SERVER_ERROR',
30 message: 'Something went wrong',
31 details: err.message
32 };
33});Route-Specific Middleware
1const authMiddleware = async (req, res, next) => {
2 const token = req.headers.authorization?.replace('Bearer ', '');
3 if (!token) {
4 res.status = 401;
5 res.body = { error: 'Authentication required' };
6 return;
7 }
8
9 const user = await verifyJWT(token);
10 req.user = user; // Attach user to request
11 await next();
12};
13
14// Chainable API style
15app.get('/profile')
16 .use(authMiddleware)
17 .handler((req, res) => {
18 // req.user is now available and typed
19 return { profile: req.user };
20 });1app.post('/admin/users')
2 .use(authMiddleware)
3 .use(async (req, res, next) => {
4 if (req.user.role !== 'admin') {
5 res.status = 403;
6 res.body = { error: 'Admin access required' };
7 return;
8 }
9 await next();
10 })
11 .rateLimit({ requests: 10, window: 60000 })
12 .use(async (req, res, next) => {
13 await logAdminAction(req.user.id, 'CREATE_USER', req.url);
14 await next();
15 })
16 .body(CreateUserSchema)
17 .handler((req, res) => {
18 // All middleware has run, req.user is populated
19 return createUser(req.body, req.user);
20 });1app.post('/api/data')
2 .rateLimit({
3 requests: 100,
4 window: 3600000 // 1 hour, in MILLISECONDS
5 })
6 .cache({
7 ttl: 300, // 5 minutes, in SECONDS (units differ from rateLimit.window)
8 key: 'user-data',
9 tags: ['user-data']
10 })
11 .handler((req, res) => {
12 return { data: processData(req.body) };
13 });Body Parsers: json() & urlencoded()
MoroJS auto-parses JSON bodies upstream, so json() is a pass-through that exists purely for Express-style app.use(json()) compatibility. urlencoded() decodes application/x-www-form-urlencoded bodies into req.body.
1import { json, urlencoded } from '@morojs/moro';
2
3app.use(json()); // compatibility no-op; JSON auto-parsed
4app.use(urlencoded({ extended: true })); // form-encoded bodiesWhen you actually need these
- • Migrating Express code that calls
app.use(json()): works verbatim - • Accepting HTML form submissions (
urlencoded) - • For pure JSON APIs, you can skip
json()entirely, because MoroJS parses bodies automatically
1json({ limit?: string | number; strict?: boolean });
2urlencoded({ limit?: string | number; extended?: boolean });Creating Custom Middleware
1// Middleware function signature
2type MiddlewareFunction = (
3 req: HttpRequest,
4 res: HttpResponse,
5 next: () => Promise<void>
6) => Promise<void>;
7
8// Example: Request timing middleware
9const timingMiddleware: MiddlewareFunction = async (req, res, next) => {
10 const start = performance.now();
11
12 // Add timing data to request
13 req.timing = { start };
14
15 await next();
16
17 const end = performance.now();
18 req.timing.duration = end - start;
19
20 // Add timing header to response
21 res.headers.set('X-Response-Time', `${req.timing.duration}ms`);
22};
23
24// Usage - Chainable API
25app.get('/timed-endpoint')
26 .use(timingMiddleware)
27 .handler((req, res) => {
28 return {
29 message: 'Success',
30 processingTime: req.timing.duration
31 };
32 });1// Middleware that runs conditionally
2const conditionalAuth = async (req, res, next) => {
3 const isPublicEndpoint = req.url.includes('/public/');
4
5 if (!isPublicEndpoint) {
6 const token = req.headers.authorization;
7 if (!token) {
8 res.status = 401;
9 res.body = { error: 'Authentication required' };
10 return;
11 }
12 req.user = await verifyToken(token);
13 }
14
15 await next();
16};
17
18// Middleware factory
19const createRoleMiddleware = (requiredRole: string) => {
20 return async (req, res, next) => {
21 if (!req.user || req.user.role !== requiredRole) {
22 res.status = 403;
23 res.body = { error: `${requiredRole} role required` };
24 return;
25 }
26 await next();
27 };
28};
29
30// Chainable API
31app.get('/admin/stats')
32 .use(conditionalAuth)
33 .use(createRoleMiddleware('admin'))
34 .handler((req, res) => getAdminStats());