Search & Envelopes
Two finishing touches make collection endpoints pleasant to use: a search parameter for free-text queries, and a single envelope shape every collection shares.
Search vs structured filters
Section titled “Search vs structured filters”A ?q= parameter is for fuzzy, free-text search across a resource (“anything matching graphql”):
GET /articles?q=graphqlStructured filters (?status=published) answer precise questions; search answers vague ones. They compose — ?q=graphql&status=published searches within a filtered set. Keep them distinct: q is ranked, fuzzy, and may be backed by a search engine; filters are exact predicates.
A consistent envelope
Section titled “A consistent envelope”Every collection response should share the same wrapper, so clients write the paging/handling code once:
The cost of total counts
Section titled “The cost of total counts”Returning total is convenient but, on large tables, counting every matching row can be as expensive as the query itself. Options: cache the count, return an approximate count, or omit it entirely (cursor pagination often does — it offers next without a grand total).
| Advantages (Response Envelope) | Trade-offs |
|---|---|
| Metadata (pagination, total) travels in the same response | More verbose than a bare array |
| Consistent structure across every collection endpoint | Client has to unwrap data every time |
| Easy to add metadata later without a breaking change | A bare array is simpler for simple use cases |
Search results carry meta.total, meta.took in the envelope |
Common mistakes
Section titled “Common mistakes”Search that’s really just a filter Symptom:
GET /users?search=johnqueries every field withLIKE '%john%'- Slow on large tables and doesn’t rank by relevance
- Real search should use a full-text search engine (Elasticsearch, PostgreSQL FTS)
Inconsistent envelopes Symptom:
GET /usersreturns{ "data": [...], "meta": {...} }GET /productsreturns{ "items": [...], "pagination": {...} }- Define one standard envelope and use it on every collection endpoint
💡 Real-world examples
GitHub API:
- Search:
GET /search/repositories?q=rest+api+language:javascript&sort=stars- Envelope:
{ "total_count": 12345, "incomplete_results": false, "items": [...] }Stripe API:
- Consistent envelope:
{ "object": "list", "data": [...], "has_more": true, "url": "/charges" }- Every list endpoint uses the same shape