A route in Express is the combination of an HTTP method and a URL path that maps to a handler function. When a request arrives, Express walks through all registered routes from top to bottom and calls the first handler whose method and path match the request. Understanding exactly how this matching works — and how route order matters — gives you precise control over every request your API handles. In this lesson you will learn every way to define routes in Express, how all five REST methods are handled, and the patterns that keep your route definitions clean and predictable.
Route Anatomy
// method path handler function
// ↓ ↓ ↓
app.get('/api/posts', (req, res) => {
res.json({ message: 'Get all posts' });
});
// Every route needs three things:
// 1. HTTP method — get, post, put, patch, delete (or all for any method)
// 2. URL path — string, regex, or pattern with parameters
// 3. Handler(s) — one or more functions: (req, res, next) => {}
app.get('/api/posts/featured', handler) must come before app.get('/api/posts/:id', handler) — otherwise the string 'featured' will be captured as the :id parameter.app.all('/path', handler) to match any HTTP method at a given path — useful for applying middleware to all methods of a route (e.g. logging or rate limiting). For path patterns that should respond to every method and every sub-path, use app.all('*', handler) as your catch-all — but always define it last./api/posts and /api/Posts are different routes. /api/posts and /api/posts/ are different routes. You can change this with app.set('case sensitive routing', false) and app.set('strict routing', false), but it is better to be explicit and consistent in your API design than to rely on these settings.All Five REST Methods
// Posts resource — all five HTTP methods
// READ — get all posts
app.get('/api/posts', async (req, res) => {
res.json({ method: 'GET', action: 'return all posts' });
});
// CREATE — add a new post
app.post('/api/posts', async (req, res) => {
res.status(201).json({ method: 'POST', action: 'create post', body: req.body });
});
// UPDATE — replace entire post
app.put('/api/posts/:id', async (req, res) => {
res.json({ method: 'PUT', action: `replace post ${req.params.id}` });
});
// UPDATE — partial update
app.patch('/api/posts/:id', async (req, res) => {
res.json({ method: 'PATCH', action: `update fields on post ${req.params.id}` });
});
// DELETE — remove a post
app.delete('/api/posts/:id', async (req, res) => {
res.status(204).end();
});
Route Order Matters — Specific Before General
// ❌ WRONG — :id catches 'featured' before the specific route is reached
app.get('/api/posts/:id', handler); // 'featured' matches :id → wrong handler
app.get('/api/posts/featured', handler); // never reached
// ✅ CORRECT — specific route before the parameterised route
app.get('/api/posts/featured', handler); // matched first for /api/posts/featured
app.get('/api/posts/:id', handler); // matched for /api/posts/123, /api/posts/abc
Route Chaining with app.route()
// Instead of repeating the path three times:
app.get('/api/posts/:id', getPostById);
app.put('/api/posts/:id', updatePost);
app.delete('/api/posts/:id', deletePost);
// Use route chaining — same path, multiple methods:
app.route('/api/posts/:id')
.get(getPostById)
.put(updatePost)
.delete(deletePost);
// Also works for the collection route:
app.route('/api/posts')
.get(getAllPosts)
.post(createPost);
Multiple Handlers Per Route
// Routes can have multiple handler functions (middleware chain)
// Each must call next() to pass control to the next handler
const validatePost = (req, res, next) => {
if (!req.body.title) {
return res.status(400).json({ message: 'Title is required' });
}
next(); // validation passed — continue to the actual handler
};
const checkAuth = (req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).json({ message: 'No token provided' });
}
next();
};
// Both middleware run in order before the final handler
app.post('/api/posts', checkAuth, validatePost, async (req, res) => {
// Only reaches here if auth and validation both passed
res.status(201).json({ data: req.body });
});
Practical Route Design for the MERN Blog API
| Method | Path | Description | Auth Required |
|---|---|---|---|
| GET | /api/posts | Get all published posts (paginated) | No |
| GET | /api/posts/featured | Get featured posts | No |
| GET | /api/posts/:id | Get single post by ID | No |
| POST | /api/posts | Create a new post | Yes |
| PUT | /api/posts/:id | Replace a post | Yes (owner) |
| PATCH | /api/posts/:id | Update post fields | Yes (owner) |
| DELETE | /api/posts/:id | Delete a post | Yes (owner) |
Common Mistakes
Mistake 1 — Specific route after parameterised route
❌ Wrong — the specific path can never be reached:
app.get('/api/posts/:id', getPostById); // 'featured' captured as :id
app.get('/api/posts/featured', getFeatured); // never runs
✅ Correct — always place specific paths before wildcard/parameterised routes:
app.get('/api/posts/featured', getFeatured); // checked first ✓
app.get('/api/posts/:id', getPostById); // fallback for actual IDs
Mistake 2 — Forgetting next() in middleware handlers
❌ Wrong — middleware that does not call next() silently hangs the request:
const logRequest = (req, res, next) => {
console.log(`${req.method} ${req.path}`);
// forgot next() — request is stuck here forever
};
app.use(logRequest);
app.get('/api/posts', handler); // never reached
✅ Correct — always call next() at the end of middleware unless sending a response:
const logRequest = (req, res, next) => {
console.log(`${req.method} ${req.path}`);
next(); // ✓ pass control to the next middleware or route
};
Mistake 3 — Using app.get() for a route that should be app.post()
❌ Wrong — wrong HTTP method causes 404:
app.get('/api/posts', createPost); // defined as GET
// Client sends POST /api/posts → no matching route → 404
✅ Correct — match the route method to the HTTP method the client sends:
app.post('/api/posts', createPost); // POST method ✓
Quick Reference
| Task | Code |
|---|---|
| GET route | app.get('/path', handler) |
| POST route | app.post('/path', handler) |
| PUT route | app.put('/path/:id', handler) |
| PATCH route | app.patch('/path/:id', handler) |
| DELETE route | app.delete('/path/:id', handler) |
| Any method | app.all('/path', handler) |
| Chain methods | app.route('/path').get(h1).post(h2) |
| Multiple handlers | app.post('/path', mw1, mw2, handler) |