The Hotel Concierge Analogy
Imagine a hotel concierge desk. When guests approach the desk, they might ask for different services: restaurant recommendations, booking a taxi, or arranging tours. The concierge needs to handle each request differently based on what the guest wants. This is exactly how route handlers work in a web server!
Just as a concierge directs different requests to different services, route handlers direct different HTTP requests to different parts of your application. Let's explore how to create these handlers in Node.js.
Understanding Route Handlers
A route handler is like having a specific procedure for each type of request. Think of it as having different instruction manuals for different situations. Here's how we create our first route handler:
const http = require('http');
const server = http.createServer((req, res) => {
// This is like our concierge asking "How can I help you?"
if (req.method === 'GET' && req.url === '/') {
// This is like handling a request for general information
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
return res.end('Welcome to our hotel!');
}
});
Building a Complete Routing System
Let's create a more comprehensive example - imagine we're building an API for a pet store:
const http = require('http');
const server = http.createServer((req, res) => {
// First, let's log every request (like keeping a guest log)
console.log(`Received ${req.method} request to ${req.url}`);
// GET /pets - List all pets
if (req.method === 'GET' && req.url === '/pets') {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
const pets = [
{ id: 1, name: 'Fluffy', type: 'cat' },
{ id: 2, name: 'Rover', type: 'dog' }
];
return res.end(JSON.stringify(pets));
}
// POST /pets - Add a new pet
if (req.method === 'POST' && req.url === '/pets') {
res.statusCode = 201;
res.setHeader('Content-Type', 'application/json');
return res.end(JSON.stringify({ message: 'Pet created successfully!' }));
}
// GET /pets/available - List available pets
if (req.method === 'GET' && req.url === '/pets/available') {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
const availablePets = [
{ id: 1, name: 'Fluffy', type: 'cat', status: 'available' }
];
return res.end(JSON.stringify(availablePets));
}
// If we reach here, no route matched (like saying "Sorry, we don't offer that service")
res.statusCode = 404;
res.setHeader('Content-Type', 'application/json');
return res.end(JSON.stringify({ error: 'Route not found' }));
});
Organizing Routes for Better Maintainability
As your application grows, you might want to organize your routes better. Here's an approach using objects:
const http = require('http');
// Route handlers organized by path and method
const routes = {
'/pets': {
'GET': (req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
const pets = [
{ id: 1, name: 'Fluffy', type: 'cat' },
{ id: 2, name: 'Rover', type: 'dog' }
];
return res.end(JSON.stringify(pets));
},
'POST': (req, res) => {
res.statusCode = 201;
res.setHeader('Content-Type', 'application/json');
return res.end(JSON.stringify({ message: 'Pet created successfully!' }));
}
},
'/pets/available': {
'GET': (req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
const availablePets = [
{ id: 1, name: 'Fluffy', type: 'cat', status: 'available' }
];
return res.end(JSON.stringify(availablePets));
}
}
};
const server = http.createServer((req, res) => {
// Check if we have handlers for this path
const pathHandlers = routes[req.url];
if (pathHandlers) {
// Check if we have a handler for this method
const handler = pathHandlers[req.method];
if (handler) {
return handler(req, res);
}
}
// No matching route found
res.statusCode = 404;
res.setHeader('Content-Type', 'application/json');
return res.end(JSON.stringify({ error: 'Route not found' }));
});
Handling Dynamic Routes
Sometimes we need routes with variables in them, like getting a specific pet by ID. Here's how we can handle that:
const server = http.createServer((req, res) => {
// Match route patterns like /pets/123
const petIdMatch = req.url.match(/^\/pets\/(\d+)$/);
if (req.method === 'GET' && petIdMatch) {
const petId = parseInt(petIdMatch[1], 10);
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
const pet = { id: petId, name: 'Fluffy', type: 'cat' };
return res.end(JSON.stringify(pet));
}
});
Error Handling in Routes
Just as a good concierge knows how to handle unexpected situations, our route handlers should handle errors gracefully:
const server = http.createServer((req, res) => {
try {
if (req.method === 'GET' && req.url === '/pets') {
// Simulate a database error
throw new Error('Database connection failed');
}
} catch (error) {
console.error('Error handling request:', error);
res.statusCode = 500;
res.setHeader('Content-Type', 'application/json');
return res.end(JSON.stringify({
error: 'Internal Server Error',
message: error.message
}));
}
});
Best Practices for Route Handlers
When creating route handlers, keep these important practices in mind:
Always Return After Sending Response
// GOOD
if (req.method === 'GET' && req.url === '/pets') {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
return res.end(JSON.stringify({ pets: [] }));
}
// BAD - Missing return
if (req.method === 'GET' && req.url === '/pets') {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ pets: [] }));
// Code might continue executing!
}
Consistent Error Handling
function handleError(res, error) {
const statusCode = error.statusCode || 500;
res.statusCode = statusCode;
res.setHeader('Content-Type', 'application/json');
return res.end(JSON.stringify({
error: error.message || 'Internal Server Error',
status: statusCode
}));
}
// Using the error handler
if (req.method === 'GET' && req.url === '/pets') {
try {
// Some operation that might fail
throw new Error('Database error');
} catch (error) {
return handleError(res, error);
}
}
Advanced Route Handling Patterns
As your application grows, consider these advanced patterns:
// Middleware pattern
function authenticate(req, res, next) {
const authHeader = req.headers['authorization'];
if (!authHeader) {
res.statusCode = 401;
res.setHeader('Content-Type', 'application/json');
return res.end(JSON.stringify({ error: 'Authentication required' }));
}
next();
}
// Route handler with middleware
const protectedRoute = (req, res) => {
authenticate(req, res, () => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
return res.end(JSON.stringify({ data: 'Protected data' }));
});
};
Next Steps in Your Learning Journey
To deepen your understanding of route handling, consider exploring:
- URL parameter parsing and validation
- Query string handling
- Route middleware patterns
- RESTful routing conventions
- Authentication and authorization in routes