Skip to content

Workers Routing -- URL Patterns and Middleware

DodaTech 5 min read

In this tutorial, you'll learn about Workers Routing. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Cloudflare Workers routing enables you to direct incoming requests to different handlers based on URL paths, HTTP methods, headers, or any request property, and to compose middleware pipelines for cross-cutting concerns like authentication and logging.

Why Routing Matters in Workers

Without routing, every request goes to a single monolithic handler. As your Worker grows, you need to organize code into separate endpoints, apply middleware for logging and auth, and route requests efficiently. Routing logic running at the edge means requests are dispatched to the right handler in under a millisecond, with no round trips to an origin router. Combined with Cloudflare Workers, you can build entire APIs that never touch a traditional server. API routing at the edge is the foundation of Serverless application architecture.

Real-world use: A blog platform uses a single Worker to route requests to different handlers: /api/posts returns JSON, /blog/* serves prerendered HTML from KV, and /admin/* requires a valid JWT token checked in middleware. The entire application runs at the edge with no origin server.

Router Architecture

flowchart TD
    R[Incoming Request] --> P[Path Router]
    P -->|/api/*| A[API Handler]
    P -->|/blog/*| B[Blog Handler]
    P -->|/admin/*| C[Admin Handler]
    P -->|/static/*| D[Static Assets]
    A --> M1[Logging Middleware]
    B --> M1
    C --> M1
    D --> M1
    M1 --> RESP[Response]
    style P fill:#f90,color:#fff
    style RESP fill:#f90,color:#fff

Manual Path Routing

Route requests using the URL object and switch statements.

export default {
  async fetch(request) {
    const url = new URL(request.url);
    const { pathname } = url;

    if (pathname === '/api/users' && request.method === 'GET') {
      return handleGetUsers();
    }
    if (pathname.startsWith('/api/users/') && request.method === 'GET') {
      const id = pathname.split('/')[3];
      return handleGetUser(id);
    }
    if (pathname === '/api/users' && request.method === 'POST') {
      return handleCreateUser(request);
    }
    return new Response('Not Found', { status: 404 });
  }
}

async function handleGetUsers() {
  const users = await getUsersFromKV();
  return new Response(JSON.stringify(users), {
    headers: { 'Content-Type': 'application/json' }
  });
}

async function handleGetUser(id) {
  const user = await getUserFromKV(id);
  if (!user) return new Response('User not found', { status: 404 });
  return new Response(JSON.stringify(user), {
    headers: { 'Content-Type': 'application/json' }
  });
}

Expected output: GET /api/users returns all users as JSON. GET /api/users/42 returns the user with ID 42 or a 404. POST /api/users creates a new user. Any other path returns a 404.

Middleware Pattern

Implement middleware for cross-cutting concerns like authentication and logging.

async function authMiddleware(request, handler) {
  const authHeader = request.headers.get('Authorization');
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return new Response('Unauthorized', { status: 401 });
  }
  const token = authHeader.slice(7);
  const payload = await verifyToken(token);
  if (!payload) {
    return new Response('Invalid token', { status: 403 });
  }
  request.user = payload;
  return handler(request);
}

async function loggingMiddleware(request, handler) {
  console.log(`${request.method} ${request.url} at ${new Date().toISOString()}`);
  const start = Date.now();
  const response = await handler(request);
  const duration = Date.now() - start;
  console.log(`Response in ${duration}ms`);
  return response;
}

export default {
  async fetch(request) {
    const chain = (req) => loggingMiddleware(req, (r) => authMiddleware(r, handleRequest));
    return chain(request);
  }
}

async function handleRequest(request) {
  return new Response(`Hello, user ${request.user?.id || 'unknown'}`, {
    headers: { 'Content-Type': 'text/plain' }
  });
}

Expected output: A request without an Authorization header returns 401. A request with an invalid token returns 403. A request with a valid token returns Hello, user 42. The console logs show the request method, URL, and response duration.

Using Itty Router for Clean Routing

For complex APIs, use a lightweight router library like Itty Router.

import { Router } from 'itty-router';

const router = Router();

router.get('/api/posts', () => {
  return new Response(JSON.stringify({ posts: ['post1', 'post2'] }), {
    headers: { 'Content-Type': 'application/json' }
  });
});

router.get('/api/posts/:id', ({ params }) => {
  return new Response(JSON.stringify({ id: params.id, title: 'Sample Post' }), {
    headers: { 'Content-Type': 'application/json' }
  });
});

router.all('*', () => new Response('Not Found', { status: 404 }));

export default {
  async fetch(request, env, ctx) {
    return router.handle(request, env, ctx);
  }
}

Expected output: GET /api/posts returns {"posts":["post1","post2"]}. GET /api/posts/99 returns {"id":"99","title":"Sample Post"}. Any other path returns a 404.

Common Errors

Error Cause Fix
Handler not returning Response Missing return in route handler Ensure every async handler returns a Response object
URL pattern too broad Catch-all * route placed before specific routes Order routes from most specific to least specific
Middleware not awaiting Calling middleware without await Always await middleware functions that return promises
Path parameter extraction Wrong array index when splitting path Use a router library or named capture groups for clarity
Infinite redirect loop Worker redirecting to itself Ensure redirect URLs point to different hosts or paths

Practice Questions

  1. Why should specific routes be defined before catch-all routes in a router?
  2. How does middleware composition differ from route handlers in Workers?
  3. What is the benefit of using a router library like Itty Router over manual URL Parsing?

FAQ

Can I use Express.js middleware patterns in Workers?

Express middleware patterns work similarly in Workers with manual composition. Libraries like Itty Router and Hono provide Express-like middleware APIs that are compatible with the Workers runtime.

Does Workers support regex in route patterns?

Workers itself does not have built-in routing, but router libraries like Itty Router and Hono support regex patterns in route definitions for advanced URL matching.

Summary

Routing in Workers lets you direct requests to specific handlers based on URL patterns and HTTP methods. Middleware functions wrap handlers for cross-cutting concerns like authentication and logging. For complex APIs, router libraries like Itty Router provide clean, Express-like routing. This pattern powers the API Gateway layer in Doda Browser's sync infrastructure, routing requests to the appropriate microservice at the edge. Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro -- security-first tools for the modern web.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro