URL Anatomy
┌─────────────────────────────────────────────────────────────┐
│ https://api.example.com/users/42/posts?page=2&limit=10#top │
│ └────┬────┘ └──┬──┘ └──┬──┘ │ └──────┬──────┘ └──┬──┘ │
│ host path param │ query string fragment │
│ │ │
│ route params not sent│
│ to server │
└─────────────────────────────────────────────────────────────┘
Express gives you access to:
- Route parameters via
req.params - Query string via
req.query
Route Parameters
Route parameters are named URL segments prefixed with :. They capture dynamic values from the URL.
Basic Syntax
app.get('/users/:userId', (req, res) => {
res.json({ userId: req.params.userId });
});
// GET /users/42 → { userId: "42" }
// GET /users/john → { userId: "john" }
Multiple Parameters
app.get('/users/:userId/posts/:postId', (req, res) => {
res.json({
userId: req.params.userId,
postId: req.params.postId
});
});
// GET /users/42/posts/101 → { userId: "42", postId: "101" }
Parameter with Hyphens and Dots
// Route: /flights/:from-:to
app.get('/flights/:from-:to', (req, res) => {
res.json(req.params);
});
// GET /flights/LAX-SFO → { from: "LAX", to: "SFO" }
// Route: /files/:filename.:ext
app.get('/files/:filename.:ext', (req, res) => {
res.json(req.params);
});
// GET /files/report.pdf → { filename: "report", ext: "pdf" }
Optional Parameters
Use ? to make a parameter optional:
app.get('/users/:userId?', (req, res) => {
if (req.params.userId) {
res.json({ userId: req.params.userId });
} else {
res.json({ message: 'All users' });
}
});
// GET /users → { message: "All users" }
// GET /users/42 → { userId: "42" }
Regular Expression in Parameters
Add constraints with regex patterns:
// Only numeric IDs
app.get('/users/:userId(\\d+)', (req, res) => {
res.json({ userId: parseInt(req.params.userId) });
});
// GET /users/42 → { userId: 42 }
// GET /users/abc → 404 (no match)
Common Patterns with Route Params
// Blog post by slug
app.get('/blog/:slug', (req, res) => {
const post = findPostBySlug(req.params.slug);
if (!post) return res.status(404).send('Post not found');
res.json(post);
});
// Product by category and ID
app.get('/products/:category/:productId', (req, res) => {
const { category, productId } = req.params;
res.json({ category, productId });
});
// API versioning
app.get('/api/v:version/users', (req, res) => {
res.json({ version: req.params.version, users: [] });
});
// GET /api/v2/users → { version: "2", users: [] }
Query Strings
Query strings come after ? in the URL as key=value pairs separated by &.
Express parses them automatically into req.query:
app.get('/search', (req, res) => {
console.log(req.query);
res.json(req.query);
});
// GET /search?q=express&page=2&sort=desc
// → { q: "express", page: "2", sort: "desc" }
Arrays in Query Strings
// GET /products?tags=node&tags=express&tags=api
app.get('/products', (req, res) => {
const tags = req.query.tags; // ["node", "express", "api"]
res.json({ tags });
});
Nested Objects in Query Strings
// GET /filter?user[name]=John&user[age]=30
app.get('/filter', (req, res) => {
console.log(req.query);
// → { user: { name: "John", age: "30" } }
res.json(req.query);
});
Boolean and Numeric Values
Query string values are always strings. Cast them explicitly:
app.get('/products', (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const active = req.query.active === 'true';
res.json({ page, limit, active });
});
Practical Full Example
app.get('/api/products', (req, res) => {
const {
category,
minPrice,
maxPrice,
sort = 'name',
order = 'asc',
page = '1',
limit = '10',
search
} = req.query;
let filtered = [...products];
// Filter by category
if (category) {
filtered = filtered.filter(p => p.category === category);
}
// Filter by price range
if (minPrice) {
filtered = filtered.filter(p => p.price >= parseFloat(minPrice));
}
if (maxPrice) {
filtered = filtered.filter(p => p.price <= parseFloat(maxPrice));
}
// Text search
if (search) {
const q = search.toLowerCase();
filtered = filtered.filter(p =>
p.name.toLowerCase().includes(q) ||
p.description.toLowerCase().includes(q)
);
}
// Sort
filtered.sort((a, b) => {
const valA = a[sort];
const valB = b[sort];
if (typeof valA === 'string') {
return order === 'desc'
? valB.localeCompare(valA)
: valA.localeCompare(valB);
}
return order === 'desc' ? valB - valA : valA - valB;
});
// Pagination
const pageNum = parseInt(page);
const limitNum = parseInt(limit);
const start = (pageNum - 1) * limitNum;
const paginated = filtered.slice(start, start + limitNum);
res.json({
data: paginated,
pagination: {
page: pageNum,
limit: limitNum,
total: filtered.length,
totalPages: Math.ceil(filtered.length / limitNum)
}
});
});
Route Params vs Query Strings
| Aspect | Route Params | Query Strings |
|---|---|---|
| Purpose | Identify a specific resource | Filter, sort, or paginate |
| Example | /users/42 | /users?role=admin&page=2 |
| Required? | Usually required | Usually optional |
| Best for | Resource IDs, slugs | Search, filtering, metadata |
Middleware with Route Parameters
// Middleware that runs only when :userId is present
app.param('userId', (req, res, next, id) => {
const user = users.find(u => u.id === parseInt(id));
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
req.user = user;
next();
});
app.get('/users/:userId', (req, res) => {
// req.user is already populated by the param middleware
res.json(req.user);
});
app.get('/users/:userId/posts', (req, res) => {
// req.user is also available here
const userPosts = posts.filter(p => p.userId === req.user.id);
res.json(userPosts);
});
Validation and Sanitization
Always validate and sanitize URL parameters:
app.get('/users/:userId', (req, res) => {
const id = parseInt(req.params.userId);
// Validate
if (isNaN(id) || id < 1) {
return res.status(400).json({ error: 'Invalid user ID' });
}
// Sanitize string params
const name = req.query.name ? req.query.name.trim() : '';
const page = Math.max(1, parseInt(req.query.page) || 1);
// Proceed safely
res.json({ id, name, page });
});
Key Takeaways
req.paramscaptures route parameters - dynamic URL segments like:idreq.queryparses the query string - key-value pairs after?- Route params identify specific resources; query strings filter/sort/paginate
- Query values are always strings - cast to numbers/booleans as needed
- Use
app.param()to run middleware for specific route parameters - Always validate and sanitize both params and query values