Some Principles For REST API Design PDF

Download as pdf or txt
Download as pdf or txt
You are on page 1of 7

SOME PRINCIPLES FOR REST API DESIGN

1. Basics of HTTP applied to REST


If we’re to build a well-designed REST API, we'd better know the basics of the HTTP protocol. It is truly
believed this will help you make better design decisions.
As far as REST API design is concerned, here are some best practices of HTTP applied to REST:
HTTP has verbs (or methods): GET, POST, PUT, PATCH and DELETE are the most common.
REST is resource-oriented and a resource is represented by a URI: /newspapers/.
An endpoint is the combination of a verb and a URI, e.g. GET: /articles/.
An endpoint can be interpreted as an action on a resource. For example, POST: /articles/ may mean
"Create a new article".
At a high-level, verbs map to CRUD operations: GET means Read, POST means Create, PUT and PATCH
means Update, and DELETE means Delete.

A response's status is specified by its status code: 1xx for information, 2xx for success, 3xx for
redirection, 4xx for client errors, and 5xx for server errors.

Of course, you can use anything the HTTP protocol offers for REST API design, but these are basic things
we need to keep in mind.

2. Don't return plain text


Although it is not imposed by the REST architectural style, most REST APIs use JSON as a data format.
However, it is not enough to return a body containing a JSON-formatted string. You need to specify the
Content-Type header too! It must be set to the value application/json.
This JSON is especially important for programmatic clients (for example, someone or a service
interacting with your API via the requests library in Python) — some of them rely on this header to
correctly decode the response.
Pro tip: We can verify a response’s Content-Type very easily with Firefox. It has built-in pretty-display for
responses with Content-Type: application/json.

1
SOME PRINCIPLES FOR REST API DESIGN

3. Avoid using verbs in URIs


If we've understood the basics, we'll now know it is not RESTful to put verbs in the URI.
This is because HTTP verbs should be sufficient to describe the action being performed on the resource.
Let's say we want to provide an endpoint to generate and retrieve a banner image for an article. We will
note :param a placeholder for a URI parameter (like an ID or a slug). You might be tempted to create this
endpoint:
GET: /articles/:slug/generateBanner/

But the GET method is semantically sufficient here to say that we're retrieving ("GETting") a banner. So,
let's just use:
GET: /articles/:slug/banner/

Similarly, for an endpoint that creates a new article:


# Don't
POST: /articles/createNewArticle/
# Do
POST: /articles/
HTTP verbs all the things!

4. Use plural resource nouns


It may be hard to decide whether or not we should use plural or singular form for resource nouns.
Should we use /article/:id/ (singular) or /articles/:id/ (plural)?
It is recommended to use the plural form.
Why? Because it fits all types of endpoints very well.
For example, GET /article/2/ is fine, but what about GET /article/? Are we getting the one and only
article in the system, or all of them?
To prevent this kind of ambiguity, let's be consistent (life advice!) and use plural everywhere:
GET: /articles/2/
POST: /articles/

2
SOME PRINCIPLES FOR REST API DESIGN

5. Return error details in the response body


When an API server handles an error, it is convenient (and recommended!) to return error details in the
JSON body to help users with debugging. Special kudos if we include which fields were affected by the
error!
{
"error": "Invalid payload.",
"detail": {
"surname": "This field is required."
}
}

6. Pay attention to status codes


This one is super important. If there's one thing you need to remember from this article, this is probably
it:
The worst thing your API could do is return an error response with a 200 OK status code. It's simply
bad semantics. Instead, return a meaningful status code that correctly describes the type of error.
Still, we might wonder, "But we’re sending error details in the response body as you recommended, so
what's wrong with that?"

Let’s read a use-case.


We once had to use an API that returned 200 OK for every response and indicated whether the request
had succeeded via a status field:
{
"status": "success",
"data": {}
}
So even though the status was 200 OK, we could not be absolutely sure it didn't fail to process our
request.

3
SOME PRINCIPLES FOR REST API DESIGN

In fact, the API could return responses such as:


HTTP/1.1 200 OK
Content-Type: text/html
{
"status": "failure",
"data": {
"error": "Expected at least two items in list."
}
}
(Yes — it also returned HTML content, because why not?)
As a result, we had to check the status code AND the ad-hoc status field to make absolutely sure
everything was fine before we would read the data.
This kind of design is a real no-no because it breaks the trust between the API and its users. We come to
fear that the API could be lying to us.
All of this is extremely un-RESTful. What should we do instead?
Make use of the status code and only use the response body to provide error details.
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "Expected at least two items in list."
}

7. Use status codes consistently


Once we've mastered status codes, we should strive to use them consistently.
For example, if we choose that a POST endpoint returns a 201 Created somewhere, use the same status
code for every POST endpoint.
Why? Because API consumers shouldn't have to worry about which method on which endpoint will
return which status code in which circumstances.

4
SOME PRINCIPLES FOR REST API DESIGN

So be consistent, and if you stray away from conventions, document it somewhere with big signs.

Generally, we can stick to the following:


GET: 200 OK
POST: 201 Created
PUT: 200 OK
PATCH: 200 OK
DELETE: 204 No Content

8. Don't nest resources


REST APIs deal with resources and retrieving a list or a single instance of a resource is straightforward.
But what happens when we deal with related resources?
Let's say we want to retrieve the list of articles for a particular author — the one with id=12. There are
basically two options.
The first option would be to nest the articles resource under the authors resource, e.g.:
GET: /authors/12/articles/
Some recommend it because it does indeed represent the one-to-many relationship between an author
and their articles.
However, it is not clear anymore what kind of resource you're requesting. Is it authors? Is it articles?
Also flat is better than nested, so there must be a better way… And there is!
The recommendation is to use the query string to filter the articles resource directly:
GET: /articles/?author_id=12
This clearly means: "get all articles for author #12", right?

9. Handle trailing slashes gracefully


Whether or not URIs should have a trailing / is not really a debate. Simply choose one way or the other
(i.e., with or without a trailing slash), stick to it, and gracefully redirect clients if they use the wrong
convention.

5
SOME PRINCIPLES FOR REST API DESIGN

Story time!

One day, as Tom was integrating a REST API into a project, he kept receiving 500 Internal Error on every
single call. The endpoint he was using looked something like this:

POST: /entities
He was mad and couldn't figure out what he was doing wrong.
In the end, it turned out the server was failing because he was missing a trailing slash! So he started
using
POST: /entities/
and everything went fine afterward.
The API wasn't fixed, but hopefully, we can prevent this type of issue for our users.
Pro tip: Most web frameworks have an option to gracefully redirect to the trailed or untrailed version of
the URL. Find that option and activate it.
In Spring Boot

@Configuration
@ComponentScan
@EnableWebMvc
public class ApplicationConfig implements WebMvcConfigurer {

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseTrailingSlashMatch(false);
}
}

10. Make use of the querystring for filtering and pagination


A lot of times, a simple endpoint is not enough to satisfy complex business cases.
Your users may want to retrieve items that fulfill a specific condition or retrieve them in small amounts
at a time to improve performance.
This is exactly what filtering and pagination are made for.

6
SOME PRINCIPLES FOR REST API DESIGN

With filtering, users can specify properties that the returned items should have.
Pagination allows users to retrieve fractions of a data set. The simplest kind of pagination is page
number pagination, which is determined by a page and a page_size.

Now the question is: how do we incorporate such features in a RESTful API?
The answer is: use the querystring.
It's pretty obvious why we should use the querystring for pagination. It would look like this:
GET: /articles/?page=1&page_size=10
But it may be less obvious for filtering. At first, you might think of doing something like this to retrieve a
list of published articles only:
GET: /articles/published/
Design issue: published is not a resource! Instead, it is a trait of the data we're retrieving. That kind of
thing should go in the querystring.
So in the end, a user could retrieve "the second page of published articles containing 20 items" like so:
GET: /articles/?published=true&page=2&page_size=20
Beautifully explicit, isn't it?

11. Learn the difference between 401 Unauthorized and 403 Forbidden
When handling security errors in an API, it's very easy to be confused about whether the error relates to
authentication or authorization (a.k.a. permissions) — happens to me all the time.
 Has the user not provided authentication credentials? Were they invalid? 👉 401 Unauthorized.
 Was the user correctly authenticated, but they don’t have the required permissions to access the
resource? 👉 403 Forbidden.

12. Make good use of 202 Accepted


202 Accepted to be a very handy alternative to 201 Created. It basically means:
The server has understood the client’s request. The server has not created the resource (yet), but that
is fine.
There are two cases which I find 202 Accepted to be especially suitable for:
If the resource will be created as a result of future processing — e.g. after a job has finished.
If the resource already existed in some way, but this should not be interpreted as an error.

You might also like