REST API Query Parameters: Production Standards and Best Practices
Guvantic Engineering
Guvantic Technical Publication
Complete guide to REST API query parameters: filtering, sorting, pagination, field selection, validation, and OpenAPI documentation. Build APIs that developers love to use.
The Problem: Inconsistent APIs
Developers hate inconsistent APIs. Different endpoints use different parameter names, unexpected pagination behavior, and unclear filtering options.
Filtering Best Practices
Simple Equality Filters
// GET /api/users?status=active&role=admin
app.get("/api/users", async (req, res) => {
const { status, role } = req.query;
const where: Prisma.UserWhereInput = {};
if (status) where.status = status;
if (role) where.role = role;
const users = await prisma.user.findMany({ where });
res.json(users);
});
Range Filters
// GET /api/orders?created_after=2026-01-01&created_before=2026-12-31&amount_gte=100&amount_lte=500
function parseRangeFilters(query: any) {
const filters: any = {};
// Date ranges
if (query.created_after || query.created_before) {
filters.createdAt = {};
if (query.created_after) filters.createdAt.gte = new Date(query.created_after);
if (query.created_before) filters.createdAt.lte = new Date(query.created_before);
}
// Numeric ranges
if (query.amount_gte || query.amount_lte) {
filters.amount = {};
if (query.amount_gte) filters.amount.gte = parseFloat(query.amount_gte);
if (query.amount_lte) filters.amount.lte = parseFloat(query.amount_lte);
}
return filters;
}
Search (Full-Text)
// GET /api/products?q=wireless+headphones
app.get("/api/products", async (req, res) => {
const { q } = req.query;
if (q) {
const products = await prisma.product.findMany({
where: {
OR: [
{ name: { contains: q as string, mode: "insensitive" } },
{ description: { contains: q as string, mode: "insensitive" } },
],
}
});
return res.json(products);
}
});
Pagination Patterns
Offset Pagination
// GET /api/users?page=2&limit=20
interface PaginatedResponse<T> {
data: T[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}
app.get("/api/users", async (req, res) => {
const page = parseInt(req.query.page as string) || 1;
const limit = Math.min(
parseInt(req.query.limit as string) || 20,
100 // Maximum limit
);
const skip = (page - 1) * limit;
const [users, total] = await Promise.all([
prisma.user.findMany({
skip,
take: limit,
}),
prisma.user.count(),
]);
res.json({
data: users,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
});
});
Cursor Pagination (Recommended for large datasets)
// GET /api/users?cursor=eyJpZCI6MTIzfQ&limit=20
app.get("/api/users-cursor", async (req, res) => {
const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
const cursor = req.query.cursor as string;
let cursorObj: { id: number } | undefined;
if (cursor) {
cursorObj = JSON.parse(Buffer.from(cursor, "base64").toString());
}
const users = await prisma.user.findMany({
take: limit + 1, // Fetch one extra to check if there are more
cursor: cursorObj ? { id: cursorObj.id } : undefined,
skip: cursorObj ? 1 : 0, // Skip the cursor item itself
orderBy: { id: "asc" },
});
const hasMore = users.length > limit;
const data = hasMore ? users.slice(0, -1) : users;
let nextCursor: string | undefined;
if (hasMore && data.length > 0) {
const lastItem = data[data.length - 1];
const cursorObj = { id: lastItem.id };
nextCursor = Buffer.from(JSON.stringify(cursorObj)).toString("base64");
}
res.json({
data,
meta: {
limit,
hasMore,
nextCursor,
},
});
});
Sorting and Field Selection
Sorting
// GET /api/users?sort=created_at:desc
// GET /api/users?sort=-created_at
// GET /api/users?sort=name:asc,created_at:desc
function parseSort(sort: string) {
if (!sort) return {};
const sortFields: Record<string, "asc" | "desc"> = {};
const parts = sort.split(",");
for (const part of parts) {
const [field, direction] = part.split(":");
if (direction === "desc" || part.startsWith("-")) {
const cleanField = part.startsWith("-") ? part.slice(1) : field;
sortFields[cleanField] = "desc";
} else {
sortFields[field] = "asc";
}
}
return sortFields;
}
app.get("/api/users", async (req, res) => {
const sort = parseSort(req.query.sort as string);
const users = await prisma.user.findMany({ orderBy: sort });
res.json(users);
});
Field Selection (Sparse Fieldsets)
// GET /api/users?fields=id,name,email
app.get("/api/users", async (req, res) => {
const fields = (req.query.fields as string)?.split(",") || [];
// Validate fields against whitelist
const allowedFields = ["id", "name", "email", "createdAt"];
const selectedFields = fields.filter(f => allowedFields.includes(f));
const users = await prisma.user.findMany({
select: selectedFields.reduce((acc, field) => ({ ...acc, [field]: true }), {}),
});
res.json(users);
});
Validation and Security
Validation with Zod
import { z } from "zod";
const QuerySchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(20),
status: z.enum(["active", "inactive", "pending"]).optional(),
sort: z.string().regex(/^[a-zA-Z_]+:(asc|desc)$/).optional(),
fields: z.string().optional(),
});
app.get("/api/users", async (req, res) => {
try {
const validated = QuerySchema.parse(req.query);
// Use validated parameters...
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
error: "Invalid query parameters",
details: error.errors,
});
}
throw error;
}
});
Security Checklist:
OpenAPI Documentation
openapi: 3.0.0
paths:
/api/users:
get:
summary: List users
parameters:
- name: page
in: query
schema:
type: integer
minimum: 1
default: 1
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20
- name: status
in: query
schema:
type: string
enum: [active, inactive, pending]
- name: sort
in: query
description: "Sort by field (e.g. created_at:desc)"
schema:
type: string
- name: fields
in: query
description: "Comma-separated list of fields to return"
schema:
type: string
responses:
"200":
description: Successful response
"400":
description: Invalid query parameters
Guvantic Engineering
Engineering & System Architecture Division
Engineering teams at Guvantic deliver mission-critical software, high-concurrency microservices, and international organic positioning strategies for fast-growing companies in USA, UK, and India.