Suppose you are designing checkout for a bookstore and need to hold books while a customer finishes an order. You could begin by listing functions such as checkStock, holdBooks, and releaseHold. But that list leaves an important question unanswered: what can the client refer to after a hold succeeds?
Thinking in resources starts with the things consumers need to identify, inspect, and work with over time.
This chapter explains how to find those things, distinguish them from their representations and storage, and choose useful boundaries. The examples assume a fictional bookstore with a public catalog and private customer reservations.
In HTTP, a resource is the target of a request, which a URI usually identifies. In a resource-oriented API, designers choose targets that represent meaningful concepts for consumers, such as a book, an order, or a stock reservation.
A resource does not have to be a physical object or a database record. A collection of books, a current availability estimate, and a report-generation job can also be useful resources.
Start with the consumer's task. For checkout, the requirement might be: “Hold two copies of this book for ten minutes, let me check the hold, and release it if I abandon checkout.” The lasting concept is a reservation. It has an identity, a quantity, an expiry time, and a state the client can inspect.
The diagram shows how the task leads to a resource model before endpoint details enter the design:
The reservation gives several operations a common subject. Checking it and releasing it concern the same hold, even if different backend functions perform those operations.
Renaming functions with nouns is not enough. A /reservation-manager endpoint could still expose an unclear collection of commands. The useful design work is deciding what a reservation means and what clients can rely on. Resource naming is a convention of the API style, not an HTTP requirement that every path contain a plural noun.
Three related concepts need separate names:
The API exposes this reservation at https://api.bookstore.example/reservations/res_731. The path identifies the target within the service, while res_731 is the identifier the application includes in its data. This model keeps both stable while the hold changes state.
The authorized owner can inspect it with this HTTP/1.1 request over HTTPS. EXAMPLE_TOKEN is a nonfunctional credential placeholder.
At 14:03, the server returns:
The JSON body is the single line above without a trailing newline. The fields and cache policy are choices for this example API.
The response describes the reservation at that time. If the owner releases it, a subsequent response can report "status":"released" with the same ID. Changing state does not necessarily create a new resource.
The representation also need not reveal all server state. The service might track warehouse allocations, internal lock versions, and audit records without exposing them to this caller. JSON is the format of the transferred description; it is not the reservation itself.
A resource boundary determines which information and behavior belong to one concept in the public contract. Database boundaries solve a different problem: organizing data for storage and processing.
Suppose the bookstore records a hold in one table, its stock allocations in another, and expiry work in a queue. An intentionally flawed customer API exposes all three and requires checkout clients to create the records in the correct order. A client that creates the hold but fails to create the allocation leaves an incomplete operation. Every consumer must also understand warehouse details.
A reservation resource lets the service own those internal steps. The client requests a hold and receives an outcome with a clear business meaning. Moving expiry work to a different scheduler should not change that meaning.
This diagram shows one API resource that several implementation components support:
The arrows describe implementation dependencies, not three operations the client must coordinate. A resource can combine data from several tables or services. It can also expose only part of one stored record.
Matching a table is not automatically a mistake. A simple catalog resource may map closely to a catalog row. The test is whether the interface expresses a useful consumer concept and can preserve that concept when storage changes.
Nor does a resource boundary automatically provide a database transaction across all its dependencies. If the service promises that an active reservation holds stock, its implementation must enforce that promise. If allocation is still underway, the resource needs a state that communicates that uncertainty instead of claiming success early.
Not every noun needs its own resource. Splitting every property into an independently accessible object increases the number of concepts and requests a client must manage.
Consider an order containing a shipping address. If that address is simply the delivery information the order records for this purchase, keeping it inside the order is reasonable. The client needs to understand it as part of the order, not manage a separate address lifecycle.
A saved customer address serves a different purpose. The customer can name it, update it, and reuse it across purchases. It may deserve its own identity. Even then, an existing order may need to retain its captured shipping address when the saved address changes.
Several questions help choose the boundary:
These are design signals, not a scoring formula. A shipment is often a separate resource because one order can produce several shipments with different tracking histories. A quantity is normally a value belonging to an order line or reservation.
An embedded object can still contain an identifier. An order line might need an ID so clients can distinguish two similar lines without requiring a standalone line endpoint. Likewise, returning a summary of a shipment inside an order does not mean the shipment loses its separate identity. Deciding how to identify a resource is separate from deciding whether to include it inside another resource's response.
A collection resource represents a group of resources. In this bookstore, /books identifies the catalog collection, while /books/book_1042 identifies one catalog entry. The collection has meaning even when it contains no books.
For this API, reading an accessible empty collection succeeds with an empty list. Looking up a particular book that does not exist has a different meaning. Treating both cases as “resource missing” would prevent consumers from distinguishing an empty catalog from an invalid book reference.
A collection representation also need not contain every member at once. A large catalog can return a bounded portion while preserving the collection's meaning. Do not split a collection into separate business resources just to limit response size.
Resources can also describe computed information. The bookstore might expose availability for a particular book and delivery region. That information may combine stock, existing holds, and shipping constraints instead of reading a single stored availability record.
Define what the computation means. “Two copies currently available” is an observation, not a promise that those copies remain available until checkout. A reservation represents a hold with rules and an expiry. Keeping availability and reservations distinct prevents clients from treating a stock check as a guarantee.
Temporary resources are useful too. If a customer requests an export that takes time, an export job can have an identity and a progress state. The resulting file and the job describe different things: the output and the work that produces it. A separate job is useful when clients need to track that work; a small calculation the service completes immediately may not need one.
A lifecycle describes the states a resource can occupy from creation through completion or removal. Understanding it helps determine whether the resource represents one coherent thing.
For this example, the service creates a reservation as active only after securing the stock. It then ends in one of three ways: checkout consumes it, the owner releases it, or time runs out.
The diagram captures this deliberately small lifecycle:
All four states describe the same reservation. In this model, an expired reservation cannot become active again; another attempt creates a new hold with a new identity. That is a business decision for this service, not a universal rule for resources.
The model must also describe invariants, conditions the service promises to preserve. Here, the quantity is a positive integer, an active reservation belongs to one customer, and consuming it can happen only while the hold is valid. A request for zero copies fails validation without creating a reservation. A correctly formed request can still fail because stock is unavailable.
Ownership affects the boundary as well. Knowing res_731 does not authorize another customer to inspect or release it. The service checks access for each operation. A collection of private reservations must apply the same ownership rules as individual reads; otherwise it can reveal resources the caller cannot access directly.
Time introduces another edge case. A reservation may expire between the client's read and checkout. The checkout operation must enforce the hold's validity at the point where it consumes it. An earlier response showing active is not permission to ignore the deadline.
Completion and disappearance are separate decisions. This example retains ended reservations for 24 hours after they end so owners can inspect the outcome. The contract must say what happens after that retention period. Releasing stock does not have to erase the resource immediately.
Before choosing detailed routes and payloads, describe each candidate resource in a few sentences. State what it means, how consumers distinguish instances, who can access it, what state it exposes, and when it begins and ends.
For the bookstore, a useful starting model is:
“Book” itself needs a definition. If customers can buy a paperback and an ebook separately, this model gives them different catalog identities. Using one ID for both would make the meaning of a reservation ambiguous. A short resource name cannot compensate for an unclear definition.
Walk through a normal checkout with the model: the customer finds a catalog item, checks availability, obtains a reservation, and completes an order that consumes the hold. Then consider expiry, unavailable stock, and another customer's access attempt. Each outcome should have a clear place in the model without forcing the client to understand internal tables.
Resource-oriented design still includes operations and business behavior. It does not require every concept to support unrestricted create, read, update, and delete operations. Clients cannot make a reservation active simply by assigning a status field. The service controls which changes are valid and how callers request them.
Thinking in resources begins with consumer needs and the concepts clients must identify and work with. Separate resource identity from changing state, transferred representations, and internal storage.
Decide what belongs in each resource based on what it represents, who owns it, and how it changes over time. Collections, computed views, and temporary jobs can be resources, while many values can remain embedded. A dependable model makes its guarantees and failure cases clear before you finalize endpoint details.