REST API – Best Practices
I’d like to talk about how awesome REST API’s can be to develop, but first and foremost, I’d like to emphasise that this is all based on both research & development, in addition to experience.
Introduction
First of all, I’d like to talk about the different levels of maturity when it comes to developing a REST API.
In the image above, it illustrates the different levels of maturity with regards to developing & engineering a REST API. But it may not be entirely clear as to what each level refers to, so let’s begin.
Level 0
This is considered to be the most basic form of REST API, typically speaking this is implementing an API through a single URI, where the endpoint accepts say some payload or some query parameters. It’s not rare to see this level of maturity receiving say a JSON payload & deciding what to do with zed payload in the business logic side of things.
Level 1
With this amount of maturity, often you can expect to see many different URI’s, with the level 1 maturity being applied, you may have a single URI, but it may employ a single HTTP method. Usually, again, this employs mostly the POST method, as it allows clients to provide some payload to the server, this payload can then be used to conduct any of the CRUD operations.
Level 2
In my professional experience, this tends to be the average or most common level applied to API’s, this is where you have a number of URI’s & a number of HTTP methods. So now, you could essentially have your GET requests for ‘api/dogs‘ do something completely different to your POST request for the same URI. This implements the industry standard, the minimal expected level of sophistication with regards to having a REST API.
Level 3
This is somewhat rare to find with a lot of API’s, or at least in my experience anyway, this is where you introduce something referred to as HATEOAS(Hypermedia As The Engine Of Application State), not to worry, it’s nothing all that scary. HATEOAS is essentially including some form of documentation & hypermedia in your responses, an example being where you have the URI ‘api/dogs‘, the response may contain a list of possible links. Inside this list of links, you can expect to see the HTTP methods that this URI supports, some form of description, a relationship to the current URI, etc. With HATEOAS more often than not, people tend to stick to some form of standard, this standard is often called HAL, this allows us to stick to a tried & tested format, perhaps some may argue a format that developers would expect.
State
When managing state, this can be tricky & ofcourse this will vary massively depending on the project, an example being how if you’re taking advantage of FaaS, then you’d want to manage the state differently to if you were handling everything on your own infrastructure. Generally we try to use tokens, whether these tokens include some session state or whether they simply include authorisation & authentication values, who knows.
Generally I try to leave as much of the session state to the client as possible, the only features that I refuse to allow the client side take control over is anything that’s related to security, for obvious reasons. As an example, for a highly secure application we don’t exactly want to allow the client to tamper with authorisation or authentication. The reason being that this makes things like session hijacking a lot easier, to avoid this, you can simply salt & hash some cookie value, making it so that the cookie only works over HTTPS & it’s HTTP only, etc. This just adds a little more security to the application as JavaScript can’t touch the cookie, so session hijacking through some form of script injection is a just that much harder.
Security
Having spoken about security in the recent section, I feel it’s only suitable to talk about it more in depth now, with regards to security, one thing I will say again is that it depends. If you want to include as much security as possible, then OAuth 2.0 may be what you desire, however if you want to take complete control over the API, then you may want to implement digest authentication, the security may not be a big deal, you may want to just implement basic authentication.
But talking from my experience, I typically find that the best for my use cases has always been either basic authentication or digest authentication. Basic authentication is essentially where the entity that’s consuming your API will provide an Authorization header, this header will include the term ‘Basic‘ & the relevant token, usually some username & password with Basic authentication. Often developers will translate the credentials into a base 64 string, this isn’t to implement some layer of encryption because it’s so easy to translate a base 64 string back to text. But rather, developers do this to ensure that any non standard characters are sent across successfully rather than having to deal with surrogate pairs & God knows what else. Then with digital digest, it’s very similar, only this does encrypt the credentials, thus being more secure than basic, yet still somewhat simple to manage & maintain. The other difference being that the Authorization header no longer contains ‘Basic‘, but instead ‘Digest‘.
There are many different forms of authentication & authorisation that you can implement & many of the standards/frameworks have any advantages & disadvantages. So, I’d now like to talk about other security aspects, such as including some form of rate limiting, firewalls, proxy servers, etc, personally being a software engineer I like to do most things by code, but that’s not always the best way to do things. As an example, rather than trying to implement my own firewall, obviously I’m going to use some hardware solution for this. Then for things such as rate limiting, of course I’d like to implement my own solution in code, although this may add more application complexity, it may be better to use a third party service. An alternative to using an external entity to take care of rate limiting could be to investigate NGINX. With regards to proxy servers, you may desire to have such infrastructure to filter incoming & outgoing traffic, thus potentially removing some threats before they even reach the application layer.
Versioning
This is something that I’ve seen many a developer overlook, including myself in my more junior days, but since working in a more enterprise environment, versioning my API’s has never been better. It instantly allows me the ability to allow other applications to utilise legacy code, if necessary for whatever reason(s), not to mention if introducing a potentially breaking change, it shouldn’t really affect stable releases. This allows your ecosystem to mature & evolve with less risk, rather than having a group of developers take the existing code & start again from scratch, it’s much better to allow developers to essentially add another section to the API. It’s literally quite as simple as having some class/file/function/method pointing to ‘api/v1‘, then some other pointing to ‘api/v2‘, since we can assume that v1 is stable, it’s been through UAT, passed all the requirements, etc, it doesn’t really matter if v2 may introduce some bad code or some code that introduces bugs, etc, because v1 is essentially totally separated from v2.
With regards to versioning, I always find using tools such as swagger to be beautiful, if I can, at the base path of a given version, such as ‘api/v1‘, provided that it’s a get request, I’ll return the swagger documentation. Again, this allows us to provide documentation for each version easily, because using swagger annotations on our classes, again, it doesn’t matter if there’s huge changes in v2, the documentation exists & it’s being served at ‘api/v1‘.
URI Naming
Personally, I like to make everything plural, the exception being say where you want to conduct some specific action, but for the basics, such as getting data, I like to make sure that my URI’s use a plural naming convention. An example being:
URI | Method | Description |
/dogs | GET | Retrieves a list of all known dogs. |
/dogs | POST | May add a dog to the list of all known dogs. |
/dogs/{id} | GET | Will get the specific details for a given dog ID. |
/dogs/{id} | PUT | Will update a specific dog for a given dog ID. |
/dogs/{id} | DELETE | Will remove a specific dog from the list. |
/dogs/{id}/buy | POST | Will create an order for a specific dog. |
I’m sure you get the idea, maybe the given example above may be a little abstract or a little silly, perhaps you can replace ‘dogs‘ with say ‘products‘, however you can visualise it best. But with the example aside, I’m sure you can see how through using such a naming convention, it allows your API to be somewhat self documented by using clear, clean & simple URI names. It also ensures that your API is consistent, I mean even if you disagree with my personal preference of using plural names, if you stick to some convention or another, your API will instantly be cleaner for it. If you’d like to read more into naming conventions, you could always read other articles such as this.
Application Architecture
Now, when it comes to writing the code, I like to keep my controller layer as clean as possible, the way I like to visualise it is that your endpoint(s) are in some class or some set of classes. Which will then communicate with your service layer, which then communicates with your repositories or DAO’s, basically I like to follow something along the lines of MVCS. I find that such application architecture allows the API to be engineered in such a way where it’s incredibly clean to work on. Implementing that amount of abstraction allows the code in the controller layer to be incredibly readable. I also find that it makes it easier for others to maintain, as rather than having all of your code in one place, split it out based on the responsibility, the way I see it is that your controller layer should be minimal, maybe have some security code in there & the code that maps your endpoints to the given responses, but the rest, I like to leave to either helper classes or my service layer.
Of course this is a topic that can vary massively, some people may agree word for word, others may find my ideas disgusting, this is purely just my personal preference. I think that alsong as the code you write is clean, concise & simple, you can’t really go too far wrong with that.
Conclusion
Provided that you follow some of the best practices that I’ve covered & other best practices that you can find online, in addition to clean, simple & concise documentation, your off to a great start. If you’re working on a legacy system, personally I’d try to implement a new version on top of the existing API, making it easier to work with & the code you implement not affecting the existing legacy system. Provided that you are working on a legacy system, I’d suggest to look to remove the legacy code as soon as possible, but I appreciate that this may not always be as easy & straightforward as just removing some set of classes.