Introduction to Server Routing
Think of server routing like a sophisticated postal system in a large office building. Just as mail needs to be sorted and delivered to the correct department and person, HTTP requests need to be directed to the appropriate handlers in your application. The routing system is like the building's directory and the mail room staff combined - it knows where everything should go and makes sure it gets there.
Let's look at a basic routing example:
const http = require('http');
const server = http.createServer((req, res) => {
// The URL is like the address on an envelope
const path = req.url;
// The HTTP method is like the type of mail (letter, package, express delivery)
const method = req.method;
if (path === '/welcome' && method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Welcome to our office building!');
} else {
res.writeHead(404);
res.end('Address not found');
}
});
server.listen(3000, () => {
console.log('Mail room is open on port 3000');
});
Route Parameters and Dynamic Routing
Imagine a hotel with numbered rooms. Just as you can find any room using its number without needing to list every room explicitly, dynamic routes let you handle many URLs with a single route pattern:
const server = http.createServer((req, res) => {
// Match routes like /room/101, /room/102, etc.
const roomMatch = req.url.match(/^\/room\/(\d+)$/);
if (roomMatch && req.method === 'GET') {
const roomNumber = roomMatch[1];
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
room: roomNumber,
message: `Welcome to room ${roomNumber}`
}));
}
});
A more sophisticated example with multiple parameter types:
const server = http.createServer((req, res) => {
// Creating a simple route parser
const parseRoute = (template, url) => {
const templateParts = template.split('/');
const urlParts = url.split('/');
const params = {};
if (templateParts.length !== urlParts.length) return null;
for (let i = 0; i < templateParts.length; i++) {
if (templateParts[i].startsWith(':')) {
// This is a parameter
const paramName = templateParts[i].slice(1);
params[paramName] = urlParts[i];
} else if (templateParts[i] !== urlParts[i]) {
return null;
}
}
return params;
};
// Example route: /hotel/:floor/room/:number
const params = parseRoute('/hotel/:floor/room/:number', req.url);
if (params) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
floor: params.floor,
roomNumber: params.number,
message: `Room ${params.number} on floor ${params.floor}`
}));
}
});
Route Organization and Middleware
Think of middleware as security checkpoints in a building. Before visitors reach their final destination, they might need to sign in, get a badge, or be escorted. Here's how we can implement this concept:
// Middleware function - like a security checkpoint
const logRequest = (req, res, next) => {
const timestamp = new Date().toISOString();
console.log(`${timestamp}: ${req.method} ${req.url}`);
next(); // Proceed to the next checkpoint or final destination
};
// Authentication middleware - like checking visitor ID
const authenticate = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
res.writeHead(401);
return res.end('Please show your ID');
}
next();
};
// Router implementation
class Router {
constructor() {
this.routes = [];
this.middleware = [];
}
use(middleware) {
this.middleware.push(middleware);
}
addRoute(method, path, handler) {
this.routes.push({ method, path, handler });
}
async handle(req, res) {
// Run all middleware first
for (const middleware of this.middleware) {
await new Promise(resolve => middleware(req, res, resolve));
}
// Find matching route
const route = this.routes.find(r =>
r.method === req.method && r.path === req.url
);
if (route) {
return route.handler(req, res);
}
res.writeHead(404);
res.end('Not Found');
}
}
Using our router:
const router = new Router();
// Add middleware
router.use(logRequest);
router.use(authenticate);
// Define routes
router.addRoute('GET', '/lobby', (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Welcome to the lobby!');
});
router.addRoute('GET', '/office', (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('This is the office area');
});
// Create server with router
const server = http.createServer((req, res) => {
router.handle(req, res);
});
Pattern Matching and Advanced Routing
Consider a library where books are organized by category, author, and title. We can create a sophisticated routing system to handle this hierarchy:
class AdvancedRouter {
constructor() {
this.routes = [];
}
addRoute(method, pattern, handler) {
// Convert pattern to regex
const regexPattern = pattern
.replace(/:[a-zA-Z]+/g, '([^/]+)')
.replace(/\*/g, '.*');
const regex = new RegExp(`^${regexPattern}$`);
this.routes.push({ method, regex, handler });
}
async handle(req, res) {
const route = this.routes.find(r => {
return r.method === req.method &&
r.regex.test(req.url);
});
if (route) {
const matches = req.url.match(route.regex);
const params = matches.slice(1);
return route.handler(req, res, params);
}
res.writeHead(404);
res.end('Not Found');
}
}
// Usage example
const router = new AdvancedRouter();
router.addRoute('GET', '/books/:category/:author/:title', (req, res, params) => {
const [category, author, title] = params;
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ category, author, title }));
});
router.addRoute('GET', '/books/:category/*', (req, res, params) => {
const [category, rest] = params;
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
category,
subPath: rest
}));
});
Real-World Application: RESTful API Router
Let's build a complete example of a blog API with proper routing:
class BlogRouter {
constructor() {
this.routes = new Map();
this.middleware = [];
}
use(middleware) {
this.middleware.push(middleware);
}
route(method, path, handler) {
const key = `${method}:${path}`;
this.routes.set(key, handler);
return this;
}
async handle(req, res) {
// Parse JSON body if present
if (req.headers['content-type'] === 'application/json') {
const buffers = [];
for await (const chunk of req) {
buffers.push(chunk);
}
req.body = JSON.parse(Buffer.concat(buffers).toString());
}
// Run middleware
for (const middleware of this.middleware) {
await new Promise(resolve => middleware(req, res, resolve));
}
const key = `${req.method}:${req.url}`;
const handler = this.routes.get(key);
if (handler) {
return handler(req, res);
}
res.writeHead(404);
res.end('Not Found');
}
}
// Usage example
const router = new BlogRouter();
// Middleware for logging
router.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
// Blog routes
router
.route('GET', '/posts', (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ posts: [] }));
})
.route('POST', '/posts', (req, res) => {
// Create new post
const post = req.body;
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(post));
})
.route('GET', '/posts/:id', (req, res) => {
// Get single post
})
.route('PUT', '/posts/:id', (req, res) => {
// Update post
})
.route('DELETE', '/posts/:id', (req, res) => {
// Delete post
});
// Create server
const server = http.createServer((req, res) => {
router.handle(req, res);
});
server.listen(3000, () => {
console.log('Blog API server running on port 3000');
});
Further Topics to Explore
To deepen your understanding of HTTP routing, consider exploring:
Route versioning - Managing API versions, like having different editions of a map for different time periods
Route validation - Ensuring requests meet specific criteria, like checking if visitors have the right clearance level
Route documentation - Generating API documentation from route definitions, like creating a building directory automatically
Route testing - Writing tests for your routes, like running fire drills to ensure emergency exits work
Route optimization - Improving route matching performance, like optimizing the mail sorting process
Content negotiation - Handling different response formats, like providing information in multiple languages