A Quick Guide to Designing RESTful Endpoints for Your Api
Let’s dive into the world of RESTful API design, especially for those of you transitioning from Java or other back-end heavy environments.
One of the common mistakes I see everyday is naming API endpoints based on the actions they perform, using prefixes like /submitSomething/
, /startSomething/
, /getSomething/
, or /updateSomething/
.
While this might feel natural for function naming, it’s not the RESTful way to do things.
REST Basics: Start with the Resource
When designing a RESTful API, everything starts with the resource. If you can name it (think nouns), then it’s probably a resource.
On the flip side, anything that describes what you want to do with that resource is typically an action, like:
Create
Retrieve
Update
Delete
NOTE: CRUD is the acronym to refer to the four functions necessary to manage data.
In the world of HTTP, we have specific verbs that map directly to these actions:
- POST: Create a new resource
- GET: Retrieve a resource
- PUT: Update an existing resource
- PATCH: Partially update a resource
- DELETE: Remove a resource
The ultimate goal of RESTful design is to create an API that is intuitive, scalable, and easy to work with.
From submitQuestion
to RESTful
Let’s take a practical example. Suppose you have an endpoint named submitQuestion
. If you’re using a POST method here, the “submit” part is redundant because POST already implies creation or submission. So, let’s clean it up:
POST /question
This is better, but there’s still room for improvement…
In RESTful design, it’s often best to use the plural form of resources. This way, your API structure naturally supports CRUD operations (Create, Read, Update, Delete) on a collection of resources.
So, instead of:
POST /question
We should have:
POST /questions
Now we’re talking! But what about when you want to deal with a specific question? That’s where identifiers come into play.
NOTE: Moose is an acceptable exception to this rule.
Avoiding REST-In-Peace API Designs
This brings us to the problem with what we call “REST-in-peace” APIs. These are APIs that seem to follow REST principles but miss the mark by not fully embracing the concept of “resources”.
Ignoring the Resource: In RESTful design, the URL should represent the resource itself, not the action. For example, an endpoint like
/getQuestion
focuses on the action of getting a question rather than the resource (the question).This goes against REST principles because the HTTP Verb should dictate the action, not the endpoint name.
Redundant Actions in URLs: When you use verbs like
get
,create
, orset
in your endpoint names, you’re duplicating what the HTTP Verbs are already designed to do.For instance,
GET /getAllQuestions
is redundant because theGET
method already implies that you’re retrieving something. A more RESTful approach would beGET /questions
.Singular vs. Plural Resource Names: Another common mistake in REST-in-peace APIs is the inconsistent use of singular vs plural resource names.
In RESTful design, the URL segment representing a collection of resources should be plural (e.g.,
/questions
for multiple questions). When referring to a specific resource, you append the resource’s identifier in the path (e.g.,/questions/{questionId}
).- Correct:
GET /questions
(to retrieve a list of questions)GET /questions/{questionId}
(to retrieve a specific question)
- Incorrect:
GET /question
(singular form used to represent a collection)GET /getQuestion
(verb-focused rather than resource-focused)
- Correct:
Misuse of Named Queries in Paths: RESTful design also encourages the use of named queries in the path to represent common filters or actions.
For example, if you want to retrieve all unanswered questions, a RESTful approach might be:
GET /questions/unanswered
This is cleaner and more intuitive than embedding the action within the endpoint name, like
/getUnansweredQuestions
, or/questions?type="unanswered"
.It keeps the focus on the resource (questions) and uses the path to naturally extend the query.
CRUD Operations in REST
Let’s walk through how you’d handle each of the CRUD operations with our questions
resource.
Create a New Question
To create a new question, you’d use the
POST
method like:POST /questions
The body of your request would include the details of the question you’re adding:
// POST /questions HTTP/1.1 { "question": "What is REST?", "submitter": "Anon dev" }
Retrieve Questions
If you want to get all the questions, you simply do:
GET /questions
Need a specific one? Use the question’s ID:
GET /questions/{questionId}
Want to apply some filters? REST lets you easily get creative with your routes or query parameters:
GET /questions/unanswered GET /questions?from=2024-08-29
Update a Question
When it’s time to update, say to add or update multiple properties of the question, use the
PUT
orPATCH
methods:PUT /questions/{questionId}
This would update the entire question object.
If you only want to update a part of it, say to update the question with an answer,
PATCH
is your friend:PATCH /questions/{questionId}
And your request body might look like this:
{ "answer": "REST is a fantasy if you're a parent. Useful if you're an API." }
NOTE: Future article coming on “POST vs PUT”… stay tuned
Delete a Question
Finally, to remove a question, use the
DELETE
method:DELETE /questions/{questionId}
The Importance of Versioning Your API
As your API evolves, it’s CRUCIAL to version your endpoints to maintain backward compatibility. At very least intentionally signals to developers breaking changes coming soon instead of breaking immediately after deploying changes.
NOTE: Not versioning is like spinning until you’re dizzy, tossing lawn darts into the air—it’s only a matter of time before one comes down on you, just like an unversioned API eventually breaks something.
NOTE NOTE: I’m the best at analogies.
There are several ways to version an API:
In the URL Path: This is the most common approach where you include the version number as part of the URL:
GET /v1/questions
NOTE: If the version remains v1 for the entire lifetime of the API either you did something very right, or very very wrong.
In the Headers: Another approach is to specify the version in the HTTP headers, which keeps your URLs clean:
GET /questions X-Api-Version: 2024-09-01
In the Query String (RECOMMENDED): You can also version your API by including the version in the query string:
GET /questions?version=1
NOTE: #1 Safest approach: Proxies can block headers, and changing versions in a URL is a nightmare—clients often hard-code those URLs. That’s a lot of work just to update a version. Using a query string is easier; it lets you change versions without breaking everything.
Regardless of the method you choose, it’s essential to follow semantic versioning principles. Semantic versioning involves incrementing the version number in a meaningful way (e.g., major changes for breaking updates, minor changes for backward-compatible enhancements, and patch changes for backward-compatible bug fixes).
Wrapping Up
To avoid falling into the trap of “REST-in-peace” API design, always remember to:
- Focus on resources rather than actions.
- Use the correct HTTP methods to perform actions on those resources.
- Ensure plurality in resource names for collections.
- Leverage named queries in paths for clarity and intuitiveness.
- Implement proper versioning to manage API evolution effectively.
By adhering to these principles, you’ll create RESTful APIs that are not only clean and easy to use but also maintainable and scalable as your application grows.