API Resource Names, Relationships, and Collections
Model API URLs around stable resources and relationships, with clear collection behavior that clients can navigate without learning server internals.
Good API resource design gives clients stable routes that match domain concepts, not database tables. This guide explains collection and item naming, when to nest relationships, whether to embed or link related data, and how bounded responses and deterministic cursor pagination keep navigation predictable. It also covers authorization checks, identifier risks, and practical trade-offs so teams can publish routes clients can use safely as services evolve.
API Resource Names, Relationships, and Collections
Introduction
Resource-oriented APIs give clients stable names for the things they read and change. A route such as /customers/cus_18/orders reads as a collection of orders related to one customer; /orders/ord_92 identifies one order. Consistent names let developers predict routes and understand how records connect without knowing which tables or services store them.
Resource modeling is a design exercise, not a database mirror. The REST API design guide discusses broader REST conventions; this post focuses on names, relationships, and collection endpoints.
Collections need a stable shape
A collection endpoint should define its default ordering, page behavior, empty result behavior, and whether it returns a bare array or an envelope. Envelopes make room for pagination metadata and links:
{
"items": [{ "id": "ord_92", "status": "shipped" }],
"nextCursor": "eyJpZCI6Im9yZF85MiJ9",
"hasMore": true
}
Do not expose internal join structures just because the backing database has them. Return the representation that clients need, with field names that remain meaningful outside the service. If a relationship is private or expensive to load, expose a link or dedicated route instead of silently embedding an unbounded tree.
Relationship choices
graph LR
Customer -->|has many| Order
Order -->|contains| LineItem
Product -->|referenced by| LineItem
Customer -->|owns| Address
This small domain has both ownership relationships and references. An order can contain snapshots of product name and price while still linking to the current product record. That choice preserves historical receipts when a product’s catalog details later change.
When to use and when not to use
Use resource paths when clients operate on identifiable domain entities and standard HTTP verbs fit the operations. Nest a child collection when the parent context is meaningful for access control or query scope. Do not nest every relationship, expose database table names, or create routes that differ only because of one client team’s terminology. For command-heavy workflows, a named action endpoint can be clearer than pretending the command is a resource replacement.
Trade-off Analysis
| Modeling choice | Strength | Cost |
|---|---|---|
| Shallow nested collection | Parent scope is explicit | Can duplicate access paths |
| Link to related resource | Separates ownership and lookup | Client may need another request |
| Embedded related object | Fewer round trips | Can become stale or oversized |
| Bare array response | Minimal representation | No obvious place for pagination metadata |
Implementation snippet
A route handler should enforce parent scope and return a bounded collection, rather than trusting a client-supplied relation:
async function listCustomerOrders(customerId: string, user: User) {
await authorizeCustomerRead(user, customerId);
const page = await orderRepository.listByCustomer(customerId, { limit: 50 });
return {
items: page.items.map(toOrderSummary),
nextCursor: page.nextCursor,
hasMore: page.nextCursor !== null,
};
}
The mapping function is a useful place to keep storage fields and API fields separate. The endpoint contract should also state whether orders are newest first and how cursor tokens expire.
Pagination and stable ordering
Offset pagination (page and limit) is easy to understand and lets clients jump to a page, but large offsets can become expensive. Inserts or deletes between requests can also shift rows, causing a client to see duplicates or miss items.
Cursor pagination works well for large or changing collections. The server returns an opaque cursor for the next page, based on a stable sort key. Use a deterministic order with a unique tie-breaker, such as (created_at, id), so records with the same timestamp still have a defined order. The cursor is a navigation token, not an authorization credential: authenticate and scope every page request on the server. Document whether the cursor expires and whether pages reflect a live collection or a consistent snapshot.
Production failure scenarios and mitigations
A nested collection leaks another customer’s records because the service filters by child ID but not parent ownership; enforce authorization and parent-child association in the query. A response embeds every line item and product history, causing huge payloads; return summaries and offer a detail route. A route rename breaks old mobile clients; keep an alias or versioned migration period. Sequential identifiers invite enumeration; use access checks regardless of ID format and consider opaque IDs where appropriate.
Observability checklist
- Track route templates, collection size, payload bytes, and query duration.
- Monitor empty-page and pagination rates to catch broken cursor behavior.
- Log authorization denials by resource type without leaking private identifiers.
- Watch old route usage during deprecation before removing an alias.
Security notes and pitfalls
Never treat an unguessable ID as authorization. Check access to the parent and each child, constrain list queries by tenant, and cap collection sizes. Avoid exposing internal fields such as storage keys or administrative flags through generic serialization. Common pitfalls include inconsistent pluralization, excessive path nesting, giant embedded graphs, unstable default ordering, and assuming relationship names match database foreign keys.
Recap checklist
- Do paths use stable domain nouns and a consistent collection convention?
- Are nested routes shallow and constrained by authorization?
- Are response size, pagination, default ordering, and relationship representation documented?
Quick Recap Checklist
- Name resources with stable domain terms and use a consistent collection convention.
- Keep list responses bounded and define pagination and ordering.
- Use nested paths only when the parent meaningfully scopes the child collection.
- Embed small stable summaries; link separately fetched or independently permissioned resources.
Interview Questions
Storage schemas change for internal reasons and often expose implementation details. An API model should reflect stable concepts clients use, allowing the database to evolve independently.
It is useful when the parent meaningfully scopes the child collection, such as listing one customer's orders. Keep nesting shallow and verify the relationship and caller's access on the server.
Embed a small, stable summary when it saves a meaningful round trip. Link or provide a separate endpoint for large, frequently changing, or independently permissioned records.
A unique tie-breaker gives records with the same primary sort value a consistent order. Without it, an item can move between pages even when the collection has not otherwise changed.
A cursor continues from a stable sort position, so inserts before that position are less likely to shift every later page as they do with offsets. The API still needs to document whether it serves live data or a snapshot.
The server may change how it encodes the sort position or continuation state. Clients should pass the token back unchanged and must not treat it as an authorization credential.
Return a successful response with an empty collection and consistent pagination metadata. A missing collection resource is a different condition from a collection that currently has no matching records.
IDs can leak through logs, links, or other responses, and opacity only makes guessing harder. The server must check that the caller is allowed to access the requested resource on every operation.
An envelope provides a place for pagination metadata, links, or collection-level information. If the API has no such needs, a simpler representation may be easier for clients to consume.
Further Reading
- Google API Improvement Proposal 158: Pagination — Page tokens, bounded page sizes, and collection pagination behavior.
- RFC 8288: Web Linking — A standard model for links between resources.
- RESTful API Design — Broader conventions for HTTP methods, resource modeling, and API evolution.
- Filtering, Sorting, and Pagination — Practical options for collection queries and page navigation.
Conclusion
Good resource paths make an API easier to explore, but the resource model should follow client needs rather than database layout. Keep collections bounded and relationships explicit.
Category
Related Posts
API Request, Response, and Error Shapes Clients Can Trust
Design consistent API request and response envelopes, validation errors, and problem details so client developers can handle success and failure reliably.
HTTP Methods, Status Codes, and Headers in API Design
Choose HTTP methods that match the operation, return status codes clients can act on, and use headers for metadata, caching, and safe retries.
API Filtering, Sorting, Pagination, and Field Selection
Build collection endpoints that let clients narrow, order, page through, and shape results without slow queries or unstable response behavior.