Introduction
In Express.js, query string parameters from GET requests are available on req.query — a parsed object where each key maps to its value from the URL. For a request to /search?q=nodejs&page=2, req.query.q returns "nodejs" and req.query.page returns "2". Express parses the query string automatically using the qs library, so no additional middleware is needed.
Basic Usage
1const express = require('express');
2const app = express();
3
4app.get('/search', (req, res) => {
5 const query = req.query.q;
6 const page = req.query.page;
7 res.json({ query, page });
8});
9
10app.listen(3000);
11
12// GET /search?q=nodejs&page=2
13// Response: { "query": "nodejs", "page": "2" }
All req.query values are strings. If the URL has no query string, req.query is an empty object {}.
Accessing Multiple Parameters
1app.get('/api/products', (req, res) => {
2 const { category, minPrice, maxPrice, sort, order } = req.query;
3
4 console.log(category); // "electronics"
5 console.log(minPrice); // "100"
6 console.log(maxPrice); // "500"
7 console.log(sort); // "price"
8 console.log(order); // "asc"
9
10 res.json({ category, minPrice, maxPrice, sort, order });
11});
12
13// GET /api/products?category=electronics&minPrice=100&maxPrice=500&sort=price&order=asc
Use destructuring for clean access to multiple parameters.
Type Conversion
Query string values are always strings. Convert them explicitly:
1app.get('/api/items', (req, res) => {
2 // String to number
3 const page = parseInt(req.query.page, 10) || 1;
4 const limit = parseInt(req.query.limit, 10) || 20;
5
6 // String to boolean
7 const active = req.query.active === 'true';
8
9 // String to float
10 const lat = parseFloat(req.query.lat) || 0;
11 const lng = parseFloat(req.query.lng) || 0;
12
13 res.json({ page, limit, active, lat, lng });
14});
15
16// GET /api/items?page=3&limit=50&active=true&lat=40.7128&lng=-74.0060
Default Values
1app.get('/api/users', (req, res) => {
2 const page = parseInt(req.query.page, 10) || 1;
3 const limit = Math.min(parseInt(req.query.limit, 10) || 20, 100);
4 const sort = req.query.sort || 'createdAt';
5 const order = req.query.order || 'desc';
6 const search = req.query.q || '';
7
8 // Use defaults when parameters are missing
9 res.json({ page, limit, sort, order, search });
10});
11
12// GET /api/users → { page: 1, limit: 20, sort: "createdAt", ... }
13// GET /api/users?page=3 → { page: 3, limit: 20, sort: "createdAt", ... }
Arrays in Query Strings
Express supports arrays via repeated keys or bracket notation:
1app.get('/api/filter', (req, res) => {
2 const tags = req.query.tags;
3 console.log(tags);
4 res.json({ tags });
5});
6
7// GET /api/filter?tags=node&tags=express&tags=mongodb
8// tags = ["node", "express", "mongodb"]
9
10// GET /api/filter?tags[]=node&tags[]=express
11// tags = ["node", "express"]
12
13// GET /api/filter?tags=node
14// tags = "node" (string, not array — only one value!)
When only one value is provided, req.query.tags is a string, not an array. Normalize it:
const tags = [].concat(req.query.tags || []);
// Always an array, even with 0 or 1 values
Nested Objects
Express uses the qs library by default, which supports nested objects:
1app.get('/api/search', (req, res) => {
2 const filters = req.query.filter;
3 console.log(filters);
4 res.json({ filters });
5});
6
7// GET /api/search?filter[status]=active&filter[role]=admin
8// filters = { status: "active", role: "admin" }
req.query vs req.params vs req.body
1// req.params — URL path parameters (from route definition)
2app.get('/users/:id', (req, res) => {
3 req.params.id; // "42" from /users/42
4});
5
6// req.query — query string parameters (after ?)
7app.get('/users', (req, res) => {
8 req.query.page; // "2" from /users?page=2
9});
10
11// req.body — POST/PUT request body (needs middleware)
12app.post('/users', express.json(), (req, res) => {
13 req.body.name; // From JSON body: { "name": "Alice" }
14});
Validation with express-validator
1const { query, validationResult } = require('express-validator');
2
3app.get('/api/search',
4 query('q').isString().trim().notEmpty().withMessage('Search query required'),
5 query('page').optional().isInt({ min: 1 }).toInt(),
6 query('limit').optional().isInt({ min: 1, max: 100 }).toInt(),
7 (req, res) => {
8 const errors = validationResult(req);
9 if (!errors.isEmpty()) {
10 return res.status(400).json({ errors: errors.array() });
11 }
12 const { q, page = 1, limit = 20 } = req.query;
13 res.json({ q, page, limit });
14 }
15);
URL Encoding
Special characters in query strings must be URL-encoded:
1// Client side
2const searchTerm = 'hello world & more';
3const url = `/search?q=${encodeURIComponent(searchTerm)}`;
4// /search?q=hello%20world%20%26%20more
5
6// Express decodes automatically
7app.get('/search', (req, res) => {
8 console.log(req.query.q); // "hello world & more"
9});
Common Pitfalls
Values are always strings: req.query.page is "2", not 2. Use parseInt() or parseFloat() for numbers, and === 'true' for booleans. Forgetting this causes bugs in comparisons and arithmetic.
Missing parameters are undefined: req.query.missing returns undefined, not null or empty string. Use || default or optional chaining req.query.key?.trim().
Single value vs array inconsistency: ?tag=one gives a string, ?tag=one&tag=two gives an array. Always normalize with [].concat(req.query.tag || []).
Query string size limits: Express defaults to a max query string length of 1000 characters. Override with app.set('query parser', qs => qs.parse(qs, { parameterLimit: 5000 })) if needed.
Prototype pollution: The qs parser protects against __proto__ injection by default in modern Express versions, but always validate and sanitize query parameters before using them in database queries.
Summary
Use req.query to access GET query string parameters — no middleware needed
All values are strings; convert explicitly with parseInt(), parseFloat(), or boolean comparison
Provide default values with || default for optional parameters
Arrays are supported via repeated keys (?a=1&a=2) or bracket notation (?a[]=1&a[]=2)
Use req.params for URL path segments, req.query for query strings, req.body for POST data
Validate query parameters with express-validator for production APIs