REST and Resource-Oriented APIs
LESSON
REST and Resource-Oriented APIs
The core idea: REST is useful when an API exposes stable resources and uses HTTP semantics honestly, trading server-side convenience for contracts that clients, caches, retries, and operators can reason about.
Core Insight
Imagine a learning platform with endpoints such as /createEnrollment, /completeLesson, and /getCourseDetails. The first version is easy for the backend team because each URL mirrors a server function. The trouble appears later: clients cannot tell which calls are safe to retry, cache behavior is unclear, documentation becomes a list of special actions, and every new workflow adds another custom verb.
REST pushes the design conversation one level earlier. Before naming endpoints, ask what stable things the client is trying to identify over time. In a learning platform, those things might be courses, lessons, enrollments, progress records, certificates, and certificate-generation jobs. Once the resources are visible, HTTP methods and status codes can carry shared meaning: GET reads, POST creates or starts work, PATCH changes part of a resource, and DELETE removes or cancels something.
The misconception to drop is that REST is mainly about pretty URLs or avoiding verbs at all costs. The deeper mechanism is contract design. A resource-oriented API gives clients stable nouns, gives infrastructure recognizable HTTP behavior, and gives the backend a public boundary that can survive internal refactors.
That stability costs something. Designing resources is slower than exposing the controller method you already wrote, and not every workflow fits cleanly into a simple CRUD shape. The goal is not purity. The goal is an API whose behavior is honest enough that a client developer, a cache, a retry loop, and an on-call engineer can make correct predictions from the contract.
Resource Modeling Before Endpoint Naming
A resource is a client-visible thing with identity and lifecycle. It does not have to be a database table, and it should not simply mirror a class name. It is the thing a caller can point at and ask the system to read, create, update, delete, or observe.
For the learning platform, a first pass might look like this:
GET /courses/{courseId}
GET /courses/{courseId}/lessons
POST /courses/{courseId}/enrollments
GET /users/{userId}/progress/{lessonId}
PATCH /users/{userId}/progress/{lessonId}
This shape tells a different story from /getCourseDetails or /completeLesson. It says that courses, lessons, enrollments, and progress records are stable concepts. It also says that "completing a lesson" is probably a state change on a progress resource, not just an arbitrary function call.
The backend may still do complex work internally. POST /courses/{courseId}/enrollments might validate eligibility, check payment state, write an enrollment row, emit an audit event, and enqueue a welcome email. REST does not require the client to know those internals. It gives the client a stable public handle for the business action.
The practical test is simple:
If the backend implementation changed tomorrow,
would this resource still make sense to a client?
If the answer is yes, the API is probably closer to a good boundary. If the answer is no, the endpoint may be exposing implementation structure rather than product structure.
HTTP Semantics as Part of the Contract
HTTP methods are not just routing labels. They create expectations about safety, idempotency, caching, and failure recovery.
Useful starting definitions:
GETis safe: it should not intentionally change server state.PUTis idempotent: sending the same replacement request again should have the same final effect.PATCHusually means partial update; its idempotency depends on the patch semantics you define.POSToften creates a subordinate resource or starts work, and it is not automatically idempotent.DELETEshould express removal or cancellation in a way that repeated attempts are predictable.
Those expectations matter most when something goes wrong. Suppose a mobile client sends a request and times out before receiving the response. Should it retry? If the request was GET /courses/42, retrying is normally safe. If the request was POST /courses/42/enrollments, retrying might create a duplicate enrollment unless the API has an idempotency key or a uniqueness rule that makes the operation repeatable.
A resource-oriented API should therefore make retry behavior part of the design, not an afterthought:
POST /courses/42/enrollments
Idempotency-Key: enroll-user-7-course-42
With a key like this, the backend can recognize a repeated creation attempt and return the original result instead of performing the side effect twice. That small detail connects API design to operational reality: networks fail, clients retry, and contracts must say what repeated work means.
The trade-off is precision versus speed. A team can ship faster by treating every endpoint as a custom POST, but then clients and infrastructure lose the shared semantics that make HTTP predictable.
Worked Example: Course Completion
Start with the naive design:
POST /completeLesson
{
"userId": "u-7",
"courseId": "c-42",
"lessonId": "l-3"
}
This endpoint works, but it hides the resource being changed. It also raises awkward questions. What happens if the client calls it twice? Can the caller read the completion state later? Is completion a command, a progress update, or a separate event?
A resource-oriented design makes the target explicit:
PATCH /users/u-7/progress/l-3
{
"state": "completed",
"completedAt": "2026-06-12T10:15:00Z"
}
Now the API says that progress is a resource with state. The client can read it, update it, and reason about the result. If the backend later changes how progress is stored, the public contract can remain stable.
There are still design choices. If completion triggers a long-running certificate calculation, the API might create a job resource instead:
POST /certificate-jobs
{
"userId": "u-7",
"courseId": "c-42"
}
GET /certificate-jobs/job-99
This is still resource-oriented. The job is a real thing the client can observe. It is more honest than pretending a long-running workflow is just an immediate field update.
When Resource Orientation Breaks Down
REST becomes harmful when the design forces every action into an unnatural noun. Some operations are commands. Some are workflows. Some are searches. Some are asynchronous jobs. The mature move is to model those concepts explicitly instead of pretending everything is a simple entity update.
Good escape hatches include:
- job resources for long-running work
- command resources for auditable business actions
- search endpoints when the query is the central object
- idempotency keys for retried creations or payments
- clear status resources for operations that outlive one request
The failure mode is dogma. An API can have noun-shaped URLs and still be confusing if the nouns are fake, the method semantics are dishonest, or the response behavior is inconsistent. Conversely, a command-style endpoint can be acceptable when the command is the real business concept and the contract explains its effects clearly.
The corrective question is:
What does the client need to identify, observe, retry, cache, or evolve independently?
Answer that question first. Endpoint names should follow from it.
Connections
This lesson sets up the next API design contrast: GraphQL. REST is strong when stable resources and HTTP semantics are valuable. GraphQL becomes attractive when several clients need different shapes of the same connected domain data.
It also prepares later lessons on versioning, validation, DTOs, and error handling. Those topics are easier to reason about once the API boundary has clear resources and method semantics.
Resources
- [RFC] HTTP Semantics RFC 9110
- Focus: Use it for method semantics, status codes, cache expectations, and the protocol behaviors behind HTTP API contracts.
- [PAPER] Architectural Styles and the Design of Network-based Software Architectures
- Focus: Read REST in its original architectural context rather than as a simplified URL style guide.
- [DOC] MDN: HTTP Request Methods
- Focus: Review the practical meaning of
GET,POST,PUT,PATCH, andDELETE.
- Focus: Review the practical meaning of
- [ARTICLE] Stripe: Designing robust and predictable APIs with idempotency
- Focus: Use it to connect API resource design to retries, duplicate prevention, and real client failure behavior.
Key Takeaways
- REST starts with client-visible resources: stable things with identity and lifecycle, not backend functions exposed over HTTP.
- HTTP method semantics are part of the contract because they shape caching, retries, idempotency, and operational predictability.
- Resource orientation should clarify reality, not force purity; commands, searches, and jobs are valid when they model the workflow honestly.
- A good REST API lets clients and operators predict what repeated requests, failures, and future evolution will mean.
← Back to Backend and API Architecture