Over the past year I've been doing a lot of work on creating RESTful APIs, from the experience of creating APIs I've learnt a few things that you should try to include on API's you create. In this tutorial we're going to go through some of the best practices you should try to include in your API.
Understanding The HTTP Method Types
Before you build a RESTful API it's important to understand the HTTP method types you can use. There are 5 HTTP
methods: - GET
for fetching data
POST
used for storing new dataPUT
used for updating a whole entityPATCH
used for updating part of an objectDELETE
used to delete data
One advantage of understanding these HTTP methods is that it allows you to make more meaningful routes on your API. You
don't need a get-product
route and a add-product
route or a delete-product
route you can have one route
of products
and use HTTP methods to decide how to handle this request.
$api->get('products', 'ProductController@getProduct');
$api->post('products', 'ProductController@addProduct');
$api->put('products', 'ProductController@updateProduct');
$api->delete('products', 'ProductController@deleteProduct');
Use Correct Return Types
When using HTTP request types it's important to also use the correct HTTP status codes. The most common HTTP status codes you will be aware of is the successful 200 code or the not found 404 code, but there are loads more. HTTP Codes### Successful Codes
200
- successful201
- created204
- no content
On a successful return from the API you can use the HTTP status codes to explain what type of successful action has
happened. For example, if a call to GET data is returned successfully you should return a 200
code along with the
object the API fetched. If you're creating an object you could return a 201
status code to mark the record as created.
If you're deleting an object you simply want to return successful but there's no data to return so you can use a
HTTP 204
to say no content is return but it was successful effectively a boolean true. ### Data Errors
400
- Bad request404
- Not found500
- server throw an unexpected error
A 400
status indicates that the server doesn't understand the request due to an invalid syntax. A 404
status shows
that the requested object was not found. A 500
status shows that there was an unexpected error on the server. These
will general come from when your application throws an uncaught exception from a general error in your code. ### Auth
Errors
401
- Request has failed authorisation403
- Access token has forbidden access to the API
401
code indicates that the request could not be performed as it does not pass the authorisation requirements. 403
indicates that an access token from supplied by the token was not accepted or has expired.
Versioning
If you're developing an API that's going to be consumed by multiple clients then you need to prepare yourself for
versioning. This is because there will be eventual change to some of the endpoints which might be consumed by one client
but not the other, therefore one client application will work correctly the other client application will break. By
using versions on your API you could have a client using the new v2
API and the other continuing to use the old v1
endpoints. You can do versioning by simply prefixing the endpoints with the current version.
GET example.com/api/v1/posts
GET example.com/api/v2/posts
How To Name Your Endpoints
When naming your API endpoints you should always think of them as nouns and not verbs. You need to define your endpoints
as resources for example if you have an endpoint where you want to get all the products, it's tempting to use the
URL /getAllProducts
. In REST APIs we need to use resources and nouns for our endpoints so instead, we will simply use
the endpoint /products
and the use HTTP request methods to define what we want the endpoint to do. For example with
our endpoint /products
using a GET method we know we want to get the products. Or we could change the method to a
POST /products
we use the same endpoint to create a new product.
Use of Singular or Plural Urls
Sometimes you'll see a mix of singular and plural URLs, I find this can be confusing and like to stick to just plurals.
This will represent a more directory style structure to your URLs. Get all the products by calling /products
. Get a
product by ID by using /products/1
Filtering With Querystrings
You should use querystrings when you want to filter the results for example on the all posts endpoint you might want to
only the last 10 posts, it doesn't make sense to create an endpoint like GET example.com/api/v1/posts/all/last/10
, you
should use a querystring like GET example.com/api/v1/posts?per_page=10
. The reason you should use querystring for this
is that you could pass in more filters such as a category ID you can't
do GET example.com/api/v1/posts/all/last/10/category/5
as the endpoints start to become confusing and the syntax will
need to be looked up each time. You should use categories in the querystring
GET example.com/api/v1/posts?per_page=10&category=5
Using Response Envelopes
This is an area that causes some discussion, some people are along the lines of not enveloping the return data which means the return json will look something like.
{
{
"foo": "boo"
},
{"foo2": "boo2"},
{"foo3": "boo3" },
{"foo4": "boo4"},
{"foo5": "boo5"
}
}
But when developing APIs I like to return responses in a data envelope such as.
{
data: [
{
"foo": "boo"
},
{
"foo2": "boo2"
},
{
"foo3": "boo3"
},
{
"foo4": "boo4"
},
{
"foo5": "boo5"
}
]
}
There are a few reasons that I prefer to work this way. One thing is that it allows you to add any additional information in the return JSON, one example of this is pagination data if you request 10 posts on page 9 you also return links to the next and previous pages.
{
data: [
{
"foo": "boo"
},
{
"foo2": "boo2"
},
{
"foo3": "boo3"
},
{
"foo4": "boo4"
},
{
"foo5": "boo5"
}
],
links: {
"next": "",
"prev": ""
}
}
Another reason to use data envelopes in your responses is for security reasons with JSON hijacking on older browsers the following article explains the JSON vulnerability in returning an array. Anatomy of a Subtle JSON Vulnerability JSON Hijacking AJAX Security Cheat Sheet
Pagination
It's important to provide a way of pagination the data you return from the API. For example, if you have an endpoint to
get all the products /products
depending on the size of the company this could return 10 products but it could return
10,000 products. This is why it's important to return the data in paged form typically 10 or 20 items per page. If you
use Laravel then you'll be familiar with the basic pagination functionality
this uses a page=2
querystring to return a chunk of the data.
{
"total": 50,
"per_page": 15,
"current_page": 1,
"last_page": 4,
"next_page_url": "http://laravel.app?page=2",
"prev_page_url": null,
"from": 1,
"to": 15,
"data": [
{
// Result Object
},
{
// Result Object
}
]
}
You can then grab the next_page_url
and prev_page_url
and return in the API response. Here is a tutorial
on how to build a VueJS pagination component.
Security - Access Tokens
RESTful services by definition are stateless, therefore you can't handle login authentication with cookies or sessions
like you would in another application. To get around this RESTful services will use access tokens to handle
authentication via an Authorization
header parameter. There are generally two types tokens, direct access tokens and
refreshed tokens.
JWT - Access Token
An access token will be returned by the API on successfully authenticating the user, this token is supposed to be short lived and used to access the application during the "logged" in the duration of the user. The next time the user comes to log in, a different access token will be used.
Refreshed Tokens
Refresh tokens will be returned when the user logs in but will also be hashed and stored in the database, you're then able to attach an expiry timestamp and a user to this token. The benefit of having a timestamp attached to the token is that you can revoke access to your application by simply expiring the token. Refreshed token are supposed to be longer life tokens and used for a client to request a short life token from the API. This means that we no longer need to use the username and password to request a short life token we can use this refresh token. You can also refresh the expiry of this token on each login which means the user doesn't need to log in each time they use your application. For further readings on access tokens it's important to be familiar with the OAUTH specification.
Throttling
Having some sort of throttling protection is very important for your API. You need to make sure people can't just continually call your API with 100s of requests a second. You should guard against this by making sure the API will fail if there too many requests per minute. If you're a Laravel user there is an inbuilt middleware to handle throttling by limiting the requests to 60 a minute, by using the middleware \Illuminate\Routing\Middleware\ThrottleRequests::class This will return some handy headers that can be used by your clients to work out how many requests they're allowed to make. - X-Rate-Limit-Limit - The number of allowed requests
- X-Rate-Limit-Remaining - The number of remaining requests
- X-Rate-Limit-Reset - The number of seconds left in the current period
How To Display Errors
It's important to define a single envelope used for your error responses, this is important to your clients to have a consistent node you can use to search for errors. For example, the first code section below will have code, * message* and description. This doesn't give a standard error envelope to search for from any clients using your API.
{
"code": 1234,
"message": "Something bad happened :(",
"description": "More details about the error here"
}
I believe that it's a good idea to use code like below where you return any errors inside an error
node this way any
clients can do a check for node on the response and will know how to handle the response and if they need to display an
error to the user.
{
"error": {
"code": 404,
"message": "Page not found"
}
}
Object Responses From Updates & Creation
PUT, POST, PATCH calls will all modify data in the databases. Many APIs will return a boolean status of true to show the resource has been updated or an error to show the resource hasn't been updated. I prefer to return the updated or created resource, for example if a PATCH call was made then this has changed some data in your database, instead of returning a boolean where the client will need to make another API call to get the new resource, why not just return the new data from the PATCH endpoint. For the POST endpoints they will be used to create a new resource which means they should return a HTTP status code of a 201. Therefore you should try to return the URL to get the new resource and the newly created ID in the headers of the response. This will allow clients to be able to query the API to return the newly created resource.
Including Related Resources
Including related resources from your API calls can be very helpful to clients trying to pin together related content.
For example, if you had an API for a blog, you'll have an endpoint to get the posts /posts/100
this will return data
like.
{
"data": {
"id": 100,
"title": "The blog post",
"featured_image": 1,
"author_id": 1,
"category_id": 1
}
}
With a blog post you have links to other resources such as the author information and the category information. This
might be information you need to display on the page to show the blog post correctly. You might want to display the
author bio box at the bottom of the post or a link to the category page so the user can see related articles. In your
endpoint it's useful to allow them to add an include
parameter which will return the related
resource. /posts/100?include=author,category
This should return the included resources.
{
"data": {
"id": 100,
"title": "The blog post",
"featured_image": 1,
"author_id": 1,
"author": {
"id": 1,
"name": ""
},
"category_id": 1,
"category": {
"id": 1,
"name": ""
}
}
}
snake_case vs camelCase
It's important to decide on a standard of using snake_case or camelCase in the field names of your API. If you're return type is JSON then it's important to note that the JavaScript naming convention is to use camelCase for your field names. Although I find that snake_case is easier to read. If you look at many popular API responses you'll see many of them will use the snake_case. I suggest you go with snake_case for your field names but whichever one you choose make sure you stick with it.
Documentation
When it comes to documentation, it's probably the most important area of your API, especially if your API is externally accessible. Without good documentation users will not understand how or what data they will be able to access. The documentation should show examples of complete requests and responses to show real examples of how to use your endpoints. If you haven't used it already I suggest you take a look at Swagger API framework. Swagger Documenation Swagger is a full API framework, you can use it to define your API and can even download a starter application will all the endpoints you need straight from a config file. View the click below to see a demo of a Swagger config and the generated documentation from the YAML config. Swagger Editor