Appreciating the significance of integrating effective patterns into your web services and API development journey often comes with hindsight. This realization frequently occurs once you’ve constructed systems either lacking in patterns or expanded upon existing ones that were initiated without a distinct structure and convention. In these scenarios, the concept of an “API” might have been an afterthought, if considered at all.
Enhancing Developer Experience
Yet, APIs can be approached from the perspective of user experience—specifically, developer experience. Most developers are dedicated to crafting visually appealing designs that effortlessly guide users through their journeys. This remains true even when the end users are developers themselves. Building REST APIs warrants an equal level of genuine attention, akin to the careful consideration devoted to the user experience of a client-facing product. After all, the API serves as the gateway to your system. Just as you and your product or business leaders don’t want to alienate users, you also shouldn’t want to alienate developers.
Now, let’s delve into the main content. Since we’re discussing REST, we won’t delve into other implementations such as SOAP, GraphQL, or gRPC, as they deserve separate articles of their own.
In a broader sense, regardless of the specific implementation, achieving the desired “developer” experience—both as a user and provider—means that your API should be:
- consistent and intuitive
- high-performing
- driven by the client
- user-friendly and easy to comprehend
What are the actual methods for adhering to these principles, and what does this mean in practice? How can one attain these goals?
Naming and namespaces
One of the fundamental aspects—and perhaps the most immediately recognizable one—is whether an API employs sound resource naming and logically structured endpoints.
Consider the following examples:
- /api/updateSceduleDate
- /api/validate_address
- /api/facebook-signup
- /api/getStyle
- /api/getSingleOrder (POST!!!)
Do these examples make you want to hide away? Are you questioning your career choices? The provided examples suffer from several issues, including:
- Typographical errors
- Lack of consistent casing style
- Absence of effort in separating logical units
While the developer did incorporate a namespace for /api (though it wasn’t consistently used for all API endpoints), their attention to detail ended there yielding very bad developer experience and terrible API design.
So, what constitutes a good API layout? What does this entail? To start, designing endpoints and their structure should consider how they are perceived by the system. For instance, if you have an object model for a business concept named ” Organization,” a suitable pattern might appear as follows:
- /api/organizations
- /api/organizations/<id>
These two URLs are generally sufficient for handling most operations related to the object itself. But what about nested resources? For that, you would simply go one level deeper:
- /api/organizations/<id>/teams
Note another aspect—nothing is written in uppercase. Furthermore, it’s advisable to stick to kebab case rather than experimenting with a mix of casing styles. Another enhancement could involve adding a version namespace, e.g.
- /api/v1/organizations
Proper Verb Usage
I previously mentioned that the following layout should suffice for all operations:
- /api/organizations
- /api/organizations/<id>
This holds true. However, as demonstrated below, it’s possible to create an overly complex API even while adhering to naming conventions:
It’s clear from the above that simply following endpoint naming conventions isn’t enough. Despite certain redundancies in the example, the same results can be achieved using the appropriate HTTP verbs for the original endpoints:
- /api/organizations
- GET (fetch all objects)
- POST (create object)
- /api/organizations/<id>
- GET (fetch single object)
- PUT (replace object with new payload)
- PATCH (update existing object)
- DELETE (delete object)
With this structure in mind, there’s no need for routes like /api/getStyle; instead, it would be /api/styles, accessible exclusively through the GET verb.
Nevertheless, while layout and correct verb usage are important aspects of REST design, there are other intricacies to consider.
Implementing HTTP Status Codes
Few things infuriate developers more than receiving incorrect status codes during API integration testing. Are you bombarding them with 500s? That’s not the way to go.
For instance, running the command:
curl -v <https://kodius.com>
Would yield a response like:
HTTP/1.1 200 OK
While this indicates that everything is functioning well, it’s essential to implement an appropriate range of status codes. Be careful not to be overly descriptive or excessively nuanced with status codes. If an endpoint controller is responsible for creating an object, using a 201 status code can be more informative.
Status codes are generally grouped into classes, allowing you to grasp the situation swiftly based on the initial number. These classes include:
- 1xx: Informational responses
- 2xx: Successful responses
- 3xx: Redirection
- 4xx: Client errors
- 5xx: Server errors
While explaining each code’s meaning in detail isn’t necessary, it’s vital to make informed decisions when assigning status codes to different scenarios. In essence, throwing a 400 error because handling an error seems tedious is not acceptable. Each error case should ideally be associated with a useful and descriptive status code. Moreover, status codes contribute to REST architecture’s elegance, in my view, when compared to GraphQL in terms of interfacing with it.
REST Specific Features
Although REST doesn’t offer the same level of power as GraphQL in this regard, it still enables the handling of various specifics, such as filtering, sorting, including nested objects, and pagination, in an elegant manner that benefits the consumer.
A typical REST API might primarily return JSON data for frontend use. However, if backend relations are modeled correctly and optimizations are required, query parameters can facilitate tasks like retrieving related objects, sorting data, and paginating results. This is sometimes referred to as “uniform interfaces”.
Using the Organization endpoint as an example, let’s say we want to filter organizations by name, ensuring a certain minimum employee count, and including employee details in the response:
/organizations?name=Foo&employee_count_gt=100&includes=employees
{
"name": "Foo",
"employee_count": "200",
"employees": [
{
"name": "John"
},
{
"name": "Dick"
}
]
}
Consider implementing HATEOAS as well, which allows a client to fully comprehend available actions on an Organization and its relationships. This further enhances the API’s usability.
{
"name": "Foo",
"employee_count": "200",
"employees": [
{
"id": 1823,
"name": "John",
"links": [
{
"rel": "self",
"href": "/employees/1823"
}
]
}
],
"links": [
{
"rel": "self",
"href": "/organizations/184"
},
{
"rel": "employees",
"href": "/organizations/184/employees"
},
{
"rel": "affiliates",
"href": "/organizations/184/affiliates"
},
{
"rel": "job-postings",
"href": "/organizations/184/job-postings"
}
]
}
In conclusion, be mindful of REST’s capabilities, ensuring ease of understanding for API consumers. Your returned data doesn’t have to be verbose; neither does it require excessive flexibility.
OpenAPI’s Benefits and Value
Formerly known as Swagger, the OpenAPI specification serves as an interface definition language describing your API and enhancing its usability for clients. A wide array of OpenAPI generators exists, a comprehensive list can be found here making it straightforward to integrate with your API if you adhere to the specification during development.
For React users, we’ve covered this specific use case in an article you can read here. This provides insights into better data fetching between backend and frontend.
Additionally, we have a Rails-based API template that makes use of OpenAPI which you can check over here. It has details on how to use OpenAPI along with Rails as well as what the benefits are!
Summary
In summary, the best practices for building REST APIs mean you should:
- take care about naming and be consistent
- properly use HTTP verbs
- use status codes
- implement HATEOAS
- make use of OpenAPI/Swagger