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

  1. 1Request parsing
  2. 2Authentication
  3. 3Authorization
  4. 4Validation
  5. 5Rate limiting
  6. 6Caching
  7. 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.

Global Middleware
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});
Route-Specific Middleware
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.

Global Middleware Example
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

Authentication 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  });
Multiple Middleware - Chainable API
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  });
Built-in Middleware Options
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.

Importing and mounting
1import { json, urlencoded } from '@morojs/moro';
2
3app.use(json());                         // compatibility no-op; JSON auto-parsed
4app.use(urlencoded({ extended: true })); // form-encoded bodies

When 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
Options
1json({ limit?: string | number; strict?: boolean });
2urlencoded({ limit?: string | number; extended?: boolean });

Creating Custom Middleware

Custom Middleware Pattern
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  });
Conditional Middleware & Middleware Factories
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());

Next Steps