Why Express.js?
Imagine you're building a house. You could create everything from scratch, crafting each brick and mixing your own concrete, but that would be incredibly time-consuming. Instead, you use pre-made materials and proven construction techniques. Express.js serves a similar purpose in web development - it provides a robust foundation and proven patterns for building web applications.
Express.js vs. Plain Node.js
Let's compare building a simple server in both:
Plain Node.js Server
const http = require('http');
const server = http.createServer((req, res) => {
// Need to handle routing manually
if (req.url === '/api/users' && req.method === 'GET') {
res.writeHead(200, {
'Content-Type': 'application/json'
});
res.end(JSON.stringify({
users: ['John', 'Jane']
}));
} else {
res.writeHead(404);
res.end('Not Found');
}
});
server.listen(3000, () => {
console.log('Server running on port 3000');
});
Express.js Server
const express = require('express');
const app = express();
// Clean, intuitive routing
app.get('/api/users', (req, res) => {
res.json({
users: ['John', 'Jane']
});
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Initializing an Express Application
Step-by-Step Setup
Setting up an Express application is like preparing your workspace before starting a project. Let's walk through it:
// 1. Create a new directory and initialize npm
mkdir my-express-app
cd my-express-app
npm init -y
// 2. Install Express
npm install express
// 3. Create your main file (index.js)
const express = require('express');
const app = express();
// 4. Add middleware for parsing JSON
app.use(express.json());
// 5. Create your first route
app.get('/', (req, res) => {
res.send('Welcome to my Express application!');
});
// 6. Start the server
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Each step serves a specific purpose:
- npm init creates your package.json file, managing dependencies
- express.json() middleware allows your server to parse JSON request bodies
- The route handler defines how your server responds to requests
- app.listen starts your server on the specified port
Understanding Express Route Handlers
Route Handling: The Traffic Control of Your Application
Think of route handlers as traffic controllers at an intersection. They determine:
- Which path the request should take
- What type of request it is (GET, POST, PUT, DELETE)
- What response should be sent back
Comprehensive Routing Example
const express = require('express');
const app = express();
// Middleware to parse JSON bodies
app.use(express.json());
// Basic GET route
app.get('/api/items', (req, res) => {
res.json({
items: ['item1', 'item2']
});
});
// POST route with request body
app.post('/api/items', (req, res) => {
const newItem = req.body;
// Validation example
if (!newItem.name) {
return res.status(400).json({
error: 'Item name is required'
});
}
res.status(201).json({
message: 'Item created',
item: newItem
});
});
// Route with URL parameters
app.get('/api/items/:id', (req, res) => {
const itemId = req.params.id;
res.json({
id: itemId,
name: `Item ${itemId}`
});
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: 'Something went wrong!'
});
});
Request and Response Objects in Express
Express vs Node.js HTTP: Request Object Comparison
// Node.js HTTP Request
http.createServer((req, res) => {
// Need to manually parse URL
const parsedUrl = new URL(req.url, 'http://localhost');
// Need to manually collect body data
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
const data = JSON.parse(body);
// Handle data
});
});
// Express Request
app.post('/api/data', (req, res) => {
// Body is already parsed
const data = req.body;
// Query parameters are easily accessible
const limit = req.query.limit;
// URL parameters are readily available
const id = req.params.id;
});
Working with Response Objects
// Different ways to send responses in Express
app.get('/api/examples', (req, res) => {
// Send plain text
res.send('Hello, World!');
// Send JSON
res.json({ message: 'Hello, World!' });
// Send with status code
res.status(201).json({ created: true });
// Send file
res.sendFile('/path/to/file.pdf');
// Redirect
res.redirect('/new-location');
});
Route Matching Order in Express
Understanding Route Precedence
Express matches routes in the order they are defined. This is like a cascade of decisions:
// Order matters!
app.get('/api/items', (req, res) => {
// This will match first
res.send('All items');
});
app.get('/api/items/:id', (req, res) => {
// This will match second
res.send('Specific item');
});
// More specific routes should come first
app.get('/api/items/special', (req, res) => {
// This will never match!
res.send('Special items');
});
Debugging Express with Postman
Step-by-Step Debugging Guide
1. Set Up Request Logging
const morgan = require('morgan');
app.use(morgan('dev'));
2. Test Different HTTP Methods
In Postman:
- Create a new request
- Select the HTTP method (GET, POST, etc.)
- Enter your URL (e.g., http://localhost:3000/api/items)
- For POST/PUT requests, add body data in JSON format
- Send the request and examine the response
3. Common Debugging Scenarios
// Add error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: err.message,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
});
});
// Add request logging
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
console.log('Body:', req.body);
next();
});
Putting It All Together: Building a Complete Express Server
Complete Server Example
const express = require('express');
const morgan = require('morgan');
const app = express();
// Middleware
app.use(morgan('dev'));
app.use(express.json());
// In-memory database
const items = [];
// Routes
app.get('/api/items', (req, res) => {
res.json(items);
});
app.post('/api/items', (req, res) => {
const { name, description } = req.body;
if (!name) {
return res.status(400).json({
error: 'Name is required'
});
}
const newItem = {
id: items.length + 1,
name,
description,
createdAt: new Date()
};
items.push(newItem);
res.status(201).json(newItem);
});
app.get('/api/items/:id', (req, res) => {
const item = items.find(i => i.id === parseInt(req.params.id));
if (!item) {
return res.status(404).json({
error: 'Item not found'
});
}
res.json(item);
});
app.put('/api/items/:id', (req, res) => {
const itemIndex = items.findIndex(i => i.id === parseInt(req.params.id));
if (itemIndex === -1) {
return res.status(404).json({
error: 'Item not found'
});
}
const updatedItem = {
...items[itemIndex],
...req.body,
id: items[itemIndex].id
};
items[itemIndex] = updatedItem;
res.json(updatedItem);
});
app.delete('/api/items/:id', (req, res) => {
const itemIndex = items.findIndex(i => i.id === parseInt(req.params.id));
if (itemIndex === -1) {
return res.status(404).json({
error: 'Item not found'
});
}
items.splice(itemIndex, 1);
res.status(204).end();
});
// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: 'Something went wrong!'
});
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});