REST API Best Practices: Design & Security

REST API Best Practices: Design & Security

for building secure, scalable, reliable and maintainable backend system it is important to follow conventions accross backend system

REST in Brief

A RESTful (Representational State Transfer) API (Application Programming Interface) is an interface that allows systems to communicate with each other over the Internet. REST is often used in the context of web services and APIs to enable the communication and exchange of data between systems.

REST API gives freedom to design API however you want but for building a secure, scalable, reliable and maintainable backend system it is important to follow conventions across the backend system.

Best Practices

1. Use Resource Names

Resource name is typically name of the model/table in a database, it can also represent some virtual logical entity. Using Resource name makes the API more human readable and understandable and it makes it clear what the API endpoint represents. Also including resource names in the URL allows for a hierarchical structure that reflects the relationships between resources. For example, /users/123/orders indicates a user with ID 123 and their associated orders.

GET /querycarts/123 -> GET /carts/123

2. Use Plurals

Pluralization is a natural part of human language. When dealing with multiple instances of a resource, using the plural form in the URL can make the API more intuitive and easier to understand. It aligns with how we typically describe multiple instances of a noun in English. Using plurals helps distinguish between individual resource instances and collections of resources. For example, /user/123 might represent a single user, while /users represents the entire collection of users.

HTTP methods like GET, POST, PUT, and DELETE are often used with plural resource names. For example, a GET request to /users might retrieve a list of users, and a POST request to /users might create a new user. This aligns with the convention of using plurals for resource names. In many cases, RESTful APIs are built on top of databases, where tables are often named in the plural form to represent collections of entities. Aligning resource names with database table names can simplify the mapping between the API and the underlying data structure.

GET /cart/123 -> GET /carts/123

3. Idempotency

Idempotence ensures that the same operation can be repeated multiple times without causing different outcomes.

In unreliable networks, requests may fail or be sent multiple times. Idempotence ensures that if a request is duplicated due to network issues or client retries, the system's state remains consistent. It allows clients to implement a safe retry mechanism. If a request fails, the client can simply retry it without the fear of causing unintended changes or inconsistencies in the system. In distributed systems where multiple components may process a request, idempotence helps maintain consistency. Even if a request is processed by multiple nodes, the end result should be the same as if it were processed only once.

POST /carts -> POST /carts {requestId: 123}

4. Use Versioning

API versioning allows developers to introduce changes, improvements, or new features to an API without breaking existing clients. By maintaining backward compatibility, existing applications can continue to function as expected even as the API evolves. Versioning provides a way for clients to transition from one version of the API to another at their own pace. Clients can choose to adopt a new API version when they are ready, reducing the risk of disruptions to their services.

GET /carts/123 -> GET /v1/carts/123

5. Query After Soft Deletion

Soft deletion typically involves marking entries as deleted instead of physically removing them from the system By default, the API should exclude soft-deleted entries from the response. This ensures that clients not explicitly requesting deleted entries receive the expected behavior. Introduce a query parameter or filter to allow clients to specify whether they want to include soft-deleted entries in the response.

GET /carts -> GET /carts?includeDeleted=true

6. Pagination

Pagination is a technique used in web development and APIs to break down large sets of data into smaller, more manageable chunks or pages. Loading and displaying a large dataset all at once can lead to slow response times and increased server and network loads. Pagination allows you to retrieve and display smaller subsets of data, improving overall performance and user experience.

GET /carts -> GET /carts?pageSize=xx&pageToken=xx

7. Sorting

When presenting data to users in a user interface, it's often helpful to allow them to choose how the data is sorted. For example, in an e-commerce application, users might want to sort products by price, name, or popularity. In scenarios where the API is used for reporting or data analysis, clients may want to retrieve data in a specific order to facilitate easier analysis. For example, sorting by timestamp for time-series data or sorting by a numeric field for ranking purposes.

GET /carts -> GET /carts?sortyBy=time

8. Filtering

Filters are often used to implement search functionality within an API. Clients can specify search criteria to retrieve only the resources that match the specified conditions. For example, /users?filter=name:John might return a list of users with the name "John."

Clients may need to filter data based on specific attributes or fields. For instance, in an e-commerce API, clients might want to retrieve products within a certain price range, such as /products?filter=price:10-50 to get products with prices between $10 and $50.

Filters enable conditional retrieval of resources. Clients can request resources that satisfy a particular condition, allowing them to fetch data based on dynamic criteria.

GET /carts -> GET /carts?filter=color:red

9. Secure Access

The X-API-KEY header is often used for API key-based authentication. Each client is issued a unique API key, and the server validates incoming requests by checking the presence and correctness of the API key. This helps control access to the API and ensures that only authorized clients can make requests.

The X-EXPIRY header is used to specify the expiration time of a request. It helps prevent replay attacks by indicating that a request is only valid for a certain period. Servers can check the timestamp in the X-EXPIRY header and reject requests that have expired, enhancing the security of the API.

The X-SIGNATURE header is used to include a cryptographic signature or hash of the request data. This signature is generated using a secret key known only to the server and the client. The server can verify the integrity and authenticity of the request by recalculating the signature on its end and comparing it with the provided X-SIGNATURE. This helps ensure that the request has not been tampered with during transit.

Together, these headers contribute to establishing a secure communication channel between clients and servers, which is crucial when dealing with sensitive or private data.

X-API-KEY=xxx -> X-API-KEY=xxx X-EXPIRY=xxx X-REQUEST_SIGNATURE=xxx(hmac(URL + Query + Expiry + Body))

10. Nested Resources For Accessing Cross Reference Resource

Nested resources can represent a logical hierarchy, making the structure of the API more intuitive. This is particularly useful when there is a clear parent-child relationship between resources. For example, /orders/123/items signifies that you are retrieving items belonging to the order with ID 123.

The use of nested resources provides context and identification for the relationships between entities. When you see a nested resource like /users/456/posts, it's clear that you are retrieving posts associated with the user with ID 456.

GET /carts/123?item=321 -> GET /carts/123/items/321

11. POST: Use Request Body Instead Of Query Params

Query parameters are typically appended to the URL and are limited in terms of length. Sending data in the request body allows for larger and more complex data payloads. This is important when dealing with substantial amounts of data or when the data structure is hierarchical. Placing sensitive information, such as passwords or user credentials, in the request body provides a more secure approach compared to query parameters. Query parameters are visible in the URL, which may expose sensitive information in logs, browser history, or other places.

POST /carts/123?addItem=321 -> POST /carts/123/items:add {itemId: 123}

12. Rate Limit

Rate limiting helps protect an API against abuse, misuse, or malicious attacks. By restricting the number of requests a client can make, it mitigates the risk of denial-of-service (DoS) attacks, brute-force attacks, and other forms of abuse that could overwhelm the API servers. It promotes fair usage of API resources by preventing any single client from monopolizing server resources. This ensures that resources are distributed more equitably among all clients, preventing one client from negatively impacting the experience of others.

Conclusion

It's essential to note that these are conventions. The key is to choose a convention and stick to it consistently throughout your API. Whichever approach you choose, clarity, consistency, and adherence to RESTful principles should be the guiding principles in your API design. Additionally, documenting your API well helps developers understand the naming conventions you've chosen.