relic-middleware

star 124

Create and apply middleware for auth, CORS, logging, and other cross-cutting concerns. Use context properties for type-safe request-scoped data. Use when adding middleware, request/response transformations, or passing data between middleware and handlers.

serverpod By serverpod schedule Updated 2/26/2026

name: relic-middleware description: Create and apply middleware for auth, CORS, logging, and other cross-cutting concerns. Use context properties for type-safe request-scoped data. Use when adding middleware, request/response transformations, or passing data between middleware and handlers.

Relic Middleware

A Middleware wraps a handler with additional logic. It can inspect/modify requests, transform responses, short-circuit with an early response, or handle errors.

typedef Middleware = Handler Function(Handler innerHandler);

Basic pattern

Middleware myMiddleware() {
  return (Handler innerHandler) {
    return (Request req) async {
      // Before: inspect or modify the request

      final result = await innerHandler(req);

      // After: inspect or transform the response

      return result;
    };
  };
}

Applying middleware

Use router.use(path, middleware). Middleware only runs on requests that match a registered route -- unmatched requests (404s) go directly to the fallback.

final app = RelicApp()
  // Global -- applies to all matched routes
  ..use('/', logRequests())
  ..use('/', corsMiddleware())

  // Scoped -- only /api routes
  ..use('/api', authMiddleware())

  // Routes
  ..get('/', homeHandler)           // logRequests + cors
  ..get('/api/users', usersHandler) // logRequests + cors + auth

Execution order

Path hierarchy first, then registration order within the same path scope:

final app = RelicApp()
  ..use('/api', middlewareC)    // specific to /api
  ..use('/', middlewareA)       // all paths
  ..use('/', middlewareB)       // all paths
  ..get('/api/foo', fooHandler);

For GET /api/foo, execution order is: middlewareAmiddlewareBmiddlewareCfooHandler (and response flows back in reverse).

Example: Auth middleware

Middleware authMiddleware() {
  return (Handler next) {
    return (Request req) async {
      final apiKey = req.headers['X-API-Key']?.first;
      if (apiKey != 'secret123') {
        return Response.unauthorized(body: Body.fromString('Invalid API key'));
      }
      return await next(req);
    };
  };
}

final app = RelicApp()
  ..get('/public', publicHandler)
  ..use('/protected', authMiddleware())
  ..get('/protected', protectedHandler);

Example: CORS middleware

Middleware corsMiddleware() {
  return (Handler next) {
    return (Request req) async {
      if (req.method == Method.options) {
        return Response.ok(
          headers: Headers.build((mh) {
            mh['Access-Control-Allow-Origin'] = ['*'];
            mh['Access-Control-Allow-Methods'] = ['GET, POST, OPTIONS'];
            mh['Access-Control-Allow-Headers'] = ['Content-Type'];
          }),
        );
      }

      final result = await next(req);

      if (result is Response) {
        return result.copyWith(
          headers: result.headers.transform(
            (mh) => mh['Access-Control-Allow-Origin'] = ['*'],
          ),
        );
      }
      return result;
    };
  };
}

Example: Error handling

Middleware errorHandlingMiddleware() {
  return (Handler next) {
    return (Request req) async {
      try {
        return await next(req);
      } catch (error) {
        return Response.internalServerError(
          body: Body.fromString('Something went wrong'),
        );
      }
    };
  };
}

Example: Add response header

Middleware addHeaderMiddleware() {
  return (Handler next) {
    return (Request req) async {
      final result = await next(req);
      if (result is Response) {
        return result.copyWith(
          headers: result.headers.transform(
            (mh) => mh['X-Custom-Header'] = ['Hello from middleware!'],
          ),
        );
      }
      return result;
    };
  };
}

Context properties

ContextProperty<T> provides type-safe, request-scoped storage. Values exist only for the duration of the request and do not leak between requests.

final requestIdProperty = ContextProperty<String>('requestId');

// Set in middleware
requestIdProperty[req] = 'req_${DateTime.now().millisecondsSinceEpoch}';

// Read in handler
final id = requestIdProperty[req];    // String? -- null if not set
final id = requestIdProperty.get(req); // String -- throws if missing

Extension pattern for clean access

final _userProperty = ContextProperty<User>('user');
final _sessionProperty = ContextProperty<Session>('session');

extension AuthContext on Request {
  User get currentUser => _userProperty.get(this);
  Session get session => _sessionProperty.get(this);
}

// In middleware: set values
_userProperty[req] = authenticatedUser;

// In handler: read values
final user = req.currentUser;

Complete example

extension on Request {
  String get requestId => _requestIdProperty.get(this);
}

final _requestIdProperty = ContextProperty<String>('requestId');

Handler requestIdMiddleware(Handler next) {
  return (req) async {
    _requestIdProperty[req] = 'req_${DateTime.now().millisecondsSinceEpoch}';
    return await next(req);
  };
}

Future<Response> handler(Request req) async {
  return Response.ok(body: Body.fromString('Request ID: ${req.requestId}'));
}

final app = RelicApp()
  ..use('/', requestIdMiddleware)
  ..get('/', handler);

Built-in context properties

Relic sets these automatically during routing:

  • request.pathParameters -- path params from matched route
  • request.queryParameters -- typed query param accessors
  • request.router -- the RelicRouter that routed the request
  • request.matchedPath / request.remainingPath -- consumed and remaining path portions

Pipeline (legacy)

Pipeline is a legacy composition pattern from Shelf. Prefer router.use() for new code. Pipeline runs middleware on all requests including 404s, while router.use() only runs on matched routes.

Install via CLI
npx skills add https://github.com/serverpod/relic --skill relic-middleware
Repository Details
star Stars 124
call_split Forks 14
navigation Branch main
article Path SKILL.md
More from Creator