mdcms/sample-sites/velox-docs/pages/middleware.md
2026-05-18 14:30:49 +07:00

6.8 KiB

title sort section-id keywords description language
Middleware 140 core-concepts middleware, request pipeline, auth middleware, rate limiting, CORS, headers How to use and write Velox middleware for request/response transformation en

Middleware

Middleware in Velox is a function that intercepts HTTP requests before they reach a route handler and can transform both the request and the response. Middleware is composable, typed, and supports async operations.

How Middleware Works

The Velox request pipeline processes a request in the following order:

  1. Global middleware — applied to every request
  2. Path-scoped middleware — applied based on route matching
  3. Route handler — the actual page or API route
  4. Response middleware — runs on the way out (in reverse order)
Request
  ↓
[Global Middleware 1]
  ↓
[Global Middleware 2]
  ↓
[Path Middleware]
  ↓
[Route Handler]
  ↓
[Response (in reverse)]

Writing Middleware

Create a middleware file in the middleware/ directory. A middleware function receives the request and a next function. Call next() to pass control to the next middleware or the route handler:

// middleware/logger.ts
import { defineMiddleware } from 'velox/server';

export default defineMiddleware(async ({ request, next }) => {
  const start = Date.now();
  const response = await next();
  const duration = Date.now() - start;
  console.log(`${request.method} ${request.url}${response.status} (${duration}ms)`);
  return response;
});

Configuring Middleware

Register middleware in velox.config.ts:

import { defineConfig } from 'velox';

export default defineConfig({
  middleware: [
    { path: '*', handler: './middleware/logger' },
    { path: '/admin/*', handler: './middleware/auth' },
    { path: '/api/*', handler: './middleware/rateLimit' },
  ],
});

Or, use a middleware.ts file at the project root for global middleware:

// middleware.ts (project root — applies to all routes automatically)
import { defineMiddleware } from 'velox/server';

export default defineMiddleware(async ({ request, next }) => {
  // runs on every request
  return next();
});

Authentication Middleware

// middleware/auth.ts
import { defineMiddleware, redirect } from 'velox/server';
import { verifyJWT } from '$lib/auth';

export default defineMiddleware(async ({ request, next }) => {
  const token = request.cookies.get('session')?.value
    ?? request.headers.get('Authorization')?.replace('Bearer ', '');

  if (!token) {
    return redirect('/login?next=' + encodeURIComponent(request.url));
  }

  let user;
  try {
    user = await verifyJWT(token);
  } catch {
    return redirect('/login?error=invalid_token');
  }

  // Attach user to request context for downstream handlers
  request.context.set('user', user);

  return next();
});

Access the context in your route:

// routes/dashboard.velox server block
const user = request.context.get('user');

Rate Limiting Middleware

// middleware/rateLimit.ts
import { defineMiddleware } from 'velox/server';

const counters = new Map<string, { count: number; resetAt: number }>();
const WINDOW_MS = 60_000; // 1 minute
const MAX_REQUESTS = 100;

export default defineMiddleware(async ({ request, next }) => {
  const ip = request.headers.get('CF-Connecting-IP')
    ?? request.headers.get('X-Forwarded-For')
    ?? 'unknown';

  const now = Date.now();
  const counter = counters.get(ip) ?? { count: 0, resetAt: now + WINDOW_MS };

  if (now > counter.resetAt) {
    counter.count = 0;
    counter.resetAt = now + WINDOW_MS;
  }

  counter.count++;
  counters.set(ip, counter);

  if (counter.count > MAX_REQUESTS) {
    return new Response('Too Many Requests', {
      status: 429,
      headers: {
        'Retry-After': String(Math.ceil((counter.resetAt - now) / 1000)),
        'X-RateLimit-Limit': String(MAX_REQUESTS),
        'X-RateLimit-Remaining': '0',
      },
    });
  }

  const response = await next();
  response.headers.set('X-RateLimit-Limit', String(MAX_REQUESTS));
  response.headers.set('X-RateLimit-Remaining', String(MAX_REQUESTS - counter.count));
  return response;
});

CORS Middleware

// middleware/cors.ts
import { defineMiddleware } from 'velox/server';

const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS?.split(',') ?? ['*'];

export default defineMiddleware(async ({ request, next }) => {
  const origin = request.headers.get('Origin') ?? '';
  const isAllowed = ALLOWED_ORIGINS.includes('*') || ALLOWED_ORIGINS.includes(origin);

  if (request.method === 'OPTIONS') {
    // Preflight response
    return new Response(null, {
      status: 204,
      headers: corsHeaders(origin, isAllowed),
    });
  }

  const response = await next();

  if (isAllowed) {
    for (const [key, value] of Object.entries(corsHeaders(origin, true))) {
      response.headers.set(key, value);
    }
  }

  return response;
});

function corsHeaders(origin: string, allowed: boolean) {
  if (!allowed) return {};
  return {
    'Access-Control-Allow-Origin': origin,
    'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    'Access-Control-Allow-Credentials': 'true',
    'Access-Control-Max-Age': '86400',
  };
}

Security Headers Middleware

// middleware/securityHeaders.ts
import { defineMiddleware } from 'velox/server';

export default defineMiddleware(async ({ next }) => {
  const response = await next();

  response.headers.set('X-Content-Type-Options', 'nosniff');
  response.headers.set('X-Frame-Options', 'DENY');
  response.headers.set('X-XSS-Protection', '1; mode=block');
  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
  response.headers.set(
    'Permissions-Policy',
    'camera=(), microphone=(), geolocation=()'
  );
  response.headers.set(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self' 'nonce-{nonce}'; style-src 'self' 'unsafe-inline'"
  );

  return response;
});

Composing Middleware

Combine multiple middleware into a single handler:

import { composeMiddleware } from 'velox/server';
import logger from './logger';
import auth from './auth';
import rateLimit from './rateLimit';

export default composeMiddleware(logger, rateLimit, auth);

Middleware Execution Order

In velox.config.ts, middleware is applied in the order it is declared for the request path, and in reverse order for the response path. This mirrors the middleware stack pattern found in Express, Koa, and similar frameworks.

Middleware On request On response
logger 1st 4th (last)
rateLimit 2nd 3rd
auth 3rd 2nd
route 4th 1st