25. Pattern: Filter Collection by Identifier
The Filter Collection by Identifier pattern allows clients to retrieve a specific batch of resources from a collection endpoint using a comma-separated list of IDs in a query parameter (e.g., GET /products?ids=1,4,6,7,8).
Instead of forcing clients to issue multiple individual HTTP requests (GET /products/1, GET /products/4, etc.) or abusing POST endpoints for read-only batch fetches, this pattern provides an efficient, idempotent, and cacheable mechanism for fetching targeted subsets of a resource collection in a single round-trip.
25.1. Overview
Frontend and mobile client applications frequently need to display arbitrary sets of resources that are already known by ID—such as items in a shopping cart, a user’s favorited products, pinned dashboard widgets, or recent notification records.
Without a batch filtering pattern, client applications fall into the N+1 HTTP Request Problem, opening dozens of concurrent TCP connections or firing sequential requests to populate a single UI view.
The Filter Collection by Identifier pattern solves this cleanly within standard REST semantics:
- The resource collection endpoint (
/products) accepts a query parameter (typicallyidsorid). - The parameter accepts a comma-separated array of values (
ids=prd_101,prd_104,prd_108) or repeated query parameters (id=prd_101&id=prd_104). - The server performs a single batch query (e.g.,
WHERE id IN (...)) and returns an array containing the matching objects.
These operations:
- Eliminate N+1 round-trip latency overhead over mobile and high-latency networks
- Preserve standard HTTP caching semantics (
GETrequests remain safe and cacheable) - Keep API client integration code simple and maintainable
25.2. When to Use Filter Collection by Identifier
Use Filter Collection by Identifier when:
- Clients need to fetch a known list of non-sequential resource IDs in batch.
- e.g., Fetching product details for 10 items stored in a user’s browser-based shopping cart (
GET /products?ids=p1,p4,p9).
- e.g., Fetching product details for 10 items stored in a user’s browser-based shopping cart (
- Resolving the N+1 HTTP request problem.
- Replacing a loop of individual
GET /resources/{id}calls with a singleGET /resources?ids=...request.
- Replacing a loop of individual
- Fetching relational cross-references in bulk.
- e.g., An order object contains an array of user IDs (
[usr_12, usr_45]); the client fetches user profile details for all referenced actors in one call (GET /users?ids=usr_12,usr_45).
- e.g., An order object contains an array of user IDs (
- The batch size is small to moderate (typically under 50–100 IDs) and fits easily within standard HTTP URI character limits.
25.3. When NOT to Use Filter Collection by Identifier
Avoid Filter Collection by Identifier when:
- The list of requested IDs is extremely large (e.g., hundreds or thousands of IDs).
- Web servers, proxies, and CDNs typically enforce URI length limits (e.g., 2,048 to 8,192 bytes). Exceeding this limit results in
414 URI Too Longerrors. For massive batch lookups, use a Search / Batch Query endpoint with aPOSTbody instead.
- Web servers, proxies, and CDNs typically enforce URI length limits (e.g., 2,048 to 8,192 bytes). Exceeding this limit results in
- Clients need to fetch a single resource by ID.
- Fetching a single item should always use standard single-resource endpoints (
GET /products/prd_101).
- Fetching a single item should always use standard single-resource endpoints (
- Filtering by dynamic domain attributes rather than explicit keys.
- For filtering by property values (e.g.,
?category=electronics&price_lt=100), use standard query filtering rather than explicit ID list filtering.
- For filtering by property values (e.g.,
25.4. What the Pattern Looks Like
Below are HTTP request and response interaction flows demonstrating batch ID filtering, handling missing IDs, and handling URI length limits.
1. Standard Batch Request
The client requests three specific product records in a single call.
Request
GET /products?ids=prd_101,prd_104,prd_108 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi...
Response
The server returns a JSON array containing only the requested items.
HTTP/1.1 200 OK
Cache-Control: private, max-age=300
Content-Type: application/json
{
"items": [
{
"id": "prd_101",
"name": "Wireless Ergonomic Keyboard",
"price": 89.99,
"inStock": true
},
{
"id": "prd_104",
"name": "Precision Optical Mouse",
"price": 34.50,
"inStock": true
},
{
"id": "prd_108",
"name": "USB-C Multi-Port Hub",
"price": 45.00,
"inStock": false
}
],
"total": 3
}
2. Partial Match Handling (Non-existent IDs)
If a client requests three IDs (prd_101,prd_999,prd_108) and prd_999 has been deleted or does not exist, the API returns 200 OK with the matching records.
Request
GET /products?ids=prd_101,prd_999,prd_108 HTTP/1.1
Host: api.example.com
Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"items": [
{
"id": "prd_101",
"name": "Wireless Ergonomic Keyboard",
"price": 89.99
},
{
"id": "prd_108",
"name": "USB-C Multi-Port Hub",
"price": 45.00
}
],
"total": 2
}
(Note: Returning matching items with 200 OK is standard REST behavior for collection filtering. Do not fail the entire batch with 404 Not Found if at least one item is missing).
3. Exceeding URL Character Limits (414 URI Too Long)
If a client sends an excessively long query string exceeding server limits.
Response
HTTP/1.1 414 URI Too Long
Content-Type: application/problem+json
{
"type": "https://api.example.com/errors/uri-too-long",
"title": "URI Too Long",
"status": 414,
"detail": "The request URI exceeds the maximum limit of 2048 characters. Reduce the number of requested IDs or use batch query."
}
25.5. Anti-Patterns to Avoid
1. The N+1 HTTP fetch loop
// Anti-Pattern: Making individual network calls in a loop
const cartItems = ['prd_101', 'prd_104', 'prd_108'];
const products = await Promise.all(cartItems.map(id => fetch(`/products/${id}`)));
- Why it’s bad: Spawns multiple TCP/TLS connections, increases server connection overhead, exhausts browser socket pools, and severely degrades performance on mobile devices. Use
GET /products?ids=prd_101,prd_104,prd_108.
2. Using POST for simple, read-only batch queries
/* Anti-Pattern: Using POST for standard GET filtering */
POST /products/batch-get HTTP/1.1
Content-Type: application/json
{
"ids": ["prd_101", "prd_104", "prd_108"]
}
- Why it’s bad:
POSTis non-idempotent and non-cacheable by default in browsers, CDNs, and HTTP proxies. UsePOSTonly when payload size genuinely exceeds HTTP URI length thresholds (~2KB).
3. Returning 404 Not Found when a single batch item is missing
- If a client requests 10 IDs and 1 item no longer exists, returning
404 Not Founddestroys the utility of the entire request. Return200 OKwith the 9 matching records.
4. Failing to set max array length limits in OpenAPI
- Allowing an unbounded
idsparameter without definingmaxItemsin your OpenAPI spec allows clients to pass thousands of keys, leading to unoptimized databaseIN (...)queries and memory exhaustion.
25.6. OpenAPI Example
A complete OpenAPI 3.0.3 specification illustrating how to document a collection endpoint supporting batch filtering by comma-separated IDs using style: form and explode: false.
openapi: 3.0.3
info:
title: Product Catalog API - Filter Collection by Identifier Example
version: 1.0.0
servers:
- url: https://api.example.com
paths:
/products:
get:
summary: List products with optional batch ID filtering
description: Returns a collection of products. Supports filtering by a comma-separated list of specific product IDs.
tags: [Products]
parameters:
- in: query
name: ids
required: false
style: form
explode: false
schema:
type: array
maxItems: 50
items:
type: string
example: ["prd_101", "prd_104", "prd_108"]
description: Comma-separated list of product IDs to retrieve in batch (e.g. ?ids=prd_101,prd_104,prd_108). Maximum 50 IDs.
responses:
'200':
description: Collection of products matching the criteria
content:
application/json:
schema:
$ref: '#/components/schemas/ProductCollectionResponse'
'414':
description: URI Too Long - Requested query string exceeded server length limits
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetails'
components:
schemas:
ProductCollectionResponse:
type: object
required:
- items
- total
properties:
items:
type: array
items:
$ref: '#/components/schemas/Product'
total:
type: integer
example: 3
Product:
type: object
required:
- id
- name
- price
properties:
id:
type: string
example: prd_101
name:
type: string
example: Wireless Ergonomic Keyboard
price:
type: number
format: float
example: 89.99
inStock:
type: boolean
example: true
ProblemDetails:
type: object
properties:
type:
type: string
example: "https://api.example.com/errors/uri-too-long"
title:
type: string
example: "URI Too Long"
status:
type: integer
example: 414
detail:
type: string
example: "The request URI exceeds the maximum limit."
25.7. Visualizing Filter Collection by Identifier (Mermaid Diagram)
sequenceDiagram
autonumber
actor Client as Client App (Cart View)
participant API as API Server / Gateway
participant DB as Database
Note over Client,DB: Problem: N+1 HTTP Requests (Avoided)
Note over Client: Client holds cart keys: ["prd_101", "prd_104", "prd_108"]
Note over Client,DB: Solution: Single Batch Collection Filter
Client->>API: GET /products?ids=prd_101,prd_104,prd_108
API->>API: Parse query param 'ids' -> Array["prd_101", "prd_104", "prd_108"]
API->>DB: SELECT * FROM products WHERE id IN ('prd_101', 'prd_104', 'prd_108')
DB-->>API: Return matching rows (3 records)
API-->>Client: 200 OK { items: [...3 products], total: 3 }
Note over Client: Client renders full cart view in 1 network round-trip!