Creating a REST API
You've heard of REST before, but what is REST? How does it work? Can you build one from scratch? Does serverless make your REST life easier?
In this chapter, learn about REST best practices and finish with a small implementation you can try right now. I left mine running ✌️
What is REST
REST stands for REpresentational State Transfer. Coined in Roy Fielding's 2000 doctoral thesis, it now represents the standard approach to web APIs.
Fielding says REST is an architectural principle. It recommends how to build a scalable web system, but there's no official standard.
You may have noticed this in the wild. RESTful APIs follow similar guidelines and no two are alike.
These days any API that uses HTTP to transfer data and URLs to identify resources is called REST.
Here are the 6 architectural constraints that define a RESTful system 👇
- client-server architecture specifies a separation of concerns between the user interface and the storage of data. This simplifies both sides and lets them evolve independently.
- statelessness specifies that the protocol is stateless. Each request to the server contains all information necessary to process that request. Servers do not maintain client context.
- cacheability specifies that clients can cache any server response to improve performance on future requests. Servers have to annotate responses with appropriate caching policies via http headers
- layered system means that, like regular HTTP, a RESTful client shouldn't need to know whether it's talking to a server, a proxy, or load balancer. This improves scalability.
- code on demand (optional) says that servers can send executable code as part of their responses. You see this with web servers sending JavaScript.
- uniform interface is the most fundamental and means that clients can interact with a server purely from responses, without outside knowledge.
A uniform interface
Creating a uniform interface is the most important aspect of a RESTful API. The less clients know about your server, the better.
Each request identifies the resource it is requesting. Using the URL itself.
Responses send a representation of a resource rather than the resource itself. Like compiling a set of database objects into a JSON document.
All messages are self-descriptive meaning both client and server can understand a message without external information. Send everything you need.
A resource's representation includes everything needed to modify that resource. When clients get a response, it should contain everything necessary to modify or delete the underlying resources.
Academics say responses should list possible actions so clients can navigate a system without intimate knowledge of its API. You rarely see this in the wild.
Designing a RESTful API
The trickiest part of building a RESTful API is how it evolves over time. The second trickiest is keeping it consistent across your app.
As your system grows you're tempted to piggy-back on existing APIs and break resource constraints. You're likely to forget the exact wording and phrasing of different parts.
All that is natural. The important part is to start on the right foot and clean up when you can.
Engineers are human
When engineers can do something with your API, they will.
They're going to find every undocumented feature, discover every "alternative" way to get data and uncover any easter egg you didn't mean to include. They're good at their job.
Do yourself a favor and aim to keep everything consistent. The more consistent you are, the easier it will be to clean up.
That means
- all dates in the same format
- all responses in the same shape
- keep field types consistent, if an error is a string it's always a string
- include every field name and value in full
- when multiple endpoints include the same model, make it the same
Here are tips I've picked up over the past 14 years of building and using RESTful APIs.
URL schema
Your URL schema exists to solve one problem: Create a uniform way to identify resources and endpoints on your server.
Keep it consistent, otherwise don't sweat it.
Engineers like to get stuck on pointless details, but it's okay. Make sure your team agrees on what makes sense.
When designing a URL schema I aim to:
- make it guessable, which stems from consistency and predictability
- make it human readable, which makes it easier to use, debug, and memorize
- avoid query strings because they look messy and can make copy paste debugging harder
- avoid strange characters like spaces and emojis. It looks cute, but it's cumbersome to work with
- include IDs in URLs because it makes debugging and logging easier
The two schemas I like, go like this:
https://api.wonderfulservice.com/<namespace>/<model>/<id>
Using an api.*
subdomain helps with load balancing and using special servers for your API. Less relevant in the serverless world because providers create unique domains.
The optional <namespace>
helps you stay organized. As your app grows, you'll notice different areas use similar-sounding names.
You don't need a namespace for generic models like user
or subscription
.
Adding a <model>
that's named after the model on your backend helps you stay sane. Yes it breaks the idea of total separation between client and server, but it's useful to maintain consistency.
If everyone calls everything the same name, you never need to translate. 😉
The <id>
identifies the specific instance of a model that you're changing.
Sometimes it's useful to use this alternative schema:
https://api.wonderfulservice.com/<namespace>/<model>/<verb>/<id>
The verb specifies what you're doing to the model. More on that when we discuss HTTP verbs further down.
Data format
Use JSON. Both for sending data and for receiving data.