DEV Community

Ishant Gaurav
Ishant Gaurav

Posted on

REST-API Design Best Practices

In this world of microservice, we develop most of endpoints using the REST(Representational State Transfer)-architecture for one mircroservice to talk to other microservice. So it is imperative to have good command on designing the REST-API in such a way which are meaningful, easy to extend and backward compatible.

If you like watching a video, then you can watch the same on my youtube channel : .

In this post, we will talk some of the best practices, which we should consider while designing any new REST API.

USE NOUN INSTEAD OF VERBS IN ENDPOINT PATHS

We shouldn’t use verbs in our endpoint paths. Instead, we should use the nouns which represent the entity that the endpoint that we’re retrieving or manipulating as the pathname.

Why So ?

This is because our HTTP request method already has the verb. Having verbs in our API endpoint paths isn’t useful and it makes it unnecessarily long since it doesn’t convey any new information. The chosen verbs could vary by the developer’s whim. For instance, some like ‘get’ and some like ‘retrieve’, so it’s just better to let the HTTP GET verb tell us what and endpoint does.

The action should be indicated by the HTTP request method that we’re making. The most common methods include GET, POST, PUT, and DELETE.

GET : retrieves resources.
POST : submits new data to the server.
PUT : updates existing data.
DELETE : removes data

UNDERSTAND THE SEMANTICS OF THE HTTP METHODS

Definition of Idempotence: A HTTP methods is idempotent when we can safely execute the request over and over again and all requests lead to the same state.

GET

  • Idempotent
  • Read-only. GET never changes the state of the resource on the server-side. It must not have side-effects. Hence, the response can be cached safely.

Examples:

GET /users – Lists all users
GET /users/1 – Shows the details of the user 1

PUT

  • Idempotent!
  • Can be used for both creating and updating
  • Commonly used for updating (full updates).

Example: PUT /users/1 – updates user 1 (uncommon: creates user 1)

To use PUT for creating, the client needs to know the whole URL (including the ID) upfront. That’s uncommon as the server usually generates the ID. So PUT for creating is typically used when there is only one element and the URL is unambiguous.

Example: PUT /user/1/avatar – creates or updates the avatar of user 1. There is only one avatar for each user.
Always include the whole payload in the request. It’s all or nothing. PUT is not meant to be used for partial updates (see PATCH).

POST

  • Not idempotent!
  • Used for creating
  • Example: POST /user creates a new user. The new URL is delivered back to the client in the Location Header (e.g. Location: /user/12). Multiple POST requests on /users lead to many new different users (that’s why POST is not idempotent).

PATCH

  • Idempotent
  • Used for partial updates. Example: PATCH /users/1– updates users 1 with the fields contained in the payload. The other fields of users 1 are not changed.

DELETE

  • Idempotent
  • Used for deletion.
  • Example: DELETE /users/1

For example :

GET /users – Retrieves a list of users
GET /users/12 – Retrieves a specific user
POST /users – Creates a new user
PUT /users/12 – Updates user #12
PATCH /users/12 – Partially updates user #12
DELETE /users/12 – Deletes user #12

Note : Although we should use verb in the method name always be using verbs and same will be applied for resource method like createUser, updateUser etc.

ALWAYS MAKE THE ENDPOINT NAME PLURAL

We should name collections with plural nouns. It’s not often that we only want to get a single item, so we should be consistent with our naming, we should use plural nouns.

Why So ?

We use plurals to be consistent with what’s in our databases. Tables usually have more than one entry and are named to reflect that, so to be consistent with them, we should use the same language as the table the API accesses.In the example below, we always use /users to represent the entity so that it would be consistent across all kind of endpoints.

GET /users – Retrieves a list of users
GET /users/12 – Retrieves a specific user

/users —-> represent all the users
/users/{id} —> represent the single entity

HIERARCHICAL OBJECTS OR RELATION BETWEEN OBJECTS

The path of the endpoints that deal with nested resources should be done by appending the nested resource as the name of the path that comes after the parent resource.We have to make sure that it makes sure what we considered a nested resources matches what we have in our database tables. Otherwise, it’ll be confusing.

For example if we have to find the all the list of the cars which a user posses then the endpoint path would be as :

GET : /users/{userId}/cars —- fetch all the cars of a particular
GET: /user/{userId}/cars/{carId} — fetch the details of a particular car.

USE THE QUERY STRING (?) FOR OPTIONAL AND COMPLEX PARAMETERS

Keep your URLs simple and the URL set small. Choose one base URL for your resource and stick to it. Move complexity or optional parameters to the query string.

GET /users?state=internal&title=senior
GET /users?id=1,2

REST API VERSIONING

You’ve already written your REST API and it has been very successful and many people have used it and are happy with it. But you have that juicy new functionality that breaks other parts of the system.

Versioning allows you to release incompatible and breaking changes of your API under a new version without breaking the clients. They can continue consuming the old version. The clients can migrate to the new version at their own speed.

If you are building internal APIs you most likely know all of your clients. So performing breaking changes can be an option again. But it will require more communication and a coordinated deployment.

URI versioning :

Versioning is usually done with /v1/, /v2/, etc. added at the start of the API path.

Before you start making your API, you can version your API by prefixing the endpoints by the API version: https://ishant.gaurav.com/v1/authors/2/blogposts/13

This way you can always increment your API version number (eg. v2, v3…) whenever there are breaking changes in your API. This also signals to the users that something drastic has changed and they should be careful when using the new version.

  • Pros: Clients can cache resources easily
  • Cons: This solution has a pretty big footprint in the code base as introducing breaking changes implies branching the entire API

Query string versioning

Instead of providing multiple URIs, you can specify the version of the resource by using a parameter within the query string appended to the HTTP request, such as https://ishant.gaurav.com/authors/2/blogspots/13?version=2. The version parameter should default to a meaningful value such as 1 if it is omitted by older client applications.

Advantage of using the versioning in URI is that the URI need to be changed for different versions of API but it also depends on the code that handles the request to parse the query string and send back the appropriate HTTP response.

  • Pros: It’s a straightforward way to version an API, and it’s easy to default to the latest version
  • Cons: Query parameters are more difficult to use for routing requests to the proper API version

Header versioning

Another way to implement the API versioning is that you could implement a custom header that indicates the version of the resource. This approach requires that the client application adds the appropriate header to any requests, although the code handling the client request could use a default value (version 1) if the version header is omitted.

Version 1:

GET https://ishant.gaurav.com/authors/2/blogspots/13 HTTP/1.1
Custom-Header: api-version=1

Version 2:

GET https://ishant.gaurav.com/authors/2/blogspots/13 HTTP/1.1
Custom-Header: api-version=2

Pros: It doesn’t clutter the URI with versioning information
Cons: It requires custom headers

Media type versioning (Accept Header)

Another way to define the api versioning is via the media type. Accept header define the media type and character encodings. We can also pass version information for Web API through accept headers without changing the URL. It is also known as media type versioning or content negotiation or accept header. Github uses the accept header versioning.

Accept: application/vnd.demo.v1+json Accept:application/vnd.demo+json;version=1.0

Pros: Allows us to version a single resource representation instead of versioning the entire API, which gives us a more granular control over versioning. Creates a smaller footprint. Doesn’t require implementing URI routing rules.

Cons: Requiring HTTP headers with media types makes it more difficult to test and explore the API using a browser.

USE HTTP STATUS CODES

The RESTful Web Service should respond to a client’s request with a suitable HTTP status response code.

  • 2xx – success– everything worked fine.
  • 4xx – client error – if the client did something wrong (e.g. the client sends an invalid request or he is not authorized)
  • 5xx – server error – failures on the server-side (errors while trying to process the request like database failures, dependend services are not available, programming errors or states that should not occur)

Consider the available HTTP status codes. However, be aware, that using all of them could be confusing for the users of your API. Keep the set of used HTTP status codes small. It’s common to use the following codes:

2xx: Success

  • 200 OK
  • 201 Created

3xx: Redirect

  • 301 Moved Permanently
  • 304 Not Modified

4xx: Client Error

  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found
  • 410 Gone

5xx: Server Error

  • 500 Internal Server Error

Don’t overuse 404. Try to be more precise. If the resource is available, but the user is not allowed to view it, return a 403 Forbidden. If the resource existed once but now has been deleted or deactivated, use 410 Gone.

PROVIDE USEFUL ERROR MESSAGES

Additionally to an appropriate status code, you should provide a useful and verbose description of the error in the body of your HTTP response. Here’s an example.

Request:

GET /employees?state=super

Response:

// 400 Bad Request
{
  "errors": [
    {
      "status": 400,
      "detail": "Invalid state. Valid values are 'internal' or 'external'",
      "code": 352
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

DOCUMENTATION

This one is a no-brainer. You could be the best API designer in the world, but without documentation, your API is as good as dead. Proper documentation is essential for every software product and web service alike.

You can help the user by being consistent and using clear and descriptive syntax, sure. But there is no real replacement for good ol’ documentation pages.

Here are some of the great examples:

There are many tools that can help you document your API, but don’t forget to add the human touch, only one human can properly understand another.

This is all from this POST, please give your comments and thoughts about it in the comments.

Top comments (1)

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

I would disagree with this one: /user/{userId}/cars/{carId}. Why? Because it complicates the implementation, it is not intuitive (as opposed to /cars/{carId}, and it creates a second URI, and in REST, we should only have a single URI per resource.

Implementation-wise, it could be a nightmare. What if cars has collections? Following this logic, we could end up with /user/{userId}/cars/{carId}/oilChanges, and so on and so forth until you traverse all possible collections. Although I won't deny programming a server that can understand and properly response to these URL's could be fun and an interesting exercise, in practice I would never do this.

I'd instead make sure that every resource owned by another resource would identify itself by carrying as part of its data its URI (or at least its ID), so, if the consumer is interested, it can query the server for the resource, if needed.