URL routing maps incoming requests to handler functions based on the URL path
and HTTP method. This is a fundamental pattern in web servers and APIs.
This example shows a minimal router implementation using vanilla Node.js,
without any framework dependencies.
Basic Router Class
export class Router {
constructor() {
this.routes = [];
}
get(path, handler) {
this.routes.push({ method: 'GET', path, handler });
}
post(path, handler) {
this.routes.push({ method: 'POST', path, handler });
}
put(path, handler) {
this.routes.push({ method: 'PUT', path, handler });
}
delete(path, handler) {
this.routes.push({ method: 'DELETE', path, handler });
}
pathToRegex(path) {
const pattern = path
.replace(/:\w+/g, '([^/]+)')
.replace(/\//g, '\\/');
return new RegExp(`^${pattern}$`);
}
getParamNames(path) {
const matches = path.match(/:\w+/g) || [];
return matches.map(m => m.slice(1));
}
match(method, url) {
for (const route of this.routes) {
if (route.method !== method) continue;
const regex = this.pathToRegex(route.path);
const match = url.match(regex);
if (match) {
const paramNames = this.getParamNames(route.path);
const params = {};
paramNames.forEach((name, i) => {
params[name] = match[i + 1];
});
return { handler: route.handler, params };
}
}
return null;
}
}
Using the Router
import { createServer } from 'node:http';
import { Router } from './router.js';
const router = new Router();
router.get('/', (req, res) => {
res.json({ message: 'Welcome to the API' });
});
router.get('/users', (req, res) => {
res.json([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]);
});
router.get('/users/:id', (req, res, params) => {
const user = findUser(params.id);
if (!user) {
res.statusCode = 404;
res.json({ error: 'User not found' });
return;
}
res.json(user);
});
router.get('/users/:userId/posts/:postId', (req, res, params) => {
res.json({ userId: params.userId, postId: params.postId });
});
router.post('/users', async (req, res) => {
const body = await parseJSON(req);
const user = createUser(body);
res.statusCode = 201;
res.json(user);
});
router.delete('/users/:id', (req, res, params) => {
deleteUser(params.id);
res.statusCode = 204;
res.end();
});
Server Setup with Helpers
function parseJSON(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
try {
resolve(body ? JSON.parse(body) : {});
} catch (e) {
reject(new Error('Invalid JSON'));
}
});
});
}
function parseQuery(url) {
const queryString = url.split('?')[1] || '';
return Object.fromEntries(new URLSearchParams(queryString));
}
const server = createServer(async (req, res) => {
res.json = (data) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(data));
};
const [pathname] = req.url.split('?');
req.query = parseQuery(req.url);
const match = router.match(req.method, pathname);
if (match) {
try {
await match.handler(req, res, match.params);
} catch (error) {
res.statusCode = 500;
res.json({ error: error.message });
}
} else {
res.statusCode = 404;
res.json({ error: 'Not Found' });
}
});
server.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
Route patterns supported:
/users - Exact match
/users/:id - Single parameter
/users/:userId/posts/:postId - Multiple parameters
Query String Parameters
router.get('/users', (req, res) => {
const { page = 1, limit = 10, sort } = req.query;
const users = getUsers({
offset: (page - 1) * limit,
limit: parseInt(limit),
sortBy: sort
});
res.json({
data: users,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: getTotalUsers()
}
});
});
← Back to HTTP Examples