Express.js Routing

Route methods, parameters, query strings, handlers, and Express Router

REST API Route Params Query Strings

Table of Contents

1. Introduction to Routing

Routing refers to determining how an application responds to a client request to a specific endpoint (URI) and HTTP method.

Basic Route Structure

Patternapp.METHOD(PATH, HANDLER)

Where:

  • app — Express instance
  • METHOD — HTTP method (GET, POST, PUT, DELETE, etc.)
  • PATH — URL path (endpoint)
  • HANDLER — Function executed when route is matched

Simple Example

JavaScriptconst express = require('express'); const app = express(); app.get('/', (req, res) => { res.send('Home Page'); }); app.listen(3000);

2. Route Methods

Express supports all HTTP methods commonly used in RESTful APIs.

Basic HTTP Methods

JavaScriptconst express = require('express'); const app = express(); app.get('/api/users', (req, res) => { res.json({ message: 'Fetching all users' }); }); app.post('/api/users', (req, res) => { res.json({ message: 'Creating a new user' }); }); app.put('/api/users/:id', (req, res) => { res.json({ message: `Updating user ${req.params.id} completely` }); }); app.patch('/api/users/:id', (req, res) => { res.json({ message: `Partially updating user ${req.params.id}` }); }); app.delete('/api/users/:id', (req, res) => { res.json({ message: `Deleting user ${req.params.id}` }); });

Less Common Methods

JavaScriptapp.head('/api/users', (req, res) => { res.status(200).end(); }); app.options('/api/users', (req, res) => { res.set('Allow', 'GET, POST, PUT, DELETE, OPTIONS'); res.status(200).end(); }); app.all('/api/secret', (req, res) => { res.json({ method: req.method, message: 'This route accepts any HTTP method' }); });

Multiple Methods Same Path

JavaScriptapp.get('/products', (req, res) => { res.json({ products: [] }); }); app.post('/products', (req, res) => { res.status(201).json({ message: 'Product created' }); }); app.put('/products', (req, res) => { res.json({ message: 'Product updated' }); });

3. Route Paths

Route paths define the endpoints at which requests can be made.

String Paths

JavaScriptapp.get('/about', (req, res) => { res.send('About page'); }); app.get('/users/profile/settings', (req, res) => { res.send('User settings page'); }); app.get('/document.pdf', (req, res) => { res.sendFile('document.pdf'); });

Pattern Paths (String Patterns)

JavaScriptapp.get('/ab?cd', (req, res) => res.send('Matches /acd or /abcd')); app.get('/ab+cd', (req, res) => res.send('Matches /abcd, /abbcd, /abbbcd, etc.')); app.get('/ab*cd', (req, res) => res.send('Matches /abXcd, /abXYZcd, /abanythingcd')); app.get('/ab(cd)?e', (req, res) => res.send('Matches /abe or /abcde'));

Regular Expression Paths

JavaScriptapp.get(/a/, (req, res) => res.send('Path contains letter "a"')); app.get(/.*fly$/, (req, res) => res.send('Path ends with "fly"')); app.get(/^\/users\/\d+$/, (req, res) => res.send('Matches /users/123, not /users/abc')); app.get(/\.(jpg|png|gif)$/, (req, res) => res.send('Image file request'));

Route Path Examples

PathMatchesDoesn't Match
/users/users/users/123
/users/me/users/me/users/you
/ab?cd/acd, /abcd/abbcd
/ab+cd/abcd, /abbcd/acd
/ab*cd/abXcd, /abXYZcd/acd
/*Any path
/.*fly$//butterfly, /dragonfly/flyaway

4. Route Parameters

Route parameters are named URL segments used to capture values at specific positions in the URL.

Basic Route Parameters

JavaScriptapp.get('/users/:userId', (req, res) => { const userId = req.params.userId; res.send(`User ID: ${userId}`); }); app.get('/users/:userId/books/:bookId', (req, res) => { const { userId, bookId } = req.params; res.send(`User: ${userId}, Book: ${bookId}`); });

Parameter Naming Conventions

JavaScriptapp.get('/products/:productId/reviews/:reviewId', (req, res) => { const { productId, reviewId } = req.params; res.json({ productId, reviewId }); }); app.get('/api/v1/:api-version/users/:user_id', (req, res) => { console.log(req.params['api-version']); console.log(req.params.user_id); res.send('OK'); });

Parameter Validation Example

JavaScriptapp.get('/users/:id', (req, res) => { const userId = req.params.id; if (isNaN(userId)) { return res.status(400).json({ error: 'User ID must be a number' }); } const id = parseInt(userId); const user = findUserById(id); if (!user) { return res.status(404).json({ error: 'User not found' }); } res.json(user); });

Optional Parameters

JavaScriptapp.get('/users/:userId?', (req, res) => { const userId = req.params.userId; if (userId) { res.send(`Viewing user: ${userId}`); } else { res.send('Viewing all users'); } });

Regular Expression Parameters

JavaScriptapp.get('/users/:id(\\d+)', (req, res) => { res.send(`User ID: ${req.params.id}`); }); app.get('/files/:file(\\w+\\.\\w+)', (req, res) => { res.send(`File: ${req.params.file}`); });

5. Query Parameters

Query parameters are key-value pairs appended to the URL after a ? separator.

Basic Query Parameters

JavaScriptapp.get('/search', (req, res) => { const queryParams = req.query; res.json(queryParams); }); // /search?q=express → { q: 'express' } // /search?q=express&page=2 → { q: 'express', page: '2' }

Working with Query Parameters

JavaScriptapp.get('/api/users', (req, res) => { const { page, limit, sort, filter } = req.query; const pageNum = parseInt(page) || 1; const limitNum = parseInt(limit) || 10; const sortBy = sort || 'name'; res.json({ pagination: { page: pageNum, limit: limitNum, sort: sortBy }, filter: filter || 'none' }); });

Parsing Different Data Types

JavaScriptapp.get('/api/filter', (req, res) => { const price = parseFloat(req.query.price); const quantity = parseInt(req.query.quantity); const inStock = req.query.inStock === 'true'; const isActive = req.query.active === 'true'; const colors = req.query.colors ? req.query.colors.split(',') : []; const fromDate = req.query.from ? new Date(req.query.from) : null; res.json({ price, quantity, inStock, isActive, colors, fromDate }); });

Query Parameter Validation

JavaScriptapp.get('/api/validate-query', (req, res) => { const errors = []; if (!req.query.apiKey) errors.push('apiKey is required'); const limit = parseInt(req.query.limit); if (req.query.limit && isNaN(limit)) errors.push('limit must be a number'); if (errors.length) return res.status(400).json({ errors }); res.json({ message: 'Valid query parameters', data: req.query }); });

6. Combining Params and Query Strings

JavaScriptapp.get('/api/users/:userId/posts', (req, res) => { const { userId } = req.params; const { page = 1, limit = 10, sort = 'createdAt', includeComments = false } = req.query; res.json({ user: userId, posts: { pagination: { page, limit }, sort, includeComments: includeComments === 'true' } }); }); // /api/users/123/posts?page=2&limit=5&sort=title&includeComments=true

Building URLs Programmatically

JavaScriptapp.get('/api/build-url', (req, res) => { const baseUrl = `${req.protocol}://${req.get('host')}`; const searchParams = new URLSearchParams({ q: 'express tutorial', page: 2, limit: 10 }); const searchUrl = `${baseUrl}/api/search?${searchParams.toString()}`; res.json({ searchUrl, currentUrl: req.originalUrl }); });

7. Route Handlers

Multiple Callback Functions (Middleware)

JavaScriptconst validateUser = (req, res, next) => { if (!req.params.id) return res.status(400).json({ error: 'User ID required' }); next(); }; const checkPermissions = (req, res, next) => { req.hasPermission = true; next(); }; app.get('/users/:id', validateUser, checkPermissions, (req, res) => { res.json({ id: req.params.id, hasPermission: req.hasPermission }); });

Array of Callbacks

JavaScriptconst logRequest = (req, res, next) => { console.log(`${req.method} ${req.url}`); next(); }; const authenticate = (req, res, next) => { if (!req.headers.authorization) return res.status(401).json({ error: 'Unauthorized' }); req.user = { id: 1, name: 'John' }; next(); }; app.get('/profile', [logRequest, authenticate, (req, res) => res.json({ user: req.user })]);

Async Route Handlers

JavaScriptapp.get('/async-data', async (req, res) => { try { const data = await fetchDataFromDatabase(); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } });

8. Response Methods

JavaScriptapp.get('/response-methods', (req, res) => { // res.send(), res.json(), res.status(), res.redirect() // res.render(), res.sendFile(), res.download(), res.end() }); app.get('/chainable', (req, res) => { res .status(200) .type('application/json') .set('X-Custom-Header', 'Hello') .json({ message: 'Chainable methods are awesome!' }); });

Advanced Response Configuration

JavaScriptapp.get('/advanced-response', (req, res) => { res.set({ 'Content-Type': 'application/json', 'Cache-Control': 'no-cache' }); res.cookie('user', 'john', { maxAge: 900000, httpOnly: true }); if (!res.headersSent) res.json({ message: 'Response sent' }); });

9. app.route() for Chaining

JavaScriptapp.route('/books') .get((req, res) => res.json({ books: ['Book 1', 'Book 2'] })) .post((req, res) => res.status(201).json({ message: 'Book created' })) .put((req, res) => res.json({ message: 'Books updated' })) .delete((req, res) => res.json({ message: 'All books deleted' }));

Complete CRUD Example

JavaScriptapp.route('/api/articles/:id?') .get((req, res) => { const { id } = req.params; res.json(id ? { article: { id, title: `Article ${id}` } } : { articles: ['Article 1', 'Article 2'] }); }) .post((req, res) => res.status(201).json({ message: 'Article created' })) .put((req, res) => res.json({ message: `Article ${req.params.id} updated` })) .patch((req, res) => res.json({ message: `Article ${req.params.id} partially updated` })) .delete((req, res) => res.json({ message: `Article ${req.params.id} deleted` }));

10. Express Router

Basic Router Setup (routes/users.js)

JavaScriptconst express = require('express'); const router = express.Router(); router.get('/', (req, res) => res.json({ users: ['Alice', 'Bob', 'Charlie'] })); router.get('/:id', (req, res) => res.json({ user: { id: req.params.id } })); router.post('/', (req, res) => res.status(201).json({ message: 'User created' })); module.exports = router;

Main App with Routers

JavaScriptconst userRoutes = require('./routes/users'); app.use('/users', userRoutes); app.use('/api/v1', apiRoutes);

Router Parameters

JavaScriptrouter.param('companyId', (req, res, next, id) => { req.company = { id, name: `Company ${id}` }; next(); }); router.get('/:companyId', (req, res) => res.json(req.company));

11. Practical Examples

Complete REST API Example (Todos)

JavaScriptconst express = require('express'); const app = express(); app.use(express.json()); let todos = [ { id: 1, title: 'Learn Express', completed: false }, { id: 2, title: 'Build an API', completed: false } ]; app.get('/api/todos', (req, res) => { let result = [...todos]; if (req.query.completed !== undefined) { const completed = req.query.completed === 'true'; result = result.filter(todo => todo.completed === completed); } if (req.query.search) { const term = req.query.search.toLowerCase(); result = result.filter(todo => todo.title.toLowerCase().includes(term)); } const page = parseInt(req.query.page) || 1; const limit = parseInt(req.query.limit) || 10; const start = (page - 1) * limit; res.json({ data: result.slice(start, start + limit), pagination: { page, limit, total: result.length } }); }); app.get('/api/todos/:id', (req, res) => { const todo = todos.find(t => t.id === parseInt(req.params.id)); if (!todo) return res.status(404).json({ error: 'Todo not found' }); res.json(todo); }); app.post('/api/todos', (req, res) => { if (!req.body.title) return res.status(400).json({ error: 'Title is required' }); const newTodo = { id: todos.length + 1, title: req.body.title, completed: false }; todos.push(newTodo); res.status(201).json(newTodo); }); app.patch('/api/todos/:id', (req, res) => { const todo = todos.find(t => t.id === parseInt(req.params.id)); if (!todo) return res.status(404).json({ error: 'Todo not found' }); Object.assign(todo, req.body); res.json(todo); }); app.delete('/api/todos/:id', (req, res) => { const idx = todos.findIndex(t => t.id === parseInt(req.params.id)); if (idx === -1) return res.status(404).json({ error: 'Todo not found' }); todos.splice(idx, 1); res.status(204).send(); }); app.listen(3000);

Blog API with Route Params and Query Params

JavaScriptapp.get('/api/posts', (req, res) => { let posts = [...db.posts]; if (req.query.author) posts = posts.filter(p => p.authorId === parseInt(req.query.author)); if (req.query.published !== undefined) { posts = posts.filter(p => p.published === (req.query.published === 'true')); } const page = parseInt(req.query.page) || 1; const limit = parseInt(req.query.limit) || 10; const start = (page - 1) * limit; res.json({ data: posts.slice(start, start + limit), pagination: { page, limit, total: posts.length } }); }); app.get('/api/posts/:postId', (req, res) => { const post = db.posts.find(p => p.id === parseInt(req.params.postId)); if (!post) return res.status(404).json({ error: 'Post not found' }); post.views++; if (req.query.include_comments === 'true') { post.comments = db.comments.filter(c => c.postId === post.id); } res.json(post); });

12. Best Practices

Routing Best Practices

JavaScript// GOOD: Descriptive route names app.get('/api/users/:userId/orders/:orderId') // GOOD: Plural for collections app.get('/api/products') // GOOD: Version your API app.use('/api/v1', v1Routes)

Parameter Validation

JavaScriptconst validateId = (req, res, next) => { const id = parseInt(req.params.id); if (isNaN(id) || id <= 0) return res.status(400).json({ error: 'Invalid ID format' }); req.id = id; next(); }; const validatePagination = (req, res, next) => { const page = parseInt(req.query.page); const limit = parseInt(req.query.limit); if (req.query.limit && (isNaN(limit) || limit < 1 || limit > 100)) { return res.status(400).json({ error: 'Limit must be between 1 and 100' }); } req.pagination = { page: page || 1, limit: limit || 10 }; next(); };

Error Handling for Routes

JavaScriptapp.use((err, req, res, next) => { const status = err.statusCode || 500; res.status(status).json({ error: { message: err.message || 'Internal Server Error', status } }); });

Security Considerations

JavaScriptconst rateLimit = require('express-rate-limit'); const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }); app.use('/api/', apiLimiter); app.get('/api/users/:id', (req, res) => { if (!/^\d+$/.test(req.params.id)) { return res.status(400).json({ error: 'ID must be numeric' }); } });

Quick Reference Cheatsheet

TopicSyntax / Access
Route Parametersreq.params/users/:userId
Query Parametersreq.query/search?q=express&page=2
GET / POST / PUT / PATCH / DELETEapp.get(), app.post(), etc.
Responseres.send(), res.json(), res.status()
Route Paths'/users' // Exact match '/users/:id' // Parameter '/ab?cd' // Optional character '/ab+cd' // One or more '/ab*cd' // Wildcard /.*fly$/ // Regex