When building modern web applications, a common practice is to have a front-end server act as a REST API. This server generally exposes certain endpoints. It accepts HTTP requests and returns JSON responses. The JSON is then consumed by a client-side JavaScript framework, which uses it to display information to the user.
Today, we’re going to talk about building RESTful APIs and some best practices that I have learned by making mistakes over the years. Most of these are pretty simple but often overlooked.
1. Separate your Response into meta and data fields
When sending a JSON response, it is useful to separate meta information (information about the request/response), from the payload that you are sending back.
// GET /api/users/1 { "meta": { "type": "success", "code": 200, "message": "", "responseId": "43qtf3hk03y34gm41" }, "data": { "id": 1, "name": "Joe Burns" } }
By adopting this pattern, your API consumers will be able to provide better error messages to the end user. I’ve also been playing around with providing a unique responseId
for all my responses using the uuid
module in Node. By combining this with HTTP access logs, I’m able to quickly debug problems in my API.
Note: I’ve been helpfully reminded on Reddit that you can alternatively use HTTP Response headers to store this meta information. That’s probably more “pure”, but I suggest choosing one convention and sticking to it.
2. Use plural names for all resources. Don’t use verbs
Give your resources plural names so that it makes more sense when querying. Here’s an example to demonstrate the point.
// Good /api/customers: Fetch all customers /api/customers/25: Fetch customer with ID: 25 // Bad /api/customer: Fetch all customers, but no plural. /api/customer/25: Fetch customer with ID: 25 // Bad (don't use verbs) /api/getcustomers /api/getcustomers/25
3. Use proper HTTP Status Codes
Don’t always return a 200 status. Here’s a handy list showing what HTTP status codes to return with:
- 200 Ok: Use this if everything worked well.
- 201 Created: Use this for POST requests when you create a new resource. This is better than the generic 200.
- 204 No Content: Use this when you have successfully deleted a resource.
- 400 Invalid Request: Use this if all the parameters were not provided.
- 401 Unauthorized: Use this if you had a permissions issue.
- 403 Forbidden: Use this if you had a permissions issue.
- 404 Not Found: Use this if you were not able to find the resource.
- 500 Internal Server Error: Use this if your server encountered an error. If you do return with a 500, make sure you return a human-readable
message
that your API consumer can use.
I normally add the status code to the meta
object as well.
4. Use proper RESTful Methods
- Use
GET
for retrieving resources. - Use
POST
for creating new resources. - Use
PATCH
for updating resources. - Use
DELETE
for deleting resources.
This makes it simpler to define the API. You won’t need to do something like this:
//BAD /api/getCustomers /api/getCustomers/:id /api/updateCustomer/:id /api/deleteCustomer/:id // Good GET /api/customers POST /api/customers PATCH /api/customers/:id DELETE /api/customers/:id
5. Version your API
This isn’t necessary if you are building internal APIs, but it is a must if you want to expose your API to consumers outside your company.
/api/v1/customers/:id // If you make non-backward compatible changes to this // endpoint, don't change v1. Just create a new endpoint. /api/v2/customers/:id
Users expect their APIs to be predictable, and by versioning them, you can make changes while ensuring older versions are backward-compatible.
6. Use query strings to manipulate data
Query strings should be used for sorting, searching and filtering data. You can also use it to add pagination via limit
and offset
query strings. Filtering should generally be done by the attribute name of the resource.
// Get all customers. /api/v1/customers // Get all customers sorted by name in ascending order. /api/v1/customers?sort=name&direction=asc // Get all customers whose first name is Jack. /api/v1/customers?name=Jack // Get first 30 customers sorted by name. /api/v1/customers?sort=name&direction=asc&limit=30 // Get 31-60 customers sorted by name /api/v1/customers?sort=name&direction=asc&limit=30&offset=30
The examples above obviously aren’t the only way to use query strings. You can create your own conventions.
I would say this is pretty good for some cases, but not all of them – mainly when we talk about non-crud systems:
https://www.thoughtworks.com/pt/insights/blog/rest-api-design-resource-modeling
This is a good point. You’re right, I primarily had CRUD in mind.