Creating Routes and Handling Requests with Express.js
What is Express.js?
Express.js is a minimal, unopinionated web framework for Node.js. It wraps Node's built-in http module with a clean, developer-friendly API — giving you routing, middleware, and response helpers without locking you into a rigid structure.
Think of it this way: Node's http module hands you the raw HTTP pipe. Express gives you a well-designed faucet to control it.
Quick fact: Express is the E in the MERN/MEAN stack, and one of the most downloaded npm packages ever — with over 30 million weekly downloads.
Why Express Simplifies Node.js Development
Raw Node.js forces you to manually parse URLs, method names, request bodies, and headers for every single route. As your app grows, that becomes a maintenance nightmare.
Here's an honest side-by-side:
Raw Node.js HTTP
const http = require('http');
const server = http.createServer((req, res) => {
if (req.url === '/' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World');
} else if (req.url === '/users') {
// more manual checks...
}
});
server.listen(3000);
Express.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
app.get('/users', (req, res) => {
res.json({ users: [] });
});
app.listen(3000);
Express handles all the tedious boilerplate — content-type headers, URL parsing, method routing — so you can focus on your application logic.
Creating Your First Express Server
Step 1 — Install Express
# Initialize a new project
mkdir my-express-app && cd my-express-app
npm init -y
# Install Express
npm install express
Step 2 — Write the minimal server
// server.js
const express = require('express');
const app = express(); // create the app instance
const PORT = 3000;
// A single route — responds to GET /
app.get('/', (req, res) => {
res.send('🚀 Express server is live!');
});
// Start listening
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});
Run it with node server.js and open http://localhost:3000 in your browser. That's your first Express server.
💡 Pro tip: Install nodemon (
npm i -g nodemon) and runnodemon server.js— it auto-restarts your server on every file change during development.
Request → Route Handler → Response Flow
Before diving into routes, it helps to understand what actually happens when a request hits your server:
Every HTTP request follows this exact path. Express's job is to make steps 2–4 as clean as possible.
Handling GET Requests
GET requests are the most common — they ask the server to retrieve data. Express handles them with app.get(path, handler).
const express = require('express');
const app = express();
// 1. Static route
app.get('/about', (req, res) => {
res.send('About page');
});
// 2. Route with URL parameter → /users/42
app.get('/users/:id', (req, res) => {
const { id } = req.params; // grab :id from URL
res.json({ message: `User ID: ${id}` });
});
// 3. Route with query string → /search?q=express
app.get('/search', (req, res) => {
const { q } = req.query; // grab ?q= from URL
res.json({ query: q, results: [] });
});
app.listen(3000);
Key objects in a GET handler
| Object | What it contains |
|---|---|
req.params |
Values from dynamic route segments (:id) |
req.query |
Values from query strings (?key=value) |
req.headers |
HTTP headers sent by the client |
Handling POST Requests
POST requests send data to the server — creating resources, submitting forms, logging in. The request body carries the data. Express needs a built-in middleware to parse it.
const express = require('express');
const app = express();
// ✅ Required: parse incoming JSON bodies
app.use(express.json());
// In-memory "database" for the demo
const users = [];
// GET all users
app.get('/users', (req, res) => {
res.json(users);
});
// POST create a new user
app.post('/users', (req, res) => {
const { name, email } = req.body; // parsed from JSON body
if (!name || !email) {
return res
.status(400)
.json({ error: 'name and email are required' });
}
const newUser = {
id: users.length + 1,
name,
email,
};
users.push(newUser);
res.status(201).json(newUser); // 201 = Created
});
app.listen(3000, () => console.log('Server on port 3000'));
Test it with curl
# Create a user
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Arjun","email":"arjun@example.com"}'
# Get all users
curl http://localhost:3000/users
Common mistake: Always send exactly one response per request. Calling
res.send()twice throws "Cannot set headers after they are sent to the client". Usereturn res.json(...)inside conditionals to stop execution early.
Sending Responses
The res object is your toolkit for sending data back to the client.
// 1. Plain text
res.send('Hello!');
// 2. JSON (auto-sets Content-Type: application/json)
res.json({ status: 'ok', data: [] });
// 3. Custom HTTP status code
res.status(404).json({ error: 'Not found' });
// 4. Redirect
res.redirect('/new-url');
// 5. Set a custom header, then respond
res.set('X-Powered-By', 'My API').json({ ok: true });
Common HTTP status codes to know
| Code | Meaning | When to use |
|---|---|---|
200 |
OK | Successful GET |
201 |
Created | Successful POST |
400 |
Bad Request | Missing/invalid input |
404 |
Not Found | Resource doesn't exist |
500 |
Server Error | Something broke on your end |
Putting It All Together
Here's a complete, minimal Express API with GET and POST routes, proper status codes, and JSON responses:
// server.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
// ── Middleware ──────────────────────────────────────
app.use(express.json());
// ── Data store (in-memory for demo) ────────────────
let products = [
{ id: 1, name: 'Keyboard', price: 2499 },
{ id: 2, name: 'Mouse', price: 999 },
];
// ── Routes ─────────────────────────────────────────
// GET all products
app.get('/products', (req, res) => {
res.json({ count: products.length, products });
});
// GET single product by ID
app.get('/products/:id', (req, res) => {
const product = products.find(
(p) => p.id === parseInt(req.params.id)
);
if (!product) {
return res.status(404).json({ error: 'Product not found' });
}
res.json(product);
});
// POST create a new product
app.post('/products', (req, res) => {
const { name, price } = req.body;
if (!name || !price) {
return res
.status(400)
.json({ error: 'name and price are required' });
}
const newProduct = { id: products.length + 1, name, price };
products.push(newProduct);
res.status(201).json(newProduct);
});
// 404 fallback for unknown routes
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
// ── Start server ────────────────────────────────────
app.listen(PORT, () => {
console.log(`✅ Server running → http://localhost:${PORT}`);
});