Monday 5 August 2024

Spring boot REST API Best practices with examples

 What is a REST API?

          A REST API is an application programming interface architecture style that conforms to specific architectural constraints, like stateless communication and cacheable data. It is not a protocol or standard. While REST APIs can be accessed through a number of communication protocols, most commonly, they are called over HTTPS, so the guidelines below apply to REST API endpoints that will be called over the internet.

 

REST API Best Practices :-

  • Follow Resource naming guidelines – Use nouns instead of verbs in endpoint paths.

We shouldn't use verbs in our endpoint paths. Instead, we should use the nouns which represent the entity that the endpoint that we're retrieving or manipulating as the pathname.

Examples –

  • GET /users
  • POST /users
  • GET /users/{id}
  • PUT /users/{id}
  • DELETE /users/{id}

Here /users represents a REST API resource.

Don't use verbs, like GET /getUsers or /save or /update 

Use logical nesting on endpoints –

Example:-

if we want an endpoint to get the comments for a blog post, we should append the /comments path to the end of the /posts path

/posts/{postId}/comments

Get particular comment from the post,

          /posts/{postId}/comments/{commentId}

 

  • Use the Appropriate HTTP Method

        After you’ve named an endpoint, determine which HTTP methods to use based on the nature of the operation being performed. For context:

  • GET - requests are used to retrieve resources.
  • PUT - requests are typically employed for resource creation or replacement.
  • POST - requests are suitable for resource creation when the server assigns a unique identifier.
  • PATCH - requests allow for partial resource updates.
  • DELETE - requests, as the name implies, deletes the specified resource.

Scenarios Where POST Might Be Used to Retrieve Data:-

  • Complex Queries:
          When the query parameters are too complex or lengthy to be included in the URL, a POST request can be used to encapsulate the query in the request body.
  • Sensitive Data
         When query parameters contain sensitive data, it might be preferable to use POST to avoid exposing data in the URL.

  • GraphQL:
          In GraphQL APIs, a POST request is typically used to send a query and retrieve data, regardless of whether it changes the server state.

  • Rate Limiting or Quota Management
          Some APIs might restrict certain operations to POST requests as part of their rate limiting or quota management strategy.


  •      Handle errors gracefully and return standard status/error codes

Below are the HTTP status codes,

  • 400 Bad Request - This means that client-side input fails validation.
  • 401 Unauthorized - This means the user isn't not authorized to access a resource. It usually returns when the user isn't authenticated.
  • 403 Forbidden - This means the user is authenticated, but it's not allowed to access a resource.
  • 404 Not Found - This indicates that a resource is not found.
  • 500 Internal server error - This is a generic server error. It probably shouldn't be thrown explicitly.
  • 502 Bad Gateway - This indicates an invalid response from an upstream server.
  • 503 Service Unavailable - This indicates that something unexpected happened on server side (It can be anything like server overload, some parts of the system failed, etc.).
  • 204 No Content - Indicates that the request was successful, and the server has fulfilled the request, but there is no additional content to send in the response body. This is the most used status code for successful DELETE operations.
  • 422 Unprocessable Entity - This status code is used to indicate that the server understands the content type of the request entity, and the syntax of the request entity is correct, but was unable to process the contained instructions. It’s often used for validation errors that are not syntax errors.
  • 409 Conflict - Indicates that the request could not be processed because of a conflict with the current state of the resource.

some common scenarios where a 409 Conflict might be used, 

  • Resource Version Conflict (Optimistic Locking:-
        When using versioning for resources (e.g., with an ETag or a version number), a 409 Conflict can be returned if a resource has been modified since the client last retrieved it.

       Example – mongo db version lock at db level

         The code will throw OptimisticLockException so need to provide status code as 409 and proper error message to the API.

  • Duplicate Resource Creation
    When a client attempts to create a resource that already exists and uniqueness constraints prevent it.

  •  Concurrent Modification 
      When two or more clients attempt to modify the same resource concurrently in a way that causes a conflict.

Below are the scenario's,

  • /products - returns List of Products

      Status Code if No Products Exist: 200 OK.

       Returning an empty list is a valid response when there are no products. A 404 Not Found would imply that the endpoint itself does not exist or that the resource could not be found at the endpoint, which is not the case here.

  • /products?page=0&size=10

      Status Code if No Products Exist: 200 OK.

Like the previous case, an empty list is a valid response for this query. The request was successful, and the endpoint exists; it’s just that there are no products to return.

  • /products/{productId} - To Get Product Based on Product ID

       Status Code if Product ID Doesn’t Exist: 404 Not Found

 If the productId provided in the path variable does not exist, the resource cannot be found, so a 404 status code is appropriate. It indicates that the resource was not found at the given endpoint.

  •  /products - POST method - Create a Product

      Status Code for Invalid Request Body Field: 400 Bad Request or 422 - Unprocessable Entity

  • 400 Bad Request: This is commonly used when there is a syntax or validation error in the request body. It indicates that the server cannot process the request due to client-side issues.
  • 422 Unprocessable Entity: This status code is used to indicate that the server understands the content type of the request entity, and the syntax of the request entity is correct, but was unable to process the contained instructions. It’s often used for validation errors that are not syntax errors.

Choosing Between 400 and 422

  • Use 400 if the request body is malformed or if there’s an error with the structure or syntax.
  • Use 422 if the request body is well-formed but fails business logic or validation rules (e.g., invalid data format or logical inconsistencies).

 

  •     Use Query Parameters for Filtering, Sorting, and Pagination:

         The databases behind a REST API can get very large. Sometimes, there's so much data that it shouldn’t be returned all at once because it’s way too slow or will bring down our systems. Therefore, we need ways to filter items.

We also need ways to paginate data so that we only return a few results at a time. We don't want to tie up resources for too long by trying to get all the requested data at once.

Filtering and pagination both increase performance by reducing the usage of server resources. As more data accumulates in the database, the more important these features become.

               /products?sort=price&limit=10&page=2

 

  • Versioning

     Versioning a REST API is essential to maintain backward compatibility while evolving your API. Here are some common strategies for versioning REST APIs:

  • URI Versioning:

               Include the version number in the URI path.

               Example:

               https://api.example.com/api/v1/users

  • Query Parameter Versioning:

  Include the version number as a query parameter.

               Example:

                       https://api.example.com/users?version=1

  • Header Versioning:

              Use custom headers to specify the API version.

              Example:

                  GET /users with header Accept: application/vnd.example.v1+json

  • Content Negotiation:

               Use the Accept header to specify the version.

               Example:

                        GET /users with header Accept: application/vnd.example.v1+json

  • Embedded Versioning:

                Include the version number in the payload.

                Example:

 { "version": "1.0", "data": { ... } }


  • Document Your API:

      Use tools like Swagger/OpenAPI for clear documentation.


  •    Security Best Practices:
  1. Implement authentication and authorization mechanisms (e.g., JWT, Spring Security).
  2. Validate and sanitize user input to prevent common web vulnerabilities (XSS, SQL injection).
  3. Secure communication using HTTPS.