skip to content

Resource Modeling

How to decide what your resources actually are before you name any URL: mapping domain entities to resources, picking granularity, and choosing between documents, collections, and controller resources. Interviewers ask because bad resource boundaries are the root cause of most awkward REST APIs.

part ofAPI stylesoverview, primer and where to startread it →
on this pageshow

questions

4

When designing an HTTP API, how do you decide what becomes a resource? Explain why a resource is not simply a database table or a domain entity, and give an example where they differ.

level: middleimportance: must knowfreq 65%

answer

  1. resource = anything worth a URI, not a table row
  2. split by permission / volatility / size
  3. one entity → many resources; many entities → one resource
  4. computed and process resources have no table
  5. resource boundary = cache + permission + concurrency boundary

basics

~20 s

A resource is anything worth naming with a URI and giving a representation — driven by what clients need to address, not by storage. Entities usually become resources, but resources also cover projections, computed views, and processes that no table matches, and one entity may back several resources.

solid answer

~50 s

A **resource** is any concept that deserves its own identifier and representation from the client's point of view. An **entity** is a domain object with identity and lifecycle. They overlap heavily but are not the same mapping. The divergences that matter: - **One entity, several resources.** A `User` entity might expose `/users/{id}`, `/users/{id}/profile` (public subset), `/users/{id}/preferences` (different caching and permissions), and `/me`. - **Resources with no table.** `/reports/monthly-revenue?month=2026-07`, `/orders/42/invoice` (rendered), `/search?q=...` — computed, aggregated, or derived. - **Processes as resources.** A long-running export becomes `/exports/{id}` with a status, so the client can poll and cancel something addressable. - **Tables with no resource.** Join tables, audit rows and internal denormalizations often should not be exposed. The design question is therefore "what do clients need to name, fetch, cache and change independently?" — because a resource boundary is also a cache boundary, a permission boundary, and a concurrency boundary.

code

http · 10 lines
http
POST /exports HTTP/1.1
{"format":"csv","range":"2026-07"}

202 Accepted
Location: /exports/ex_7

GET /exports/ex_7 HTTP/1.1

200 OK
{"id":"ex_7","status":"running","progress":0.4}

go deeper

for a junior

Say a resource is anything the client needs to address by URI, give an example that is not a table (a report, a search), and note one entity can have several resources.

for a middle

Use the split/merge signals — permission, volatility, size, atomicity — and give a worked example of one entity exposed as several resources.

for a senior

Argue from cache, permission and concurrency boundaries, and cover process resources with 202 plus a status URI.

for a principal

Treat the resource model as the durable public contract that must survive storage refactors, and reason about where composition belongs so clients never own server invariants.

## Definitions first A **resource** is the target of a URI: any information worth naming — a document, a collection, a computed value, a real-world object, a process. Its **representation** is the bytes you send for it (a JSON document, a PDF, an image); one resource can have several representations. An **entity** in the domain sense is an object with identity that persists over time — a Customer, an Order. A **table** is a storage artifact. The common beginner move is `table → resource`, one for one, CRUD on each. It works for simple systems and quietly fails as the domain grows, because storage is optimised for writes and normalisation while an API is optimised for the interactions clients actually have. ## Where the mappings diverge **One entity → many resources.** Split when parts of an entity differ in *permission*, *volatility*, or *size*. - Permission: `/users/{id}` (self and admins) versus `/users/{id}/public-profile` (anyone). - Volatility: a product's description changes monthly and its price and stock change every minute. One resource means you either cache aggressively and serve stale prices, or cache nothing and pay for the description every time. Two resources give two cache lifetimes. - Size: an article's body versus its comments; separate resources let each paginate and cache on its own. **Many entities → one resource.** A checkout page's data may come from cart, pricing, promotions and inventory. Exposing four resources forces every client into four round trips and re-implementing the join. A single `/checkouts/{id}` that composes them is a legitimate resource — the API's job is to serve the interaction, not to publish the schema. **Resources with no entity.** Computed or derived things are first-class resources: `/reports/monthly-revenue?month=2026-07`, `/accounts/{id}/balance`, `/orders/{id}/invoice` in `application/pdf`. They have URIs, representations, cache semantics and ETags like anything else. **Processes as resources.** Operations that take time are best modelled as a resource representing the operation: `POST /exports` returns `202 Accepted` with `Location: /exports/{id}`, and `GET /exports/{id}` reports `status: running|done|failed` with a link to the result. This turns "a thing happening" into something addressable, pollable, cancellable and auditable. **Entities with no resource.** Join rows, audit tables, internal caches. Exposing them leaks the schema and creates a contract you must maintain. Publish what clients need. ## Document versus collection versus controller resources A useful vocabulary: - **Document** — a single thing: `/orders/42`. - **Collection** — a server-managed set of documents: `/orders`. - **Store** — a client-managed set where the client picks the key: `PUT /users/7/bookmarks/{clientKey}`. - **Controller** — a resource representing an executable concept when no document/collection mapping is honest. Recognising which kind you are creating keeps the verbs consistent: collections take `POST` to create and `GET` to list; documents take `GET`/`PUT`/`PATCH`/`DELETE`; stores take client-keyed `PUT`. ## Granularity signals in practice Split a resource when: parts have different permissions; parts have very different change rates; part of it is unbounded (a growing list) and would make the parent's representation grow without limit; clients routinely want one part without the other; or two parts need independent optimistic-concurrency control, because a single ETag over a whole aggregate makes unrelated edits collide. Merge when: clients always fetch the pieces together; the pieces change together and share a lifecycle; or splitting would force clients to reassemble an invariant the server should own (never make a client responsible for keeping two resources consistent — if updating them together must be atomic, they are one resource). ## A worked example A `Subscription` entity in the database has plan, price, status, card token, billing address, and a usage counter. As an API: - `/subscriptions/{id}` — plan, status, dates. Moderately cacheable, owner-readable. - `/subscriptions/{id}/payment-method` — separate permission, separate audit, PCI-sensitive, changed by a different flow. - `/subscriptions/{id}/usage` — high-volatility counter, short cache, cheap to fetch alone. - `/subscriptions/{id}/invoices` — an unbounded collection, paginated, would otherwise inflate the parent forever. One table, four resources — each with its own cache policy, permission and concurrency token. That is the mapping doing real work rather than mirroring storage.

  • Give a concrete signal that one resource should be split into two.
    Different change rates with a shared cache policy. If a product's description is stable for months but its stock count changes every few seconds, one resource forces a cache lifetime that is either wrong for the price or wasteful for the description. Different permissions are the other strong signal — if half the fields need an elevated role, that half is a separate resource.
  • When is it wrong to split, even though the pieces look separable?
    When they must change atomically. If a client updating A without B leaves the system in an invalid state, splitting makes the client responsible for an invariant the server should own — and HTTP gives you no cross-resource transaction. Keep them in one resource so a single request carries the whole consistent change.
  • Is exposing exactly one endpoint per database table ever the right call?
    For a small internal CRUD service with one known client, it is fast and adequate. It becomes a liability when the schema is your public contract: every normalization or refactor becomes a breaking API change, and clients pay round trips to re-join what you split. Even then, a thin projection layer is cheap insurance.

A restaurant menu is not the kitchen's inventory list: it names what diners order, sometimes combining ingredients and sometimes hiding them entirely.

saying these in an interview costs you the question

  • Equating resources with database tables and generating one CRUD endpoint per table
  • Treating a resource as a class and its endpoints as methods on that class
  • Believing a resource must map to something stored — rejecting computed or process resources
  • Splitting resources that must change atomically, pushing an invariant onto the client
  • Never splitting, so one endpoint carries fields with wildly different permissions and cache lifetimes

context

open as a page

Some HTTP APIs expose paths like `/me`, `/settings`, or `/orders/{id}/status` that are not collections and have no id in the URL. What are these singleton resources, when are they the right choice, and which HTTP methods make sense on them?

level: juniorimportance: should knowfreq 40%

basics

~20 s

A singleton is a resource that exists exactly once in its context, so it needs no id: /me, /config, /orders/42/status. Use GET to read and PUT/PATCH to update. There is usually no POST (nothing to create) and often no DELETE.

open as a page

An HTTP API has one endpoint per database table, and rendering a single screen takes eleven calls. A colleague proposes one large endpoint that returns everything the screen needs. Evaluate both extremes and describe how you would actually choose the granularity of resources.

level: seniorimportance: should knowfreq 50%

basics

~20 s

Fine-grained resources are chatty and push joins onto clients; one screen-shaped mega-resource couples the API to a UI, ruins caching and invalidation, and grows without bound. Choose by interaction: resources sized to how clients actually use data, with composition for genuinely joint reads and splits where permission, volatility or size differ.

open as a page

When designing resource URIs for an HTTP API, how do you choose between an opaque surrogate identifier such as `/users/8f3c...` and a natural key such as `/users/[email protected]` or `/products/SKU-1234`? What are the consequences of each?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Natural keys are readable but change, collide across scopes, and leak data into URLs and logs. Surrogate ids are stable and neutral, so make them canonical. Support natural-key lookup as a filtered query or a documented alias that redirects to the canonical URI.

open as a page