We are in the era of microservices. Organizations are breaking down web applications into small, independent cloud services that communicate with each other seamlessly. APIs are at the crux of this architecture, since most of the data exchange between these services happens through API requests.
An API (Application Programming Interface) outlines the set of rules that the application will use to communicate with its internal components as well as with other applications. If you are in the process of building an application, it makes sense to expose your app's APIs, and design them in a way that makes it easier for developers to use with other applications.
While there are no formal guidelines or standards that govern how you have to build your APIs, there are certain principles that can help you develop APIs in the most commonly accepted ways. Most ideas can be grouped under the following four principles of API design:
In this article, we will cover the four basic principles of API design:
- Build APIs using common and widely accepted standards
- Keep your responses easily understandable and consumable
- Secure your APIs
- Support with good documentation
1. Build APIs using common and widely accepted standards
Most cloud applications have adopted REST as the preferred approach to transfer data over API requests. REST has been around for quite some time. Some practices have worked well and are still widely used, while some are not. Let's look at some of the commonly accepted standards in REST API design.
a. Follow basic conventions for request paths
Request paths should specify the request being made and should be defined in a manner that's easy to understand, without leaving any room for confusion.
Use resource names
Adding verbs to your request path is redundant, since the type of request (e.g., GET, POST) defines that clearly. Use just the resource names in your API path to avoid any additional text.
Bad:
/api/create-products
Good:
/api/products
Use plural forms
Plural forms can be used for managing single items and collections both. The use of singular forms does not give an idea if there are multiple items within, leading to confusion. And having both singular and plural forms will just double your API requests list. Use the following method to use plural forms only, so that the request is clear and intuitive.
Bad:
/api/product
Good:
/api/products
/api/products/:product_id
Use nested hierarchy
Nesting helps fetch related items and subitems with ease. It also gives a clear indication about the relationship to the user.
Bad:
GET /products/
GET /product-reviews
Good:
GET /products/:product_id/reviews
PUT /products/:product_id/reviews/:review_id
b. Use JSON as an exchange format
Go with JSON. It's lightweight and flexible. It is easily consumed by various channels and is a widely used format. You can use other formats too, such as XML, CSV, and HTML, but JSON trumps them all because it is widely accepted, human-readable, and it offers easy data execution. Set "Content-type": "application/json" in the header for your request and response.
c. Version APIs
When releasing new features, changes, or bug fixes, make sure you version your APIs, and list down breaking changes clearly. This ensures that your changes do not impact users' implementation. One of the standard ways to do this is using Semantic Versioning.
Also, for all the versions, use version numbers after the base URL. This is least likely to impact the relative path of your endpoints already used by the users.
Example:
http://api.domain.com/v1/products
d. Offer ways to filter, paginate, sort, and search data
Databases can get really big. Your APIs should provide ways that allow users to narrow down to exactly what they want, instead of returning all at once, else it may soon exhaust their bandwidth or even bring down your system.
Users may want filtering options so they can fetch the required data from a huge data set. You can offer many ways to filter data through requests.
Example: Allow users to fetch products by categories:
/v1/products?query={categories: ["category1", "category2"]}
Provide the ability to paginate responses, so even their users fetch data in sets. This can be done by using the 'skip' and 'limit' parameters.
Example:
/v1/products?skip={skip_value}&limit={limit_value}
Allow users to get items in the order of their choice. Provide ways to sort items based on various fields, such as date, price, name, and more.
Example:
/v1/products?sort=-price,updated_at
Give your users a way to quickly search for relevant data by allowing them to perform a full-text search against all values of all the fields.
Example:
/v1/products?text={{search}}
2. Keep your responses easily understandable and consumable
This is probably the most important part of your APIs. How easily a developer is able to work with your APIs depends a lot on how well structured and convenient your API responses are. Just having a few things in place can ensure the responses are up to the mark.
a. Use standard HTTP response codes
HTTP provides status codes that your APIs could use to tell the users what just happened with the request that they executed. It also helps them easily understand if the API request was a success or failure. You can use the ranges of codes for common responses:
- Information: 100–199
- Successful: 200–299
- Redirect: 300–399
- Client errors: 400–499
- Server errors: 500–599
b. Define a proper response body
Resources in the body should have proper metadata, and should be wrapped according to the path. For JSON keys having multiple words, use either hyphens (-), underscores (_) or camelcase. Use any one and use it consistently in all the responses:
Example:
{
"users": [{
first_name: "John",
last_name: "Doe",
…
}, {
first_name: "Jane",
last_name: "Doe",
…
}],
"skip": 0,
"limit": 20,
}
c. Have clear response headers
Defining proper response headers helps provide proper information about the server's behavior. If you are going to expose your APIs externally, it is important to support CORS (cross-origin resource sharing) header. Cache headers help the user determine if the data was served from the origin or cache server. You can also use the "X-Request-Id: {{TRACE_ID}}" header in the response. It helps trace the entire lifecycle of the request while debugging.
d. Remove unnecessary response headers
Not all headers are required. You can remove unnecessary response headers, such as "Server", "X-Served-By", or "X-Powered-By", and save a small amount of bandwidth for your users.
3. Secure your APIs
Use SSL/TLS for securing all your endpoints and resources. No exceptions. The communication between the client and the server should always be private, since it may involve sensitive data. SSL/TLS ensures that the data that you send and receive is encrypted and safe.
To prevent your APIs from unwanted abuse and attacks, have certain limits in place. Provide a definite way for users to know these limits by having limit headers such as "X-RateLimit", "X-RateLimit-Remaining", and "X-RateLimit-Reset".
4. Support with good documentation
Make your API docs publicly accessible. Avoid PDFs. Avoid docs behind authentication. This makes it very difficult to find your docs, and even difficult for users to search for relevant content.
Your API docs should have references to all the API versions that you support. And it should call out breaking changes explicitly.
The documentation should provide an overview (covering basic conventions, base URLs, etc.) and should list down error codes with possible use cases. It should also cover the whole request-response cycle.
To make it easier for readers to use your APIs, make endpoints easily usable by providing simple ways to copy-paste URLs or by providing curl examples. Or a better strategy would be to provide Postman collections or OpenAPI specifications, so users can try out your APIs their own way.