| |

REST API 02 ๐ŸŒ Resources, URIs, and Collection Naming Conventions

โฑ๏ธ Estimated read: 11 min | |

A REST API’s URI structure is not cosmetic. It is the contract that clients navigate, the surface that documentation describes, and the layer where the difference between a well-designed API and an inconsistent one becomes visible. The uniform interface constraint from the previous chapter requires that every resource be identified by a URI, but it does not dictate how those URIs should look. That is where naming conventions come in: a consistent set of rules for naming resources, collections, and the relationships between them.

The conventions are not arbitrary either. They follow from the resource model. A resource is any information that can be namedโ€”a user, an order, a collection of products. A collection is a resource that is a set of other resources. A sub-resource is a resource that exists only in the context of another resource. The URI structure reflects this model: collections are plural nouns, individual resources are identified by an identifier after the collection, and sub-resources are nested under their parent . The noun/verb distinction is the most fundamental rule: URIs identify things, HTTP methods perform actions.

Key point: REST URIs use plural nouns for collections (/users), append identifiers for individual resources (/users/42), and nest sub-resources under their parent (/users/42/orders); they do not contain verbs (use HTTP methods instead), and they use query parameters for filtering, sorting, and pagination rather than path segments.


Why URI conventions matter

The predictability problem. A client developer who has learned that GET /users returns a list and GET /users/42 returns one user can guess that GET /orders returns orders and GET /orders/7 returns one order. The structure is predictable. When an API mixes /getUser, /users/list, and /user?id=42, every endpoint must be looked up individually, and the mental model never forms. Consistency is not a luxuryโ€”it is the property that makes an API usable without reading the entire documentation .

The resource model problem. The URI structure should mirror the resource model. If orders belong to users, the URI should reflect that: /users/42/orders rather than /orders?user_id=42. Both work, but the nested structure communicates the relationship and makes the access pattern visible. The nested form answers “what are this user’s orders?” The query form answers “which orders have this user ID?” These are different questions, and the URI should express which one is being asked .

The caching problem. Caches key on the URI. If GET /users/42 returns the same representation regardless of who asks, it is cacheable. If the URI includes session-specific query parameters (/users/42?session=abc), the cache hit rate drops to near zero. Clean, stable URIs are a precondition for the cacheable constraint from the previous chapter.

The versioning problem. URI structure is part of the API’s public contract. Changing a collection name from /users to /accounts is a breaking change for every client. Established conventionsโ€”plural nouns, stable identifiers, nested resourcesโ€”reduce the likelihood of a redesign because the structure follows the domain model rather than a transient preference.


a. Plural nouns for collections, identifiers for individual resources

A collection is a set of resources. Its URI uses a plural noun: /users, /orders, /products. An individual resource is identified by appending its identifier to the collection URI: /users/42, /orders/7, /products/abc-123 .

GET    /users          # List all users
POST   /users          # Create a new user
GET    /users/42       # Retrieve user 42
PUT    /users/42       # Replace user 42
PATCH  /users/42       # Partially update user 42
DELETE /users/42       # Delete user 42

The plural form is the convention because the collection is the resource, and the individual member is a subset. Using singular (/user) works technically but is inconsistentโ€”GET /user returns a list, which contradicts the singular noun. Using a verb (/getUsers) violates the uniform interface because the action is already expressed by the HTTP method.

The identifier should be stable and opaque to the client. A numeric ID, a UUID, or a slug are all acceptable. The client should treat the identifier as a string and should not parse it for meaning . If the server changes from numeric IDs to UUIDs, the client that treated the ID as opaque continues to work.


b. Nested resources for relationships

When a resource exists only in the context of another resource, the URI nests it under the parent. An order belongs to a user, so the URI is /users/42/orders. A comment belongs to a post, so the URI is /posts/7/comments. The nesting depth should be limitedโ€”one or two levels is the practical maximum. Three or more levels (/users/42/orders/7/items/3/reviews) produce URIs that are difficult to read and difficult to cache .

GET  /users/42/orders        # List orders for user 42
POST /users/42/orders        # Create an order for user 42
GET  /users/42/orders/7      # Retrieve order 7 for user 42

The nested form is not always the right choice. If the child resource can exist independentlyโ€”if orders can be queried across all usersโ€”then a top-level collection with a filter is better: /orders?user_id=42. The rule of thumb is: nest when the child cannot exist without the parent, and use a query parameter when the child has its own identity and can be accessed independently .

A common mistake is mixing the two forms inconsistently. If GET /users/42/orders returns the user’s orders, then GET /orders/7 should also work for direct access. The nested URI is a convenience for the relationship, not a replacement for the top-level resource.


c. Query parameters for filtering, sorting, and pagination

Query parameters are for modifying the representation of a collection, not for identifying resources. Filtering (?status=active), sorting (?sort=created_at&order=desc), pagination (?page=2&per_page=20), and field selection (?fields=id,name) all belong in the query string .

GET /users?status=active&sort=name&page=2&per_page=20
GET /orders?user_id=42&status=shipped
GET /products?category=electronics&min_price=100&max_price=500

The rule is: if the parameter changes which resources are returned, it is a filter and belongs in the query. If it changes which resource is identified, it belongs in the path. /users/42 identifies user 42. /users?status=active identifies a subset of the users collection. The distinction is subtle but consistent: path segments are for identity, query parameters are for selection and presentation .

Pagination deserves a specific note because it has multiple common conventions. Offset-based pagination (?page=2&per_page=20) is simple but has the “shifting window” problem when items are inserted or deleted during traversal. Cursor-based pagination (?cursor=abc123&limit=20) is stable but more complex to implement. Both are valid; the choice depends on the data’s mutability and the client’s needs.


Complete Example Session

The following session traces a set of URIs for a blog API and shows the resource model that each URI expresses.

// ============================================
// PART 1: THE COLLECTIONS
// ============================================
// Plural nouns identify collections
GET    /posts           # List all posts
POST   /posts           # Create a post
GET    /users           # List all users
POST   /users           # Create a user
GET    /comments        # List all comments (top-level, queryable)

// ============================================
// PART 2: INDIVIDUAL RESOURCES
// ============================================
// Identifier appended to the collection
GET    /posts/7         # Retrieve post 7
PUT    /posts/7         # Replace post 7
DELETE /posts/7         # Delete post 7
GET    /users/42        # Retrieve user 42

// ============================================
// PART 3: NESTED RESOURCES
// ============================================
// Comments belong to a post, so they nest under it
GET    /posts/7/comments        # List comments for post 7
POST   /posts/7/comments        # Create a comment on post 7
GET    /posts/7/comments/3      # Retrieve comment 3 on post 7

// ============================================
// PART 4: QUERY PARAMETERS FOR SELECTION
// ============================================
// Filter, sort, and paginate a collection
GET /posts?status=published&sort=created_at&order=desc&page=1&per_page=10

// ============================================
// PART 5: WHEN NOT TO NEST
// ============================================
// Comments can be queried across all posts,
// so a top-level collection with a filter is also valid
GET /comments?post_id=7

// ============================================
// PART 6: WHAT NOT TO DO
// ============================================
// Verbs in the URI (use HTTP methods instead)
GET  /getPosts
POST /createPost

// Singular collection names (inconsistent with list response)
GET  /post

// Deep nesting (hard to read, hard to cache)
GET  /users/42/posts/7/comments/3/replies/1

// Session-specific parameters in the URI (breaks caching)
GET  /posts?sessionid=abc123

The six parts show the resource model in practice: collections use plural nouns, individual resources append identifiers, nested resources express ownership, query parameters handle filtering and pagination, and the anti-patterns show what to avoid. What the session demonstrates is that the URI structure is a direct expression of the resource model, and consistency is what makes the API predictable.


Quick Reference

URI Patterns

PatternExampleMeaning
Collection/usersSet of users
Individual resource/users/42One user
Nested collection/users/42/ordersOrders belonging to user 42
Nested individual/users/42/orders/7Order 7 for user 42
Filter/users?status=activeSubset of users
Sort/users?sort=name&order=ascOrdered collection
Pagination/users?page=2&per_page=20Page of results

HTTP Methods on Resources

MethodCollectionIndividual
GETListRetrieve
POSTCreateNot typical
PUTNot typicalReplace
PATCHNot typicalPartial update
DELETENot typicalRemove

Naming Rules

RuleDoDon’t
Noun, not verb/users/getUsers
Plural collection/users/user
Identifier in path/users/42/users?id=42
Nest for ownership/users/42/orders/orders?user=42 (if owned)
Query for filtering/users?status=active/users/active

Nesting Depth

DepthExampleVerdict
1/users/42Good
2/users/42/ordersAcceptable
3+/users/42/orders/7/itemsAvoid

Best Practices

โœ… Do This:

GET    /users                    # Plural collection
GET    /users/42                 # Identifier in path
GET    /users/42/orders          # Nested ownership
GET    /orders?status=shipped    # Query for filtering
DELETE /users/42                 # Method expresses action

โŒ Don’t Do This:

GET    /getUser                  # Verb in URI
GET    /user                     # Singular collection
GET    /users/42/orders/7/items/3 # Deep nesting
POST   /users/42/delete          # Action in path
GET    /orders?sessionid=abc     # Session in URI (breaks caching)

Common Pitfalls

PitfallWhy It HappensFix
Verb in URIRPC mindsetUse HTTP method
Singular collectionInconsistent with listPlural noun
ID in query for individualConvenience/users/42 not /users?id=42
Deep nestingOver-modeling relationshipsFlatten after two levels
Filter as path segmentConfusing selection with identityUse query parameter

Real-World Examples

1. Collection

GET /users

2. Individual resource

GET /users/42

3. Nested collection

GET /users/42/orders

4. Filtered collection

GET /orders?status=shipped

5. Sorted collection

GET /users?sort=name&order=asc

6. Paginated collection

GET /posts?page=2&per_page=20

7. Create in collection

POST /users

8. Replace individual

PUT /users/42

9. Delete individual

DELETE /users/42

10. Top-level access to nested resource

GET /comments?post_id=7

Visual

Resource Model to URI

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  RESOURCE MODEL                              โ”‚
โ”‚  User                                        โ”‚
โ”‚  โ”œโ”€โ”€ has many Orders                         โ”‚
โ”‚  โ”‚   โ””โ”€โ”€ has many Items                      โ”‚
โ”‚  โ””โ”€โ”€ has one Profile                         โ”‚
โ”‚                                              โ”‚
โ”‚  URI STRUCTURE                               โ”‚
โ”‚  /users                  (collection)        โ”‚
โ”‚  /users/42               (individual)        โ”‚
โ”‚  /users/42/orders        (nested collection) โ”‚
โ”‚  /users/42/orders/7      (nested individual) โ”‚
โ”‚  /users/42/profile       (sub-resource)      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Path vs Query

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  PATH (identity)                             โ”‚
โ”‚  /users/42                                   โ”‚
โ”‚  โ†’ identifies user 42                        โ”‚
โ”‚                                              โ”‚
โ”‚  QUERY (selection/presentation)              โ”‚
โ”‚  /users?status=active                        โ”‚
โ”‚  โ†’ selects from the users collection         โ”‚
โ”‚                                              โ”‚
โ”‚  /users/42?fields=name,email                 โ”‚
โ”‚  โ†’ identifies user 42, selects fields        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Nesting Decision

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Can the child exist without the parent?     โ”‚
โ”‚  โ”œโ”€ NO โ†’ nest                                โ”‚
โ”‚  โ”‚        /users/42/orders                   โ”‚
โ”‚  โ””โ”€ YES โ†’ top-level + filter                 โ”‚
โ”‚           /orders?user_id=42                 โ”‚
โ”‚                                              โ”‚
โ”‚  Depth > 2?                                  โ”‚
โ”‚  โ””โ”€ YES โ†’ flatten or use query               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Method Mapping

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  /users                                      โ”‚
โ”‚  GET    โ†’ list                               โ”‚
โ”‚  POST   โ†’ create                             โ”‚
โ”‚                                              โ”‚
โ”‚  /users/42                                   โ”‚
โ”‚  GET    โ†’ retrieve                           โ”‚
โ”‚  PUT    โ†’ replace                            โ”‚
โ”‚  PATCH  โ†’ partial update                     โ”‚
โ”‚  DELETE โ†’ remove                             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Collection URIPlural noun (/users)
Individual URIIdentifier in path (/users/42)
Nested URIParent + child (/users/42/orders)
FilteringQuery parameter (/users?status=active)
SortingQuery parameter (?sort=name&order=asc)
PaginationQuery parameter (?page=2&per_page=20)
Verbs in URINot allowed
Nesting depth2 levels maximum
ID in queryAvoid for individual resources

Key takeaways:

  • URIs identify resources, not actions. Use plural nouns for collections and append identifiers for individual resources. The action is expressed by the HTTP method, not the path .
  • Nest when the child is owned. A resource that cannot exist without its parent belongs under the parent’s URI. A resource that can be queried independently uses a top-level collection with a filter .
  • Query parameters are for selection and presentation. Filtering, sorting, pagination, and field selection belong in the query string. Identity belongs in the path .
  • Limit nesting to two levels. Deeper nesting produces URIs that are hard to read, hard to cache, and hard to document. Flatten or use query parameters instead .
  • Identifiers are opaque. The client should not parse the ID for meaning. Numeric IDs, UUIDs, and slugs are all valid as long as they are stable and unique .
  • Consistency is the property that matters. A predictable URI structure lets client developers guess endpoints correctly and reduces the documentation burden. Inconsistency forces every endpoint to be looked up individually .

Remember: The URI is the API’s most visible surface. It is what clients type, what documentation lists, and what caches key on. The conventionsโ€”plural nouns for collections, identifiers in paths, nesting for ownership, query parameters for filteringโ€”are not arbitrary rules. They follow from the resource model, and they produce an API that is predictable, cacheable, and consistent. When the URI structure mirrors the domain model, the API becomes self-explanatory. When it does not, every endpoint becomes a special case, and the client developer’s mental model never forms.



Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!