The basic you should know before building your REST api

There is no recipe to build a REST api, but, there are a lot of good practices that could be followed, so, I gathered the most used and well accepted practices that you should know before building yours.

Index

I hope this article can be helpful to you, so, let's begin.

Most commonly used HTTP methods

POST, GET, PUT, DELETE and PATCH. Those are not the hardest names to remember, are they? But, do you know what each one of those is for?
Let's start to understand each one of them:

GET

You should use this method every time you want to fetch an existing resource. If the resource does not exist, you must return a 404. If the resource does exist, then you must return a 200. The Content-Type header should be informed, but it is not mandatory.

POST

Basically you use this method when you want to create a resource. The request content-type should be the same as the response content-type. You should also return a 201 status code if the resource creation were successfully done, a 409 when a conflict occurs, a 400 if the request body syntax were not valid and the server could not parse it and a 422 if the request body were parsed successfully but the request body was invalid. For generic errors, use 500, but try to avoid it.

PUT

You use a PUT when you want to update an entire resource. If the server does succeed updating it must return a 200. If the resource you are trying to update does not exist, the server should return a 404.

PATCH

You should use PATCH when you want to update a partial content of a resource. That's the main difference between PUT and PATCH.

Let's say you have a resource:

{
  "id": 1,
  "name": "John",
  "occupation": "Software Engineer",
  "enjoy_using_xml": true
}

Then, the PATCH request should comply the following pattern:

[
  { "op": "replace", "path": "/name", "value": "Gabriel" },
  { "op": "add", "path": "/age", "value": 23 },
  { "op": "remove", "path": "/enjoy_using_xml"}
]

So, the resource should be updated to:

{
  "id": 1,
  "name": "Gabriel",
  "age": 23,
  "occupation": "Software Engineer"
}

If the server does succeed updating it must return a 200. If the resource you are trying to update does not exist, the server should return a 404.
You can check all PATCH operations here.

DELETE

This method is used to delete a resource. If the server does succeed deleting the resource it must return a 200. If the resource you are trying to delete does not exist, the server should return a 404. My advice is: avoid using this method. It's very rare the need to delete a resource. If you still want to use it, consider using logical deletion to avoid data loss.

Most commonly used HTTP status

2xx - Successful

  • 200: Success
  • 201: Created (should return the Location header and an empty body)
  • 202: Accepted (should return the actual status of the request before returning it)
  • 204: No content

3xx - Redirection

  • 301: Moved permanently (when the resource were moved to another uri - must return the Location header)
  • 303: See other (when the resource location is available on another uri - must return the Location header)
  • 304: Not modified

4xx - Client error

  • 400: Bad request (the request could not be parsed)
  • 401: Unauthorized (the request requires user authentication)
  • 403: Forbidden (you are authenticated but you have no permission to access the resource)
  • 404: Not found (the resource was not found)
  • 405: Method Not Allowed (the resource url exists, but there is no method for this action)
  • 409: Conflict (the resource you are trying to create already exists based on the fields you have informed)
  • 422: Unprocessable Entity (the server understand the request body, but the content is invalid)

5xx - Server error

  • 500: Internal server error (The server encountered an unexpected condition which prevented it from fulfilling the request.)

Don't use verbs in the resource uri

I've seen a lot of awful resource names. I've seen a lot of people using verbs in their apis. Just don't. A resource name and its uri should represent an entity. That's it.
Example, let's say you have an user resource.

Prefer endpoints like:

/users?page=1&page_size=10
/users;username=gabfssilva
/users/1

If you want to create a resource, create a POST endpoint:

/users

Don't do this:

/users/save

Or this:

/users/get

Or this:

/users/delete

Always use the HTTP methods for different actions. Avoid as much as you can using verbs in the uri.

Always use JSON as the Content-Type

I have just one thing to say: don't use XML. Although both XML and JSON are easy to read, JSON take a few advantages over XML:

  • It is much simpler
  • It uses less space
  • No need to create a parser at the JavaScript client

It's not just me who prefers JSON over XML. Lately developers are searching less for XML and more for JSON:

If you were going to follow only one thing from this article, I'd say: use JSON as your content type.

Envelop your response

There are a lot of differences between SOAP and REST. If you are reading this article, probably you know that SOAP is much more complex and complete than REST. Its specs help you build a web service based on well known patterns, and, if you like engineering patterns, you will find it very useful. The problem is, as I said, SOAP is complex, so, it has a lot of features that you won't need to use usually, that's why you should feel more comfortable using REST. Although, there are a few things that we miss using REST, such as an envelop to be returned as a response.
The good news is: you can create your own envelop! The envelop should contain a concise structure. It should support to return error messages. It should describe if the result body is a list or just a single item.
You don't know how to create a nice envelop? So, take this as an inspiration:

{
    "resource_name": { for single result. e.g. "user"
        "id": 1,
        "_links":{
            "self": {
                "href": "/api/resource_name_plural/1"
            },
            "other_resource_selectors": {
                "href": "/api/resource_name_plural/1?selectors=other_resource"
            }
        }      
        ... 
    },
    "resource_name_plural": [ for a list result. e.g. "users"
        {
            "id": 1,
            "_links":{
                "self": {
                    "href": "/api/resource_name_plural/1"
                }
            }           
            ... 
        },
        {
            "id": 2,
            "_links":{
                "self": {
                    "href": "/api/resource_name_plural/2"
                }
            }             
            ... 
        }
    ],
    "messages": [
        {
            "message": "application encountered an error",
            "type": "application_error"
        },
        {
            "message": "parameter x cannot be lower than 0", 
            "type": "invalid_parameter"
        }
    ],
    "pagination": {
        "total_size": 3029,
        "page": 2,
        "page_size": 25
    },
    "_links": {
        "next": {
            "href": "/api/resource_name_plural?page=3&page_size=25"
        },        
        "previous": {
            "href": "/api/resource_name_plural?page=1&page_size=25"
        }
    }       
 }

Note that, the "resource_name" is what you call your resource. If you have an entity called user and you want to create the resource that represents it, your resource should be named also as user. So, the "resource_name_plural" must be users. It does not apply for every case. Sometimes you want to represent a resource differently from a domain entity.

Make client-side errors well explained in the response

Validation errors should be well explained in the response. If a given path parameter is higher than the limit, the response should contain an explanation about the validation error. The same as for query and matrix parameters, as well as for the request body. Example:

{
    "messages": [
        {
            "message": "parameter x cannot be lower than 0", 
            "type": "invalid_path_parameter"
        },
        {
            "message": "body field z cannot be null", 
            "type": "invalid_body_field"
        },
        {
            "message": "query parameter y cannot be empty", 
            "type": "invalid_query_parameter"
        }
    ]
}

Support versioning, but avoid to use it

Why someone would put some effort to do something, and, after it is done, just avoid to use it? Well, I will try to explain.
It is very common the need to change a request or a response schema. Sometimes you need to add, remove or rename fields, so, versioning helps you to not break the old clients who already use your api.
However, using versioning leads to many problems. Considering you have many versions, it will be very hard to maintain, you will need to make sure that every active version is working. It will be harder to create tests, duplicated code will constantly happen and so on. As you can see, versioning can make your life a living hell.
So, make your app support versioning, because someday you will need to use, but, as much as you can avoid creating new versions. My advice is, only create a new version if you remove a field. Actually, if the clients say that the field that will be removed is not used by them, why would you create a new version? Even in this case, removing a field I'd avoid creating a new version. It is hard, it is expensive, it gives headaches.
Martin Fowler gives a nice explanation about why you should avoid to use versioning.

Support async and callback operations for long running services

Long running tasks are very common. If you need to send an e-mail, do expensive database selections or use any other slow service before creating a resource, you should probably consider processing the request asynchronously.
How you do that? You could send the message to a queue and return an accepted to the client, informing that you will process his request as soon as you can.
Also, you can provide a callback feature based on a resource status or events, it is up to you to decide.
Let's say you have a resource called "email". This resource is used to send e-mails. As we know, sending e-mails is kind of a job not that fast, so, you will receive a request and callback the client after the e-mail is sent. You could support a feature along the resource.
Example:

POST /api/emails HTTP/1.1
Content-Type: application/json

{
    "title": "This is a pretty cool e-mail",
    "message": "You shall not pass",
    "tracker_id": "80814325-ecd2-968d-37d3-3f7e71988b60",
    "subscribers": [
        {
            "status": "SAVED",
            "url": "https://client.application.org/callback/saved/80814325-ecd2-968d-37d3-3f7e71988b60"
        },
        {
            "status": "SENT",
            "url": "https://client.application.org/callback/sent/80814325-ecd2-968d-37d3-3f7e71988b60"
        }
    ]
}

So, as soon as the server is able to save the e-mail in a database, it will callback the client based on given url. The same will happen when the e-mail is sent. The resource status will be updated to "SENT" and the server will callback the client based on the given url.

This approach was suggested by a friend of mine, Gabriel Ozeas, so, it is fair to mention him over here. =P

Sub-Resources

Sub-resource is a secondary resource. It usually is referenced by a primary resource and it does not exist without this one, although this is not a rule.
Example:

{
    "user": {
        "id": 1,
        "username": "gabfssilva",
        "active": true,
        "person": {
            "id": 1,
            "fullname": "Gabriel Francisco",
            "occupation": "Software Engineer",
            "_links": {
                "self": {
                    "href": "/api/users/1/person"
                 }
             }
        },
        "_links": {
            "person": {
                "href": "/api/users/1/person"
             },
             "self": {
                 "href": "/api/users/1"
             }
        }
    }
}

You can also use selectors to avoid returning several sub-resources in a response body.

When the sub-resource exists without the root resource, it should also be exposed as a root resource:

GET /api/users/1 HTTP/1.1
Content-Type: application/json

Response body:

{
    "user": {
        "id": 1,
        "username": "gabfssilva",
        "active": true,
        "person": {
            "id": 1,
            "fullname": "Gabriel Francisco",
            "occupation": "Software Engineer",
            "_links": {
                "self": {
                    **"href": "/api/people/1"**
                 }
             }
        }
        "_links": {
            "person": {
                **"href": "/api/people/1"**
             },
            "self": {
                "href": "/api/users/1"
             }
        }
    }
}

So, if you want to get only the person:

GET /api/people/1 HTTP/1.1
Content-Type: application/json

Response body:

{
    "person": {
        "uri": "/api/people/1",
        "id": 1,
        "fullname": "Gabriel Francisco",
        "occupation": "Software Engineer",
        "_links": {
            "self": {
                **"href": "/api/people/1"**
            }         
        }
    }
}

Parameter types

HTTP has three types of parameters: path parameter, query parameter and matrix parameter. The following examples will show you when to use each one of them:

Path parameter

You usually use path parameters when you need to receive a identifier for a resource.

/api/users/1

Matrix parameter

Matrix parameter are used to query data based on the resource fields

/api/users;username=gabfssilva

Query parameter

Query parameters are used to inform meta-data to the server. Pagination is example of a good use of query parameters.

/api/users?page=1&page_size=15

Matrix parameters vs Query parameters

Most people get confused when to use matrix and query parameters. If you do, don't blame yourself, just keep reading and you will understand what they mean. ;)
The main difference between those two is that matrix parameters apply to a specific resource and query parameters are global, in other words, for the primary resource and its sub-resources.
Knowing this, we can see that it makes more sense to use matrix parameters to query data from a resource field and query parameters to inform metadata.

Let's take the following GET as an example:

/api/countries;name=brazil/states;name=Sao Paulo/cities?page=1&page_size=20

So, I'm searching for a list of the cities in the state of Sao Paulo, Brazil, using pagination to limit the result to 20 cities per page. You could notice how we used matrix parameters to query the country and the state. The query parameters are used for the pagination.

Bad Request vs Unprocessable Entity

Developers tend to use bad request for any invalid request, even if the request can be parsed. This is wrong. If you can parse the body and the fields are invalid for some reason, you should return an unprocessable entity with a well explained response body informing the validation errors.

HATEOAS and HAL

You should always consider implementing HATEOAS in your services. Using HATEOAS will make your api easier to navigate. To implement it, basically you need make your api explorable through links that reference resources to each other. Every example here was implementing it, so, you could see the Sub-resources topic and check how to do it yourself.
Also, you should consider using HAL. I'm not going to show an example here because it is kind of complex, but, you check it out over here and see how to implement it.

Prefer to use snake_case when naming your fields

You should be wondering: "does it really matter?". Well, actually it does. Although camelCase is the most used field naming approach, snake_case is much easier to read, so, you really should consider using it.

Support selectors to avoid huge responses

Selectors are used to expand additional info of a resource, so, when we need to return resource that has a huge amount of JSON, we also could try to hide its fields and only return the ones that the client requires.

Example:

{
   "user":{
      "id":1,
      "username":"gabfssilva",
      "person":{
         "id":"1",
         "address":{
            "id":1,
            "_links":{
               "self":{
                  "href":"/api/people/1/address"
               }
            }
         },
         "_links":{
            "self":{
               "href":"/api/people/1"
            },
            "address_selector":{
               "href":"/api/people/1?selectors=address"
            }
         }
      },
      "_links":{
         "self":{
            "href":"/api/users/1"
         },
         "person_selector":{
            "href":"/api/users/1?selectors=person"
         },
         "person_address_selectors":{
            "href":"/api/people/1?selectors=person,address"
         }
      }
   }
}

If you want to get an address from an user, instead of navigating for each resource you could directly use the selectors to fetch them along the user:

GET /api/users/1?selectors=person,address HTTP/1.1
Content-Type: application/json

{
   "user":{
      "id":1,
      "username":"gabfssilva",
      "person":{
         "id":"1",
         "fullname":"Gabriel Francisco",
         "age":23,
         "occupation":"Software Engineer",
         "last_watched_movie":"Captain America: Civil War",
         "address":{
            "id":1,
            "country":"Brazil",
            "state":"Sao Paulo",
            "city":"Sao Paulo",
            "_links":{
               "self":{
                  "href":"/api/people/1/address"
               }
            }
         },
         "_links":{
            "self":{
               "href":"/api/people/1"
            },
            "address_selector":{
               "href":"/api/people/1?selectors=address"
            }
         }
      },
      "_links":{
         "self":{
            "href":"/api/users/1"
         },
         "person_selector":{
            "href":"/api/users/1?selectors=person"
         },
         "person_address_selectors":{
            "href":"/api/users/1?selectors=person,address"
         }
      }
   }
}

Use a tool to help you make a good documentation

There are a few tools that can help you create your documentations. I don't know many tools, but, the few I know are:

Depending on the language and the framework that you used to create your endpoints, you should be able a nice tool to create the docs, or even automated docs generation from your source code.

Use SSL even for non-sensitive information

I don't think it is even necessary to remember, but, always use SSL, even if you are not transferring non-sensitive information. Man-in-the-middle attacks can be very harmful, so, don't count on luck.

Get to know the 5LMT Proposal

We all know that HTTP methods are very limited and sometimes we want to do multiple operations in a single resource. Some people end up using verbs in the URL, but, there are other alternatives, such as the Five Levels of Media Type Proposal (5LMT).
Basically, the 5LMT says the Content-Type should carry the information about the request, thus, the server can know what to do, without breaking your RESTful API:

POST /api/transactions HTTP/1.1 
Content-Type:application/json; charset=utf-8; domain-model=VisaTransaction; version=1.0.0.0; format=application%2fjson; schema=application%2fjson; is-text=true

{
//request body
}
POST /api/transactions HTTP/1.1 
Content-Type:application/json; charset=utf-8; domain-model=MasterCardTransaction; version=1.0.0.0; format=application%2fjson; schema=application%2fjson; is-text=true

{
//request body
}

It's amazing how this approach can help you. I discovered it by searching how I could expose my CQRS app through a RESTful api.

Use the best REST framework of your language

Try to research, to compare and to test the frameworks that your language offers. As beautiful as the api should be, so should be the code of the server. Remember, other people will maintain your code and nothing fairer than a nice and well known architecture. It will inspire the developers to keep the RESTful api that you once created.

Final considerations

As much as I change my mind about good practices in REST, I will try to update this article. If you have a tip, a review or just an observation, feel free to comment. I will answer you as soon as I can.

[]'s

Gabriel Francisco

Software Engineer at GFG, 25 years, under graduated in Computer Science and graduated in Service-oriented Software Engineering. Like playing guitar once in a while. Oh, and I'm kind of boring.

São Paulo

comments powered by Disqus