Workers Routing -- URL Patterns and Middleware
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
- Why should specific routes be defined before catch-all routes in a router?
- How does middleware composition differ from route handlers in Workers?
- What is the benefit of using a router library like Itty Router over manual URL Parsing?
FAQ
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