Routing Basics
Real applications need to handle many different URLs and HTTP methods. Routing is the process of directing incoming requests to the appropriate handler based on the URL path and method. In this lesson, you'll build a simple router from scratch.
What is Routing?
Routing maps incoming requests to specific handler functions:
GET /users -> handleGetUsers()
POST /users -> handleCreateUser()
GET /users/123 -> handleGetUser(123)
PUT /users/123 -> handleUpdateUser(123)
DELETE /users/123 -> handleDeleteUser(123)
Simple if/else Routing
The most basic approach:
const http = require('http');
const server = http.createServer((req, res) => {
const { method } = req;
const url = new URL(req.url, `http://${req.headers.host}`);
const path = url.pathname;
// Route: GET /
if (method === 'GET' && path === '/') {
res.end('Home page');
return;
}
// Route: GET /about
if (method === 'GET' && path === '/about') {
res.end('About page');
return;
}
// Route: GET /api/users
if (method === 'GET' && path === '/api/users') {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify([{ id: 1, name: 'Alice' }]));
return;
}
// 404 Not Found
res.statusCode = 404;
res.end('Not Found');
});
server.listen(3000);
Building a Simple Router
Let's create a reusable router class:
class Router {
constructor() {
this.routes = [];
}
// Add a route
add(method, path, handler) {
this.routes.push({ method, path, handler });
}
// Shorthand methods
get(path, handler) { this.add('GET', path, handler); }
post(path, handler) { this.add('POST', path, handler); }
put(path, handler) { this.add('PUT', path, handler); }
delete(path, handler) { this.add('DELETE', path, handler); }
// Find matching route
match(method, path) {
return this.routes.find(
route => route.method === method && route.path === path
);
}
// Handle request
handle(req, res) {
const url = new URL(req.url, `http://${req.headers.host}`);
const route = this.match(req.method, url.pathname);
if (route) {
route.handler(req, res);
} else {
res.statusCode = 404;
res.end('Not Found');
}
}
}
Route Parameters
Handle dynamic URL segments like /users/123:
class Router {
constructor() {
this.routes = [];
}
add(method, path, handler) {
// Convert /users/:id to regex /users/([^/]+)
const paramNames = [];
const pattern = path.replace(/:(\w+)/g, (_, name) => {
paramNames.push(name);
return '([^/]+)';
});
const regex = new RegExp(`^${pattern}$`);
this.routes.push({ method, regex, paramNames, handler });
}
match(method, path) {
for (const route of this.routes) {
if (route.method !== method) continue;
const match = path.match(route.regex);
if (match) {
// Extract parameters
const params = {};
route.paramNames.forEach((name, i) => {
params[name] = match[i + 1];
});
return { handler: route.handler, params };
}
}
return null;
}
}
// Usage
router.add('GET', '/users/:id', (req, res, params) => {
res.end(`User ID: ${params.id}`);
});
router.add('GET', '/posts/:postId/comments/:commentId', (req, res, params) => {
res.end(`Post: ${params.postId}, Comment: ${params.commentId}`);
});
RESTful API Structure
A typical REST API routes structure:
const router = new Router();
// Users resource
router.get('/api/users', listUsers);
router.post('/api/users', createUser);
router.get('/api/users/:id', getUser);
router.put('/api/users/:id', updateUser);
router.delete('/api/users/:id', deleteUser);
// Nested resources
router.get('/api/users/:userId/posts', getUserPosts);
router.post('/api/users/:userId/posts', createUserPost);
// Other resources
router.get('/api/posts', listPosts);
router.get('/api/posts/:id', getPost);
Organizing Routes
As your app grows, organize routes into modules:
// routes/users.js
const userRoutes = (router) => {
router.get('/api/users', listUsers);
router.post('/api/users', createUser);
router.get('/api/users/:id', getUser);
router.put('/api/users/:id', updateUser);
router.delete('/api/users/:id', deleteUser);
};
module.exports = userRoutes;
// routes/posts.js
const postRoutes = (router) => {
router.get('/api/posts', listPosts);
router.post('/api/posts', createPost);
router.get('/api/posts/:id', getPost);
};
module.exports = postRoutes;
// app.js
const router = new Router();
require('./routes/users')(router);
require('./routes/posts')(router);
Static File Serving
Serve files from a directory:
const http = require('http');
const fs = require('fs');
const path = require('path');
const MIME_TYPES = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'text/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml'
};
function serveStatic(req, res, staticDir) {
const url = new URL(req.url, `http://${req.headers.host}`);
let filePath = path.join(staticDir, url.pathname);
// Default to index.html
if (url.pathname === '/') {
filePath = path.join(staticDir, 'index.html');
}
const ext = path.extname(filePath);
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404);
res.end('File not found');
return;
}
res.writeHead(200, { 'Content-Type': contentType });
res.end(data);
});
}
Complete Router Example
const http = require('http');
class Router {
constructor() {
this.routes = [];
}
add(method, path, handler) {
const paramNames = [];
const pattern = path.replace(/:(\w+)/g, (_, name) => {
paramNames.push(name);
return '([^/]+)';
});
this.routes.push({
method,
regex: new RegExp(`^${pattern}$`),
paramNames,
handler
});
}
get(path, handler) { this.add('GET', path, handler); }
post(path, handler) { this.add('POST', path, handler); }
put(path, handler) { this.add('PUT', path, handler); }
delete(path, handler) { this.add('DELETE', path, handler); }
handle(req, res) {
const url = new URL(req.url, `http://${req.headers.host}`);
for (const route of this.routes) {
if (route.method !== req.method) continue;
const match = url.pathname.match(route.regex);
if (match) {
const params = {};
route.paramNames.forEach((name, i) => {
params[name] = match[i + 1];
});
req.params = params;
req.query = Object.fromEntries(url.searchParams);
return route.handler(req, res);
}
}
res.statusCode = 404;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ error: 'Not Found' }));
}
}
// Create app
const router = new Router();
// Define routes
router.get('/', (req, res) => {
res.end('Home');
});
router.get('/api/users', (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify([{ id: 1, name: 'Alice' }]));
});
router.get('/api/users/:id', (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ id: req.params.id }));
});
// Create server
const server = http.createServer((req, res) => router.handle(req, res));
server.listen(3000);
Key Takeaways
- Routing maps URLs and methods to handler functions
- Start simple with if/else, then refactor to a router class
- Use regex to match route patterns with parameters
- Extract route parameters (
:id) into aparamsobject - Organize routes into separate files as your app grows
- Follow REST conventions for API routes
- Return 404 for unmatched routes
Summary
You've learned how to implement routing in Node.js—from simple if/else chains to a proper router class with parameter extraction. You understand RESTful API conventions and how to organize routes as your application grows. This is a fundamental skill for building any web application.
In the next module, you'll learn about npm and how to use external packages to enhance your Node.js applications.

