Skip to content

Modeling Resources

Before naming a single URI, decide what your resources are. Good resource modeling makes the rest of the API fall into place; poor modeling shows up later as awkward endpoints and special cases.

Start from the domain language. The persistent things your users talk about — orders, invoices, playlists, devices — are your primary resources. They usually map to collections you create, read, update, and delete.

// The resources of a small blog API, as TypeScript shapes.
type User = { id: string; name: string; email: string };
type Article = { id: string; title: string; body: string; authorId: string };
type Comment = { id: string; body: string; articleId: string; authorId: string };

Not every database table is a resource, and not every resource is a table. A resource is anything worth addressing with a URI — including computed or virtual things like /me (the current user) or /search.

When a use case sounds like a verb — “publish an article”, “cancel an order” — resist inventing /publishArticle. Two cleaner options:

  • Model the change as a field update: PATCH /articles/42 with { "status": "published" }.
  • Model the action as a sub-resource it creates: POST /orders/42/cancellation.

Aim for resources that are neither too coarse (one giant /data blob) nor too fine (a separate resource per field). A good test: a resource should be something a client wants to fetch, create, or change as a unit.

Advantages (Resource Modeling)Trade-offs
A predictable API — developers can guess the URIRequires domain analysis before implementing
Resource-based APIs evolve more easily than verb-based onesSome actions (search, publish) are awkward to map to a resource
The HTTP method signals intent — no need to read the bodyComplex business logic may need special sub-resources
Cacheable per the HTTP standardOver-normalizing forces multiple calls per operation

Verb-based endpoints Symptom:

  • POST /getUser, POST /createOrder, POST /cancelOrder
  • Every operation uses POST — nothing is cacheable, idempotency is unclear
  • Fix: GET /users/\{id\}, POST /orders, POST /orders/\{id\}/cancellation

Wrong resource granularity Symptom:

  • One resource holds everything: GET /everything?type=user&id=1
  • Or resources are split too finely: GET /users/\{id\}/name, GET /users/\{id\}/email
  • A resource should be something a client wants to fetch or change as a single unit

💡 Real-world examples

GitHub API:

  • /repos/\{owner\}/\{repo\} — the repository is the resource
  • /repos/\{owner\}/\{repo\}/issues — issues are a sub-collection
  • /repos/\{owner\}/\{repo\}/issues/\{number\} — a single issue

Stripe API:

  • /customers/\{id\} — the customer resource
  • /customers/\{id\}/payment_methods — payment methods as a sub-resource of the customer

Sources: GitHub REST API — repos, Stripe API — customers

How should "publish an article" usually be modeled?
Which statement about resources and database tables is correct?
What is a good test of resource granularity?