Introduction to HTTP Requests and Responses
Think of HTTP requests and responses like a conversation at a coffee shop. The customer (client) makes a request to the barista (server), and the barista responds with either the ordered drink or an explanation of why they can't fulfill the order. Every interaction follows this request-response pattern.
Let's look at a basic example of handling requests and responses:
const http = require('http');
const server = http.createServer((request, response) => {
// Like a barista checking what the customer wants
console.log('Customer ordered:', request.url);
console.log('Order method:', request.method);
console.log('Order details:', request.headers);
// Preparing the response (like making the drink)
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end('Here\'s your order! ☕');
});
server.listen(3000, () => {
console.log('Café is open on port 3000');
});
Understanding HTTP Request Properties
Think of an HTTP request like a detailed order form at a restaurant. Each part has its specific purpose:
const server = http.createServer((req, res) => {
// Method: Like the type of service (dine-in, takeout, delivery)
console.log(`Service type: ${req.method}`);
// URL: Like the specific menu item being ordered
console.log(`Order item: ${req.url}`);
// Headers: Like special instructions for the order
console.log('Special instructions:');
console.log(req.headers);
// Query parameters: Like customizations to the order
const url = new URL(req.url, `http://${req.headers.host}`);
console.log('Customizations:');
console.log(url.searchParams);
res.end('Order received!');
});
A more detailed example handling different types of requests:
const server = http.createServer(async (req, res) => {
// Reading the body content (like listening to detailed instructions)
let body = '';
for await (const chunk of req) {
body += chunk;
}
// Parsing JSON body if present (like interpreting complex orders)
let orderDetails;
if (req.headers['content-type'] === 'application/json') {
try {
orderDetails = JSON.parse(body);
} catch (error) {
res.writeHead(400, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({
error: 'Invalid order format'
}));
}
}
console.log('Complete order details:', {
method: req.method,
url: req.url,
headers: req.headers,
body: orderDetails || body
});
res.end('Order processed!');
});
Crafting HTTP Responses
Think of HTTP responses like preparing and serving dishes in a restaurant. Each response needs proper presentation and accompaniments:
const server = http.createServer((req, res) => {
// Setting status code (like the order status)
// 200: Order successful
// 404: Item not found
// 500: Kitchen problems
if (req.url === '/menu') {
// Serving the menu (JSON response)
res.writeHead(200, {
'Content-Type': 'application/json',
'Cache-Control': 'max-age=3600' // Menu valid for an hour
});
const menu = {
drinks: ['Coffee', 'Tea', 'Latte'],
prices: [2.99, 2.50, 3.99]
};
res.end(JSON.stringify(menu));
} else if (req.url === '/special') {
// Serving HTML content (like a special menu card)
res.writeHead(200, {
'Content-Type': 'text/html',
'X-Special-Offer': 'true'
});
res.end(`
Today's Special
Buy one coffee, get one free!
`);
} else {
// Item not on menu
res.writeHead(404, {
'Content-Type': 'application/json',
'X-Error-Type': 'ItemNotFound'
});
res.end(JSON.stringify({
error: 'Item not found on menu'
}));
}
});
Handling Different Content Types
Like a restaurant that serves different types of dishes, each needing specific preparation and presentation:
const server = http.createServer((req, res) => {
// Content type handler (like different serving methods)
const serveContent = (content, type) => {
res.writeHead(200, { 'Content-Type': type });
res.end(content);
};
switch (req.url) {
case '/text':
// Serving plain text (like a simple receipt)
serveContent('Simple order confirmation', 'text/plain');
break;
case '/json':
// Serving JSON (like a detailed order summary)
serveContent(
JSON.stringify({ order: 'confirmed', items: ['coffee'] }),
'application/json'
);
break;
case '/html':
// Serving HTML (like a formatted menu)
serveContent(`
Order Confirmed
Thank you for your order!
`, 'text/html');
break;
case '/binary':
// Serving binary data (like a digital receipt)
const buffer = Buffer.from('Receipt data');
serveContent(buffer, 'application/octet-stream');
break;
default:
res.writeHead(404);
res.end('Item not found');
}
});
Real-World Example: File Server
Let's build a simple file server that handles various content types:
const http = require('http');
const fs = require('fs').promises;
const path = require('path');
const MIME_TYPES = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.gif': 'image/gif',
'.pdf': 'application/pdf'
};
const server = http.createServer(async (req, res) => {
try {
// Remove query parameters and decode URL
const filePath = path.join(
'public',
decodeURIComponent(req.url.split('?')[0])
);
// Security check (prevent directory traversal)
if (!filePath.startsWith('public/')) {
res.writeHead(403);
return res.end('Access denied');
}
const fileStats = await fs.stat(filePath);
if (fileStats.isDirectory()) {
// Serve directory listing
const files = await fs.readdir(filePath);
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
Directory: ${req.url}
${files.map(file => `
- ${file}
`).join('')}
`);
} else {
// Serve file
const ext = path.extname(filePath);
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
const content = await fs.readFile(filePath);
res.writeHead(200, {
'Content-Type': contentType,
'Content-Length': content.length,
'Cache - Control': 'public, max-age=86400'
});
res.end(content);
}
} catch (error) {
if (error.code === 'ENOENT') {
res.writeHead(404);
res.end('File not found');
} else {
res.writeHead(500);
res.end('Server error');
}
}
});
server.listen(3000, () => {
console.log('File server running on port 3000');
});
Error Handling and Status Codes
Like a restaurant handling different situations, HTTP servers need to communicate various outcomes:
const handleError = (res, status, message, details = null) => {
res.writeHead(status, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: message,
details: details,
status: status
}));
};
const server = http.createServer(async (req, res) => {
try {
if (req.url === '/coffee') {
// 200: Success (Order completed)
res.writeHead(200);
res.end('☕ Coffee served!');
} else if (req.url === '/maintenance') {
// 503: Service Unavailable (Machine being cleaned)
handleError(res, 503, 'Coffee machine under maintenance');
} else if (req.url === '/tea') {
// 501: Not Implemented (We don't serve tea)
handleError(res, 501, 'Tea service not available');
} else if (req.url === '/water') {
// 429: Too Many Requests (Too many water orders)
handleError(res, 429, 'Too many water requests, please wait');
} else {
// 404: Not Found (Item not on menu)
handleError(res, 404, 'Item not found on menu');
}
} catch (error) {
// 500: Internal Server Error (Kitchen problems)
handleError(res, 500, 'Internal server error', error.message);
}
});
Further Topics to Explore
To deepen your understanding of HTTP requests and responses, consider exploring:
Compression - Like packaging takeout orders efficiently
Streaming responses - Like serving a multi-course meal in sequence
Content negotiation - Like accommodating dietary preferences
Response caching - Like preparing popular dishes in advance
Request/Response interceptors - Like quality control checks
Rate limiting - Like managing a busy restaurant during peak hours