Best API Design Practices

Best API Design Practices

If you like the free content I put out, consider subscribing to my newsletter on substack to get a well-researched article every week delivered straight to your inbox.


If you have been writing services at your workplace, you know the real pain when multiple developers are contributing to the same repository but not following a general guideline for defining the APIs.

While there are some existing software architecture styles like REST/SOAP on how we should design our APIs for communication, this article revisits some of the most important guidelines for building APIs that are more understandable and future extensible.

Here are some points to follow and reflect upon while building your APIs:

1. Choose your endpoint carefully

While designing your endpoints, you must decide carefully on which entity(user, product, etc) you want to expose data to the end customers of your APIs.

You can try choosing an endpoint that is self-understandable by the clients and could fulfill some use cases. For example:

/users: this endpoint represents information about users

/users/{username}: this endpoint represents information for a user with some username

/users/123: this endpoint represents information for a user with ID: 123

As you can see, choosing /users endpoint helped in exposing a large set of resources. The point is: to choose a more appropriate and probably broader scope for the endpoint that could satisfy multiple use cases.


2. JSON format for data exchange

JSON is hands-down the most popular technique used for exchanging data in API requests/responses across the tech industry for various reasons.

  1. It is easy to read compared to XML format. Simple key-value structure.
  2. It is lightweight compared to XML format and thus consumes less network bandwidth and helps in fast transmission between client-servers.
  3. JSON is relatively fast to parse and does not require heavy encoding/decoding similar to XML.
  4. JSON is popularly used across various frameworks and requires very little boilerplate code to parse and process in all languages.


3. Use nouns instead of verbs in the endpoint path

Using verbs can become inconsistent as developers might have different preferences for wording actions (get vs retrieve). Nouns provide a universal way to represent resources. The HTTP method (GET, POST, PUT, DELETE) already specifies the action being performed. Nouns in the path identify the resource being accessed.

Here's an example:

  • ? Verb-heavy path: /products/updateProductPrice/123 (unclear and lengthy)
  • ? Noun-based path: /products/123/price (clearer and concise) - the HTTP method (PUT) would indicate updating the price.

One more tip concerning the naming convention is to always mention the nouns as plural. The primary reason is using /product endpoint denotes CRUD operations on one product whereas using /products represents that there could be multiple products in a single GET/POST/DELETE request/response.


4. Use Logical nesting on endpoints

If one resource has an inherent relationship with another, nest the child resource within the parent's path. Nested paths mimic how we think about data - articles have comments, users have posts, etc. As a result, users can easily guess how to access related resources by exploring the URL structure.

For instance:

  • ? Flat path (less intuitive): /comments?articleId=123
  • ? Nested path (clearer): /articles/123/comments


5. Describe API’s functionality with standard HTTP methods

From my experience across various companies, I have seen people performing CRUD operations (create/read/update/delete) on the resources using GET, POST, and DELETE methods only.

  • GET: This method retrieves data from a resource. It's essentially a request for information. For example, in a user management API, a GET request to /users might return a list of all users.
  • POST: This method is used to create new resources (and can be used for updating existing resources as well). It typically involves sending data along with the request in the body. So, a POST request to /users with user information in the body might create a new user.
  • DELETE: This method deletes a resource. A DELETE request to /users/123 would remove that particular user.

While other people use the PUT method for updating a resource and the POST method for only creating new resources, the choice lies completely in your hands. The whole point is: to represent the APIs with the right set of HTTP methods and not use only one HTTP method for all endpoints.


6. Handle errors gracefully and return standard HTTP error codes

A critical part of designing APIs is how you communicate the errors from your API to the client. To eliminate any confusion and maintain standard conventions when an error occurs, we can adhere to the standard HTTP error codes. Some of the error codes are popularly used:

  • 400 Bad Request: This indicates invalid user input in the request. Maybe missing data, a malformed JSON object, or a value out of range.
  • 401 Unauthorized: Authentication failed. The user is not authorized to access the resource.
  • 403 Forbidden: The user is authenticated but doesn't have permission to perform the requested action on the resource.
  • 404 Not Found: The requested resource doesn't exist on the server (e.g., trying to access a non-existent user).
  • 500 Internal Server Error: A generic error on the server side. This shouldn't be used for specific errors as it offers no guidance to the API use
  • 502 Bad Gateway: This indicates an invalid response from an upstream server.
  • 503 Service Unavailable: This indicates server failure. This can happen due to anything like service not being available, server overload, etc.

If the HTTP status codes are too broad for your use case and you want some more status codes then you can associate your internal error codes with appropriate HTTP status codes.

For instance, an e-commerce API might define a 4001 code for "Payment Declined" to provide more specific information than a generic 400 (Bad Request).

4001 would define a family of 400 status codes and similarly, you can use more granular status codes (like 4002, 4003, and so on) to define the exact error scenario.


7. Filtering, Sorting, and Pagination

When viewing any website, the data fetched from the backend service can be pretty huge using the GET APIs. To filter this large data and show it to the end user in a more convenient manner, there are three strategies used.

  • Filtering: This functionality refines a dataset based on specific criteria. Imagine a product API - you might want to filter results by category (electronics), price range ($100-$200), or availability (in stock). Filtering allows users to narrow down the information to what's relevant to them. For example: you can pass the filters in the GET API method query parameters like: GET /products?category=electronics&price_limit=200
  • Sorting: This feature organizes data according to a chosen attribute. In the same product API example, you might sort results by price (low to high), average rating, or newest arrivals. Sorting helps users find products based on their preferences. For example, you can sort the product prices on an e-commerce website using: /products?sort=price,asc
  • Pagination: This technique breaks down large datasets into manageable chunks (pages). For instance, the product API might return 10 results per page. Users can then navigate through the pages to see all available products without having to load everything at once. Pagination improves performance and user experience by avoiding overwhelming users with massive amounts of data. You can use page(page number) and limit(number of items on each page) and use pagination in this way: /products?page=2&limit=25

Overall, filtering, sorting, and pagination help users manage to see limited and desired data sets on the website.


8. Provide API Versioning

When you work long enough on a system, it might be the case that you would want to change the API contract to do some edits but you really can’t do that since your existing clients are already using the API in production.

To avoid this conflict of API upgrade or newer version rollout, backend services usually expose the API using some versioning in the API URL. For example:

www.example.com/api/v1/ for the version 1 set of APIs, and similarly www.example.com/api/v2/ for the version 2 set of APIs, and so on. So, consider designing your APIs using versioning in the URL from day 1, so that later on you can change this version in the URL to provide new upgraded contracts.

The main benefits of API versioning are:

  1. Smooth rollouts of new API contracts without forcing clients to do the force upgrade to a new version of APIs
  2. Backward compatibility in case something goes wrong


9. Authentication and Authorization

When you are exposing any data as a resource to the clients, you must make sure to perform authentication and authorization of the actions and the users performing those actions on those resources.

Authentication: this is the process of verifying the identity of a user, whether it’s a valid user account or not who is trying to perform any operation on the resource.

Authorization: this is the process of verifying what a user account can do after they have been authenticated. For example: some users might have permission to read the resource but only admin users will have permission to create/update a new resource.

These concepts are complementary. APIs first authenticate to confirm a user's identity, and then use authorization to determine what actions they can perform on the available resources. This layered approach ensures that only authorized users can access and modify data within the system. This can be achieved through various methods like OAuth, API keys, or JWTs (JSON Web Tokens).


10. Rate Limiting

Rate Limiting is a concept where you try to limit the number of requests allowed to be processed successfully during a span of time. For example: 10 RPS represents that a maximum of 10 requests will be allowed per second to be processed successfully and all other requests during that second will be blocked.

This is required for the following reasons:

  1. Prevention against DDOS attacks where the suspicious hacker tries to call the API with an abnormally high rate of traffic(probably in hundreds of thousands) with the intention to bring down the system. In such cases, rate limiting helps allow only certain requests and blocks all other requests.
  2. If your service can’t manage more than a certain threshold, then rate limiting helps you to manage an expected load on your service in the runtime production environment and prevents your service from going down.


11. Logging and Monitoring

Last, but not least, ensure proper logging and monitoring is in place for your APIs observability.

  1. Log API requests and responses for auditing purposes.
  2. Emit metrics for measuring
  3. Set up alerts on top of latency, error rate, success rate, and traffic throughput.


Conclusion

Well, that’s it. These were the main points to keep in mind while designing your APIs. There are some more things that you can probably take care of like providing visible and accurate documentation to the API clients, implementing more security guardrails for API, implementing a cache for faster API responses, etc. I would leave this to the reader’s creativity on how they can make APIs more secure and process requests faster.


That’s it, folks for this edition of the newsletter. Please consider liking and sharing with your friends as it motivates me to bring you good content for free. If you think I am doing a decent job, share this article in a nice summary with your network. Connect with me on Linkedin or Twitter for more technical posts in the future!

Book exclusive 1:1 with me here.

Thanks for reading Curious Engineer! Subscribe for free to receive new posts and support my work.

Resources

Best Practices in API Design by Swagger

REST API Practices by FreeCodeCamp


Thanks for sharing it with us. On AnyAPi we also offer different APIs. anyapi.io

回复

要查看或添加评论,请登录

Vivek Bansal的更多文章

  • How to implement a Circuit Breaker

    How to implement a Circuit Breaker

    If you like the free content I put out, consider subscribing to my newsletter on substack to get a well-researched…

    7 条评论
  • How to implement Consistent Hashing

    How to implement Consistent Hashing

    If you like the free content I put out, consider subscribing to my newsletter on substack to get a well-researched…

    3 条评论
  • Optimistic Locking Implementation

    Optimistic Locking Implementation

    If you like the free content I put out, consider subscribing to my newsletter on substack to get a well-researched…

  • 1 year to Curious Engineer ??

    1 year to Curious Engineer ??

    If you like the free content I put out, consider subscribing to my newsletter on substack to get a well-researched…

  • Message Queues vs Message Brokers

    Message Queues vs Message Brokers

    If you like the free content I put out, consider subscribing to my newsletter on substack to get a well-researched…

    4 条评论
  • Introduction to gRPC

    Introduction to gRPC

    If you like the free content I put out, consider subscribing to my newsletter on substack to get a well-researched…

    7 条评论
  • Non-Functional Requirements

    Non-Functional Requirements

    Brief Introduction Let’s say you are building a website that allows users to book flight tickets. The requirements for…

    4 条评论
  • QuadTrees

    QuadTrees

    If you like the free content I put out, consider subscribing to my newsletter on substack to get a well-researched…

    2 条评论
  • Text Based Search: ElasticSearch

    Text Based Search: ElasticSearch

    If you like the free content I put out, consider subscribing to my newsletter on substack to get a well-researched…

    3 条评论
  • Sharding vs Partitioning

    Sharding vs Partitioning

    If you like the free content I put out, consider subscribing to my newsletter on substack to get a well-researched…

    5 条评论

社区洞察

其他会员也浏览了