When designing an HTTP API, how do you decide between exposing a nested path like `/orders/{orderId}/items` and a top-level collection with a filter like `/items?orderId={orderId}`? What does each choice commit you to?
answer
- composition → nest; association → top-level + filter
- child ids unique only within parent → must nest
- cross-parent query → needs top-level collection
- nesting gives authorization scope for free
- one canonical URI; nested item route redirects or 404s on mismatch
basics
~20 sNest when the child is owned by the parent, has no meaning or identity outside it, and is always accessed through it. Use a top-level collection with a filter when the child is independently identifiable and queried across parents. Many APIs do both: nested for creation and scoped listing, a canonical top-level URI for the item itself.
solid answer
~50 sThe test is **ownership and independent identity**. Nest (`/orders/{id}/items`) when: the child cannot exist without the parent, its lifecycle is bound to the parent's (delete the order, the items go), it is only ever listed within one parent, and the parent supplies the authorization scope for free. Go top-level with filters (`/items?orderId=...`) when: the child has a stable global id, callers need cross-parent queries ("all items with SKU X, any order"), or it belongs to more than one parent. In practice a hybrid is common and correct: `POST /orders/42/items` and `GET /orders/42/items` for the scoped operations, but the created item's canonical `Location` is `/order-items/9001`, and `GET`/`PATCH`/`DELETE` on that URI work directly. That gives ergonomic scoping without pretending the child has no identity. What you should avoid is two *equally canonical* URIs for the same item — pick one, and make the other a redirect or a documented alias.
code
http · 11 linesPOST /orders/42/items HTTP/1.1
Content-Type: application/json
{"sku":"ABC","qty":2}
201 Created
Location: /order-items/9001
GET /order-items?sku=ABC&limit=50 HTTP/1.1
200 OKgo deeper
Give the ownership rule: nest when the child belongs to the parent and cannot exist alone; use a filter when it stands on its own.
Add identifier scope, cross-parent queries, and the hybrid pattern where creation is nested but the item has a canonical top-level URI.
Lead with the authorization benefit of scoped paths and the mismatch-must-404 rule, plus keeping one query implementation behind both routes.
Treat URI hierarchy as a durable domain claim: what it commits you to when relationships change, how reparenting breaks nested URIs, and how to keep the canonical-URI rule enforceable across teams.
## The question behind the question URI hierarchy is a claim about the domain. `/orders/42/items` says: items live inside orders. `/items?orderId=42` says: items are their own thing, and an order is one attribute you can filter by. Both are valid REST; picking the wrong one shows up later as awkward endpoints and duplicated logic. ## Signals for nesting **Existence dependency.** The child is meaningless without the parent. An order line has no life of its own; deleting the order deletes it. A composition, in UML terms, not an association. **Access is always scoped.** Nobody asks "give me line item 9001" without knowing the order. If every real query carries the parent, the parent belongs in the path. **Authorization falls out of the path.** `/orders/42/items` lets you resolve and authorize order 42 once, then everything below inherits that scope. This is a real security benefit: it makes it structurally hard to return a child belonging to someone else's parent, because the query is already constrained. With `/items/9001` you must remember to check that item 9001's order belongs to the caller — the classic broken-object-level-authorization bug. **Identifier scope.** If child ids are only unique *within* the parent (line number 1, 2, 3 per order), nesting is not a style choice, it is required: `/orders/42/items/1` is unambiguous and `/items/1` is not. **Discoverability.** The nested URI reads as documentation and is trivially derivable from the parent representation. ## Signals for top-level plus filter **Independent identity.** The child has a globally unique id that callers store, log, and reference. Forcing them to remember the parent to build a URL is friction — and worse, if a child can be *moved* between parents its URI would change, which breaks bookmarks, caches, and stored references. **Cross-parent queries.** "All items shipped late", "all comments by user X across every post". A nested design cannot express this without inventing a second, unnested endpoint anyway. **Multiple parents.** A photo in several albums has no single owner to nest under. Nesting forces you to elect an arbitrary primary parent. **Uniform querying.** One top-level collection means one place for filtering, sorting and pagination logic, one set of tests, one cache story. N nested collections means N. ## The hybrid, and how to keep it honest Most mature APIs converge on: nested endpoints for **creation and scoped listing**, a top-level canonical URI for the **item itself**. ``` POST /orders/42/items → 201, Location: /order-items/9001 GET /orders/42/items → the order's items, paginated GET /order-items/9001 → the canonical item PATCH /order-items/9001 GET /order-items?sku=ABC → cross-order query ``` This works because `POST` to a nested collection reads naturally (the parent supplies context for creation) while the item still has one address. The rule that keeps it clean: **one canonical URI per resource.** If you also expose `GET /orders/42/items/9001`, either make it a `301`/`308` to the canonical form, or accept that you now maintain two code paths, two cache entries and two ETags for one thing — and that a client can hold a URI that becomes wrong if the item is reparented. Also keep the nested route *consistent*: `/orders/42/items/9001` must 404 if item 9001 belongs to order 7. Returning it anyway (because the handler only looked up the item id and ignored the parent segment) is a real and common bug, and it is exactly the authorization hole nesting was supposed to prevent. ## Practical guidance Start from the domain: is this composition or association? Composition nests. Association gets a top-level collection with a filter, and possibly an association resource of its own. Then check the query patterns: if you can name a real, needed query that crosses parents, you will need the top-level collection regardless, so build it first and treat the nested route as a scoped convenience over it — same handler, extra predicate, not a parallel implementation.
- A request arrives for `/orders/42/items/9001`, but item 9001 belongs to order 7. What should the API return?`404 Not Found`. The URI names a resource that does not exist — there is no item 9001 under order 42. Returning the item because the id matched ignores the parent segment and is a broken-object-level-authorization bug: it lets a caller who can read order 42 read another customer's line item by guessing ids.
- You need both `GET /orders/42/items` and `GET /items?orderId=42`. Is that duplication a problem?Only if they are two implementations. Route both to the same query handler, with the nested form injecting `orderId` from the path, so filtering, sorting, pagination and authorization stay in one place. The remaining question is which URI is canonical for links and caching — document that, and prefer emitting only the canonical one in responses.
- How deep would you let nesting go?Two levels of collection nesting is the practical ceiling: `/parents/{id}/children`. Beyond that the URI encodes a traversal path rather than an identity, becomes brittle to reparenting, and forces clients to know the whole ancestry. Deeper relationships are better expressed as top-level resources with filters or links in the representation.
A chapter belongs inside a book (nest it). A person can be an author of many books, so people get their own directory and you filter (top-level).
saying these in an interview costs you the question
- Nesting purely because the database has a foreign key, with no ownership or lifecycle dependency
- Ignoring the parent path segment in the handler and looking the child up by id alone
- Exposing two equally canonical URIs for the same item with no redirect or documented preference
- Claiming nested URIs are 'more RESTful' — REST says nothing about path shape
- Nesting a child that can be reparented, so its URI changes when it moves