Designing Resource URLs

intermediate
11 min

Designing Resource URLs

URLs are your API's user interface. A developer who has never read your docs should be able to guess that GET /api/customers/88/orders lists a customer's orders. In this lesson we cover the naming rules, hierarchy patterns, and judgment calls that produce intuitive, durable URL schemes.

Resources Are Nouns, Methods Are Verbs

The core REST idea: URLs identify things, and HTTP methods act on them. The moment a verb sneaks into a path, the design is drifting.

text
Good Avoid GET /api/articles GET /api/getArticles POST /api/articles POST /api/createArticle DELETE /api/articles/12 GET /api/deleteArticle?id=12

The right-hand column also has a real bug: performing a delete via GET means crawlers, prefetchers, and browser link previews can destroy data.

Core Naming Conventions

  • Plural nouns for collections: /products, /users, /orders. Then /products/42 reads naturally as one item from the collection.
  • Lowercase with hyphens: /purchase-orders, not /PurchaseOrders or /purchase_orders. Hyphens are the convention search engines and most style guides prefer for URLs.
  • No file extensions: use content negotiation (Accept headers), not /report.json.
  • Stable identifiers: IDs in paths should never change. Many public APIs expose slugs (/articles/rest-url-design) or UUIDs instead of raw database auto-increments, which also avoids leaking row counts.
  • No trailing slash inconsistency: pick /products (no trailing slash) and redirect the other form.

Nesting: How Deep Is Too Deep?

Nested paths express ownership: /customers/88/orders means orders belonging to customer 88. This is clear and self-documenting, but stop at one level of nesting. A path like /customers/88/orders/1042/items/3/product is fragile and forces callers to know the whole chain.

The standard escape hatch: once a resource has its own identity, promote it to a top-level collection. Order 1042 is globally unique, so expose /orders/1042 directly, and keep /customers/88/orders only as a filtered listing. Rule of thumb: nest for creation and listing within a parent, but fetch and mutate by direct ID.

Actions That Are Not CRUD

Some operations resist the noun model: publishing an article, cancelling an order, resending an email. Two accepted patterns exist. Treat the action as a sub-resource with POST, like POST /orders/1042/cancellation, or pragmatically use a verb suffix as many large APIs do, like POST /articles/12/publish. Either is fine; inconsistency between the two across your API is not.

Real-World Blueprint

A content platform might expose:

text
GET /api/v1/authors list authors POST /api/v1/authors create an author GET /api/v1/authors/ana-roy one author by slug GET /api/v1/authors/ana-roy/articles articles by that author GET /api/v1/articles?status=draft all drafts across authors PATCH /api/v1/articles/9f31 edit an article POST /api/v1/articles/9f31/publish state transition

Notice how listing by owner uses nesting, cross-cutting queries use query parameters, and direct access uses top-level IDs. That trio covers nearly every need.

Consistency Beats Cleverness

Whatever you choose, apply it everywhere. A style document with ten rules, enforced in code review, is worth more than any individual rule. Clients write wrappers around your URL patterns; every inconsistency becomes a special case in someone else's codebase forever.

Key Takeaways

  • URLs name resources with plural, lowercase, hyphenated nouns; HTTP methods supply the verbs.
  • Nest one level for ownership; promote identifiable resources to top-level collections.
  • Model non-CRUD operations as sub-resources or consistent action endpoints.
  • Never allow state changes through GET, and keep identifiers stable forever.

Next up: Query Parameters, Filtering, Sorting and Pagination, where collections learn to scale.

Designing Resource URLs - REST APIs | CodeYourCraft | CodeYourCraft