where with comparison operators, whereIn, orderBy,
search, and paginate.
What You’ll Build
The Product Model
Note: Full-text search requires a text index on the fields you want to search (typicallynameanddescription). Create it once withdb.products.createIndex({ name: 'text' }).
Parsing the Query Safely
The trick is to translate raw query params into a typed shape before touching Esix. A zod schema gives you that translation, validation, and the TypeScript type all in one definition:z.enum(['price', 'name', 'createdAt']) is an
allowlist of sortable fields (so callers can’t sort by passwordHash), and
max(100) is an upper bound on perPage (so they can’t ask for a million
records). z.coerce.number() handles the fact that query string values arrive
as strings.
Building the Query
Apply each filter conditionally. Esix’s fluent interface keeps this readable:searchis the first link in the chain. When the request includes a search term, it kicks off the query; otherwise you start with awhere. Both return aQueryBuilder, so the rest of the chain doesn’t care.whereInhandles array params naturally. The schema normalises both?category=lampsand?category=lamps&category=desksinto a string array, which is exactly whatwhereInwants.- Comparison operators handle the price range.
where('price', '>=', minPrice)and the matching<=give you a numeric between without leaving the query builder.
Trying It Out
Pattern Notes
- Always parse before you query.
ProductFiltersSchema.parse(...)is the cleanest way to keep injection-style bugs out of your handlers — and you get the inferred TypeScript type as a bonus. - Allowlist sortable fields.
z.enum([...])is the smallest, highest-value piece of validation you can add to a listing endpoint. - Sensible defaults beat optional everything. zod’s
.default(...)keeps every filter sensible when the client sends nothing — your tests and your front-end will thank you.
What’s Next
- Aggregation Dashboard — apply the same chained-query pattern to summary metrics.
- Retrieving Models — the full reference for
where,whereIn,orderBy, andsearch. - Pagination — more on
paginateresponse shape.