Web API Best Practices | Quick Answers For Designing REST Services

 

Shane Delamore

Shane Brinkman-Davis Delamore is a master of the craft and passionate about UX design and programmer productivity.

Updated Jun 16, 2020

A lot has been written on the topic of API best practices, especially for the web. In this post we’ve gathered the best principles and resources to guide your next RESTful API project in the right direction.

Key Takeaway

You'll make a good REST API if you just use this pattern:

  • create post: POST '/api/posts'
  • read post: GET '/api/posts/{post_id}' 
  • update post: PUT '/api/posts/{post_id}' 
  • delete post: DELETE '/api/posts/{post_id}' 
  • query all posts for user: GET '/api/posts?user_id={user_id}' 

What Protocol to use? REST or GraphQL?

While REST is the popular current standard, it isn’t the only option. GraphQL is an up-and-coming standard which a lot of good points. One of the main advantages of GraphQL is the ability for the client to control exactly what data is returned from a request. If your client is going to be requesting large or complex data structures, you might look into using GraphQL.

However, for most projects, REST is still an excellent choice. There is a lot of existing tooling available and it naturally integrates with the rest of the web. REST is really just the web protocol (HTTP) minimally adapted for programs rather than humans. GraphQL is really a whole new protocol, even though it is built on top of HTTP.

Always Use JSON for Both Request and Response Data

Just use JSON. Send and receive all your data as JSON and you and your customers will be happy. PUT & POST should accept JSON in the request body. All GET requests should return JSON.

  • API Horrors: XML
  • API Horrors: Form-Data - though easy in the browser, not all client libraries make it easy to send form-data. 

REST and HTTP Methods - It’s all just CRUD

If nothing else, if you follow this section you’ll be in good shape. For almost all applications you really just need the CRUD operations plus queries (create, read, update, delete & queries).

Follow this pattern for each record type you want to make accessible via the API. In this example, the record type is "posts." The record type should be plural.

CRUD:

  • create post: POST '/api/posts'
  • read post: GET '/api/posts/{post_id}' 
  • update post: PUT '/api/posts/{post_id}' 
  • delete post: DELETE '/api/posts/{post_id}' 

Queries:

  • get all posts: GET '/api/posts' 

Usually you don’t want all records for given type. Pass query parameters as, well, HTTP query parameters:

  • get all posts by user_id: GET '/api/posts?user_id={user_id}'

Everything you ever wanted to know about REST and HTTP methods: REST – PUT vs POST – REST API Tutorial

API Design Beyond CRUD - Complex Actions and Queries

If your application needs mutation actions that update more than one record or queries which return more than one record type, you’ll be going off the beaten path; REST doesn’t have clear guidelines for how to proceed.

A word of warning. If you think you need to do something that doesn’t fit in the basic REST CRUD+Query patterns, think very hard about it before proceeding. You really can get a lot done within basic REST, and the better you conform to standard REST, the more client-side apps will work seamlessly with your API. That said, there are often good reasons to add a little extra functionality to an API beyond standard REST. Just be sure to only do it AFTER you already support all the standard REST endpoints that make sense for your application.

If you’ve decided to add some non-standard endpoints, the most important thing is to define a general strategy up front and then stick to it.

Example: One way to define complex end-points is to use a "resource-dash-action" naming scheme:

  • complex query: GET '/api/posts-histogram' 
  • complex action: POST ‘/api/posts-erase-old' 

Just remember that GET methods can be cached by the browser. The URL, including query-string, should always return the same results if the source data hasn’t changed. Further, GETs should never, ever mutate your data.

API Design DOs and DON’Ts

Here are a few quick tips to get you rolling. REST is all about defining your endpoints - the URLs you’ll use to read and modify your data.

  • Do: Make one endpoint for each type of resource. If your API primarily provides access to a back-end database, you’ll want to make an endpoint for each table you want to make available over the API.
    • Examples:
      • /api/posts
      • /api/users
      • /api/comments
  • Don’t: Make more than one endpoint for the same type of resource.
    • API Horrors:
      • /api/post/{post_id} for reading one post and /api/posts for reading a set of posts
  • Hierarchical endpoints with multiple IDs are not a replacement for the basic type+id endpoints. The problem is, in order to fetch a sub-resource, you have to know the parent resource’s ID. This adds substantial complexity to clients as they now have to track compound resource IDs:
    • API Horrors
      • /accounts/{account_id}/memberships/{person_id}
    • Exception:
      • As long as there is only one ID in the URL, patterns which select related records can avoid the multi-ID URL headache. For example, this fetches all memberships for an account. 
      • /accounts/{account_id}/memberships
  • You could add hierarchical endpoints in addition to type+id endpoints. Below is an example. However, we'd recommend against it. Keeping things simple is key to a successful API. KISS!
    • If you implement these first:
      • get comment GET /comments/{comment_id}
      • create comment POST /comments
      • update comment PUT /comments/{comment_id}
      • query comments GET /comments/?{query_params}
    • It's reasonable to add these convenient shortcuts:
      • create comment on postPOST /post/{post_id}/comments
      • query comments on postGET /post/{post_id}/comments

Sessions

JSON Web Tokens, or more commonly called JWTs (“jots”) are an open, industry standard method for securely and efficiently tracking sessions. There are great libraries out there for most platforms, and most the time the API client doesn’t need to understand them. The server generates them, returns them to the client, and all the client has to do is pass them back to the server, untouched, on subsequent requests.

  • API Horrors: Don’t use cookies. Many non-web-browser clients have poor or no support for cookies.

Security - Require HTTPS 

If you are using sessions, you simply have to use HTTPS. Sessions are secure only as long as no-one else can copy them. That’s only true if you are using an encrypted end-to-end connection.

Tooling, Documentation and Swagger

If you want your API to get used, it has to be documented and it has to be easy to try out and experiment with. Swagger is a nice answer to all of the above. Swagger is a standard way to document REST APIs in JSON format. Further, Swagger provides an excellent web-interface module you can deploy on your site for both documentation and experimentation purposes. There exists tons of tooling server-side for automatically generating the JSON definitions. There are also client-side libraries which will consume a Swagger JSON definition and generate client-side code for using that API.

You should really use a server-side framework that supports Swagger exports. One of the biggest problems with APIs is maintaining their documentation over time. If your documentation is generated directly from your server’s code, you know it’ll always be up to date.

Screen shot of Swagger's web UXSwagger's automatic documentation & testing interface

Testing Your API with Postman

There are some great resources for testing REST APIs. One of our favorites is Postman. This tool makes it easy to explore your REST API organically, but since it logs all your calls, it's easy to go back and automate what you did to make tests.

Using Postman to craft a GET request

Postman is fully configurable for any type of HTTP request

API Root

Often an API lives at the same URL as a human-readable website. In these scenarios, the convention is to put the api at /api/*. Alternatively, if your API is going to live on its own domain, api.myhost.com/* is also a solid option.

Versioning

A common way to do versioning is to change the API root: /api/v1/* , /api/v2/* etc. The nice thing about this scheme is it’s OK to worry about it later. You can start with /api/* for v2 and later add /api/v2/*

Four REST API Versioning Strategies | xMatters

Summary

In summary, here’s what you really need to know:

  • Use the standard REST CRUD + Query pattern as much as you can.
  • Use JSON for all data going in and out of your API.
  • Use JWTs for sessions.
  • Require HTTPS
  • Use Swagger for documentation and interactive learning for new developers
  • Use /api/v1/* for your API versioning

Recommended further reading: Best Practices for Designing a Pragmatic RESTful API

How can we help?

Can we help you apply these ideas on your project? Send us a message! You'll get to talk with our awesome delivery team on your very first call.