Introduction
APIs are created in ways that redefine and stretch products, services, and organizations. Many businesses have asked themselves questions, like:
- How can we get more modularity?
- How do we take advantage of new tools and technology?
- Are we able to scale our infrastructure?
- How do we know that we’re doing it right?
Often, the answer is in ‘the Cloud’ with APIs and decoupled applications. Similarly, companies are taking an API-first approach and building their core product around an API.
Regardless, the last question in the series above creeps into many peoples’ minds; are we doing it right?
In this article, we’ll dig a little deeper into API design principles and best practices. Also, we’ll provide some examples and, hopefully, by the end, you’ll be more confident with moving forward with your APIs.
If you did not arrive here from What is API Design?, I recommend glancing through the article to gain context for design principles and best practices.
API Design Principles
Principles inform best practices. The best practices may change, but principles persist over time1. This does not mean that principles are immutable. However, like a compass, they allow designers to navigate new space while keeping their bearings.
In this section, let’s explore some API design principles in depth.
Choose an API Approach
Choosing an approach to API design is a principle because some level of planning should always be a principle. The two types of approaches when designing APIs are:
- Design-first
- Code-first
Design First
Design first approaches try to represent the API in a specification before writing the code. Common API specifications include:
- OpenAPI or OAS (RESTful APIs)
- RAML (RESTful APIs)
- AsynchAPI (WebSocket APIs)
The specification becomes the blueprint for the API and contains information like:
- protocols
- schemas
- parameters
- headers
- operations
More importantly, the specification is machine-readable. This allows API tools to consume the specification to generate documentation, tests, API clients, and mock APIs.
Next, let’s compare some of the specifications listed above.
OpenAPI and RAML
Both specifications are designed for use with RESTful API architectures. Furthermore, looking at the two specifications side-by-side may not show any obvious differences because they both use YAML (although you can also represent OpenAPI in JSON).
Additionally, both specs are based on HTTP, so they have similarities in defining routes, parameters, and response objects. The main difference between the two specifications is that RAML focuses on code reusability2.
The specification has an !includes
operator that pulls in data objects (i.e., response objects) from other files3. This means that schemas or data objects are more reusable across specifications.
That said, RAML is not a clear winner over OAS. Both specifications are widely used and supported. Furthermore, Uri Sarid, co-author of RAML and CTO of Mulesoft, has written, “To explicitly bridge these two approaches [RAML and OAS], MuleSoft has built an open-source API Modeling Framework (AMF), available now under the Apache License, which reads and writes both RAML and OAS. […] we [Mulesoft] are explicitly committing to interoperability between RAML as a modeling language and OAS as a description language.”4
OpenAPI and AsynchAPI
Unlike the comparison to RAML, the AsynchAPI specification is for a different type of API than OpenAPI. Below is a helpful graphic from AsynchAPI’s documentation comparing the two specifications.
You don’t need to understand AsynchAPI to see the similarities. Not coincidently, many parts of the AsynchAPI specification were based on the OpenAPI specification5.
AsyncAPI is an open source initiative that seeks to improve the current state of Event-Driven Architectures (EDA). Our long-term goal is to make working with EDAs as easy as it is to work with REST APIs.
AsynchAPI Docs, Getting Started
Therefore, if you’re building an API for real-time data that handles the interaction between publishers and subscribers, the AsynchAPI specification could be your choice.
Code First
In a code-first approach, business requirements guide the code implementation6. You may be thinking, why would anyone code first when they could just plan it out?
Code first approaches are similar to how an agile team may work to tackle complex business requirements. It may be a waste of time to plan out an entire API specification, only to use 5% of the original plan. There may not be a pre-made specification, but there is still planning with user stories and requirements.
An example of an emerging technology that focuses on a code-first approach is the Python framework FastAPI. The framework builds the OpenAPI specification as you develop routes or create response schemas.
Code first approaches do not abandon API specifications. Instead, they use tools to generate the specification as they are building or after they complete the API build.
Design APIs Around Resources
The adage to design APIs around resources is nested in how the internet is set up. The internet has verbs, built-in, that describe actions performed by API requests. We’ll provide more explanations and examples on this topic in the best practices section.
It’s important to identify the resources that an API facilitates. In a previous article, I described a simple resource like a User or a Pet. These examples were based on the common Swagger Pet Store example specification. However, it’s typically not that easy and straightforward.
Regardless, even with an API that performs AI or statistical inference, we can locate the resource as the model. It might be helpful to look for the nouns that are involved with the API.
Nested Resources
It’s difficult to find a consensus on nesting resources with APIs. An example of nested resources could be /user/jarrett/pet/{petId}
, where a user has the nested resource pets.
Consequently, I cannot give you a clear principle, but there are some guidelines on different ways to nest them. The only principle to draw from nested resources is to nest with caution. In the best practices section, we’ll talk more about nested resources.
Don’t Repeat Yourself or Others
In this article, we mentioned two types of specifications for REST APIs. Also, we talked about AsynchAPI specification for event-driven architectures (another common event-driven architecture technology is Apache Kafka).
The amount of tooling, technological help, and add-on technology, like GraphQL, is enormous for APIs. The web API ecosystem has grown over the past two decades to automate and improve APIs’ design, development, and implementation.
Not repeating yourself is a principle of software development, but it applies equally to API design. For example, reusability is already built into specifications, like RAML.
It’s probably unlikely that you will need to invent an architecture, redesign how the web interfaces with your API, or create your own API specification. In API design, remember to leverage the technologies available (many of which are open source).
Platform Independence7
Interacting with an API should not depend on the type of client application. In other words, the API’s internal workings should not reject client interactions because of the operating system or programming language.
You can achieve platform independence by following standard protocols and defining a data change format7.
Service Evolution7
The principle of designing for service evolution comes from the layer of abstraction that the web provides. We can change what the API route is doing without changing the URL for the route, the type of returned data, or how the client interacts with the interface.
For example, one of the REST architectural properties is the “…modifiability of components to meet changing needs (even while the application is running)”8 .
There might be a personality score for a user at the URL http://api.example.com/user/personality-score
. Over time, we discover better ways to calculate that score or even have a proxy server that sends the data to a microservice. The point being, we have many possibilities for how we can achieve calculating the score. Regardless of our choice, the client applications will always find a score at that same URL.
The API will evolve. Therefore, it’s important not to stretch current implementation to support a trivial use case. Additionally, an API should implement some sort of versioning to account for breaking changes.
In the next section, we’ll look at best practices. I hope you can see how the practices are informed by the principles laid out in this section.
Best Practices
Keep URL Simple[9 ][10]
Companies will keep their API as a subdomain or route on their organization domain. For example, a company named API Designers has a website https://apidesigners.org
. The domain is simple, and we want to keep it that way. If API Designers had an API, we would expect it to be found at either:
https://api.apidesigners.org
https://apidesigners.org/api
It’s more common to see the first URL. The top URL uses a subdomain, which DNS records will direct to a different server (the API server). However, either works well for the base URL of the API.
Building on this example, let’s talk about API versioning and its place in the URL.
Include Version in URL
Although not stated, using version control with your API is a best practice. Your API specification should include the version number that the spec applies to. The version maybe 1.4.2
in the spec, but we only want the major version in the URL.
It’s only necessary to include the major version of the API in the URL because there should not be any breaking changes introduced in minor or patch versions.
It may be helpful to read about software versioning to understand this section.
Therefore, our API’s URL for version 1 should be:
https://api.apidesigners.org/v1
Using the full word version is unnecessarily verbose, and it’s understood that ‘v’ is short for version. When we launch version 2, with all-new features, the URL will be:
https://api.apidesigners.org/v2
Use Nouns and Not Verbs[9 ][10]
The web has built-in action verbs for resources on the internet. Those verbs include GET, PUT, PATCH, POST, DELETE. Some of the verbs are easy to understand. However, often it’s easy to mix up PUT and PATCH.
Because the actions are implicit in the type of request that users send (i.e., GET, DELETE), we do not need to name the action in the URL explicitly. For example:
- Sending a GET request to
api.apidesigners.org/v1/secrets
will return (“get”) the data for our API design secrets. - Sending a GET request to
api.apidesigners.org/v1/sercrets/getSecrets
orapi.apidesigners.org/v1/fetchSecrets
is redundant and considered “wrong”.
Words like fetch, get, query, and add do not belong in the URL. If you find that you’re stuck designing a route, you may need to use query parameters or read up on HTTP methods.
Use the OpenAPI or RAML Specifications for REST APIs
We have already spent some time discussing the two main specifications for REST APIs. It’s best practice to use one of the two for REST APIs because of their industry-wide adoption.
Often, you can generate the specifications before, during, or after development. This allows for flexibility in the API production workflow.
Provide Examples10
Example body and request objects benefit the producers and consumers of the API. If you’re developing the API, examples become mock responses. If you’re consuming the API, example response objects help visualize the data.
An example request is different than a schema. A typical schema looks like this:
{ "name": "string", "phone": "number", "addresses": { "object" "home": "string", "work": "string" }
An example of the request may be,
{ "name": "P Sherman", "phone": 2223334567, "addresses": { "home": "42 Wallaby Way, Sydney", "work": "" }
Providing examples in the documentation or schema is equally important for the request body.
API consumers have little control over the response object, but they are responsible for sending the proper request body. Having examples for your API may look different depending on your implementation or specification. However, most support examples for query parameters or body payload objects.
Return the Proper HTTP Status Code10
HTTP status codes are a part of how the internet communicates. Like HTTP request methods, the statuses are built-in. Therefore, designers should leverage them to communicate the right information. A few examples include:
- 201 – Created
- 202 – Accepted
- 401 – Unauthorized
- 403 – Forbidden
- 406 – Not Acceptable
- 501 – Not Implemented
The 201 – Created status code is often overlooked when creating resources through an API. It’s easy to return a 200 – OK when a client application uses a POST method to send data for a new resource.
Some of the status codes seem niche, but there are 10-15 that are extremely common. API consumers notice error codes more than they notice success codes. Moreover, there are subtle differences in the base error codes, and careful consideration is required for the correct use case:
- 400 – Bad Request
- 401 – Unauthorized
- 403 – Forbidden
- 404 – Not Found
- 405 – Method Not Allowed
- 406 – Not Acceptable
It’s more common to inspect a status code when there’s an error. Paying attention to error types can help communication between the producers and consumers of the API.
Set the Content-Type Header for the Response
Although APIs typically communicate with JSON, there are still APIs that use XML or return file objects. The content-type header tells the client the content type of the returned content 11. Common content-types are:
application/json
application/xml
image/png
text/html; charset=UTF-8
text/csv
Client applications use the Accept header to inform the API of the type of data they are looking for. An API could serve different data formats at the same URI. Setting the Content-Type, combined with the client’s Accept header, opens the door for content negotiation.
Support Partial Responses for Large Resources7
If you plan to serve large resources through your API, it would be best practice to support partial responses. Large response objects could cause timeouts for client applications or overwhelm their processing capabilities.
You can support large requests by setting the Accept-Ranges header for GET requests. This conveys that the GET operation supports partial requests. Then, a client application can fetch the large resource in chunks. Consequently, you’ll have to also set the Content-Length header in the response7.
An example would be a large image. Other resources, possibly stored as rows in a database, should support filtering and pagination. That’s in the next section.
What’s a reasonable response size?
We can mention a smaller design principle that helps answer this question: The ideal response size for any request is the smallest possible size that contains all the information the client needs.12 Principles are open to interpretation and adaptation. Researching your API’s consumers could inform your design process on necessary response object size.
Support Filtering and Paginating7
Most data is stored in row-like structures (relational) or document structures (non-relational). These data structures are queried in sets or by properties of the data object.
Similar to running a query in the application, the API should support query options and pagination. For resources, this could look like:
https://api.apidesigners.org/v1/secrets?specification=OAS
We query the API design secrets resource for secrets related to the OpenAPI Specification in the above example. When paginating, we can pass the parameters skip
and limit
to fetch data in successive requests.
https://api.apidesigners.org/v1/secrets?skip=0&limit=10
https://api.apidesigners.org/v1/secrets?skip=10&limit=25
Nested Resources
API design can become complicated when the resources contain nested resources. This is fairly common. Think of an online store that has products. Each product could have any number of reviews.
A couple of ways to design how we could fetch reviews for a product are:
/products/{productId}/reviews
/products/{productId}?q=reviews
/reviews?productId={productId}
As previously mentioned in the principles section, there are arguments for and against the different approaches. However, it’s a common practice to use one of the methods in the list.
Some things to consider when choosing a nested resource model are13:
- Database support for the API design
- The potential for long URLs
- Possible redundant endpoints
- Security implications for access management
- Multiple database queries for top-level and nested resources
Conclusion
API Design is an expanding topic with new ideas, tools, and practices appearing frequently. In this article, we tried to capture a handful of API design principles and best practices to guide a designer down a path that ultimately proves successful.
Additionally, API design is an ongoing process where new versions of APIs grow out of old ones. I hope you continue to iterate on your previous work to find the best solutions. Thanks for reading!
Footnotes
1 Retz, Jarrett. “What Is API Design? Best Practices, Tools, Tutorials & More!” The Last Call – RapidAPI Blog, 22 Mar. 2021, rapidapi.com/blog/api-design/. Accessed 25 Mar. 2021.
2 Rettberg, Tonja. “OpenAPI and RAML in Comparison.” Q_PERIOR AG, www.q-perior.com/en/blog/openapi-and-raml-in-comparison/. Accessed 25 Mar. 2021.
3 “RAML 200 Tutorial.” RAML, raml.org/developers/raml-200-tutorial. Accessed 25 Mar. 2021.
4 Sarid, Uri. “Open API and RAML: Better Together.” MuleSoft Blog, 27 Apr. 2017, blogs.mulesoft.com/dev-guides/open-api-raml-better-together/. Accessed 25 Mar. 2021.
5 “Coming from OpenAPI | AsyncAPI Initiative.” 605c657882e81fb31cdebd4f–Asyncapi-Website.netlify.app, www.asyncapi.com/docs/getting-started/coming-from-openapi. Accessed 25 Mar. 2021.
6 “Design First or Code First: What’s the Best Approach to API Development?” Swagger.io, 2017, swagger.io/blog/api-design/design-first-or-code-first-api-development/. Accessed 18 Mar. 2021.
7 Narumoto, Masashi, et al. “API Design Guidance – Best Practices for Cloud Applications.” Microsoft.com, 12 Jan. 2018, docs.microsoft.com/en-us/azure/architecture/best-practices/api-design.
8 Wikipedia Contributors. “Representational State Transfer.” Wikipedia, Wikimedia Foundation, 24 July 2019, en.wikipedia.org/wiki/Representational_state_transfer.
9 Deshpande, Tanmay. “RESTful API Design — Step by Step Guide | Hacker Noon.” Hackernoon.com, first published 12 Jan. 2018, hackernoon.com/restful-api-design-step-by-step-guide-2f2c9f9fcdbf. Accessed 18 Mar. 2021.
10 “Best Practices in API Design.” Swagger.io, 2013, swagger.io/resources/articles/best-practices-in-api-design/. Accessed 18 Mar. 2021.
11 “Content-Type.” MDN Web Docs, developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type.
12 “Rest – What Is the Ideal Response Size to Consider When Designing API Objects and Sub-Objects.” Software Engineering Stack Exchange, softwareengineering.stackexchange.com/questions/349032/what-is-the-ideal-response-size-to-consider-when-designing-api-objects-and-sub-o. Accessed 25 Mar. 2021.
13 Ploesser, Kay. “REST API Design Best Practices for Sub and Nested Resources.” REST API Design Best Practices for Sub and Nested Resources | Moesif Blog, 12 Mar. 2019, www.moesif.com/blog/technical/api-design/REST-API-Design-Best-Practices-for-Sub-and-Nested-Resources/. Accessed 25 Mar. 2021.
hagnat says
“Sending a GET request to api.apidesigners.org/v1/sercrets/getSecrets”
typo “sercrets”