Skip to content

Summary of the best practices to follow in designing the RESTful APIs

Notifications You must be signed in to change notification settings

anilkbachola/restapi-design-guidelines

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

REST API Design Best Practices

This is an effort to summarize the best practices to follow in designing the RESTful APIs. The list is not comprehensive, most of the items are stemmed from experience and industry acceptance.

Examples in this document, uses json as the content type, but they also applies to other content types.

Terminology

Before we start talking about the best practices, lets try to understand some of the basics of REST and the Http.

  • Resource - Is an object or representation which has some associated data with it and there can be set of methods to operate on it.

    Example: Student resource, Employee resource and operations like Create, Get, Delete

  • Collection - Is a set of resources.

    Example: Students collection, Employees collection

  • Resource URL - Uniform Resource Locator. A path through which a resource(s) can be located and actions can be performed.

    A URL is a specific type of Uniform Resource Identifier (URI). A generic URI is of the form:

    scheme:[//[user[:password]@host[:port]][/path][?query][#fragment]
    

    It comprises:

    • scheme: Like http(s), ftp, mailto, file, data, irc
    • Two Slashes:
    • authority section: An optional username:password followed by @ symbol
    • host: Consisting of a registered hostname or IP address
    • port: An optional port number, separated from hostname with colon.
    • path: which contains the data or resource path
    • query: An optional query paramerts in the form key=value separated by &
    • fragment:
  • Actions - A standard HTTP Verb, which indicates a type of action that can be performed on a resource.

    • GET: Requests data from a resource. Should not produce any side effects.
    • POST: Requests to create a resource or a collection.
    • PUT: Request to update a resource or create the resource if it does not exist
    • DELETE: Request to delete a resource.
    • HEAD: Request to get metadata of a resource. Resources that support GET may support HEAD as well.
    • PATCH: Request to apply a partial update to a resource
    • OPTIONS: Request to retrieve information about a resource, at a minimum returning valid methods for this resource.
  • Path Parameters -

    Path parameters are part of the URL, which can be varied and represents resource hierarchy.

  • Query Parameters -

    Query parameters are added to the url after the ? mark, while a path parameter is part of the regular URL.

  • Http Headers -

    HTTP headers allow the client and the server to pass additional information with the request or the response. A request header consists of its case-insensitive name followed by a colon ' : ', then by its value

  • Http Body -

    Is the data that is sent along with a request.

Resource Representation (Resource URLs)

  1. Make sure to have a unique identifier for each Resource

    For example: Student ID for Student resource. This will help locate a specific resource using its ID.

  2. Endpoint URL should only contain resource/collection names (nouns) not actions/verbs

    There is no need to have the 'action' in the resource URL, the action that can be performed on a resource/collection can be expressed through Http Verb.
    Examples:

    * Not Recommended:
    
    /addEmployee
    /deleteEmployee
    /updateStudent 
    
    * Recommended
    
    /employees
    /employees/{empid}
    
  3. Use collection names to represent resources

    To narrow down to a specific resource in a collection, use the unique id of the resource.

    Examples:

    - /students     - To represent Student resource
    - /employees    - To represent Employee resource
    
    - /students/1000    - To locate Student resource with id 1000
    - /employees/123    - To locate Employee resource with id 123
    
    - /students/1000/courses    - Course resource nested under Student resource
    - /students/1000/courses/101    - Specific Course resource with id 101 for Student resource with id 1000
    
    - /courses/
    - /courses/101/students
    - /courses/101/students/1000
    
  4. Use HTTP Verbs to represent an action on a resource. Do not use action names in URLs

    POST - create a resource GET - retrieve a resource PUT - update a resource (by replacing it with a new version)* PATCH - update part of a resource (if available and appropriate)* DELETE - remove a resource

    Examples:

     - POST /students   -> to Create one/more Student resource in collection.
     - GET /students    -> to Get Students collection
     - GET /students/1000   -> to Get Student resource with id 1000
     - DELETE /students/1000    -> Delete a Student resource with id 1000
    
  5. Use query parameters to further narrow down or filter or sort on a resource or collection

    Searching, Filtering, Sorting and Pagination are some common actions that we perform on a resource. All of these actions are simply a GET on a resource or collection with additional conditions. There is no need for special endpoints to handle these actions, instead can be achieved through query parameters.

    Sorting:

    GET /employees?sort=rank_asc
    GET /employees?sortOn=rank&sortOrder=asc
    
    GET /employees?sort=-rank,+age  -- sort by age ascending and rand descending
    

    Filtering:

     GET /employees?country=US
     GET /employees?country=US&dept=IT
     GET /employees?country=US&dept=IT&sortOn=firstName&sortOrder=asc
    

    Searching:

    GET /employees?search=James
    GET /students?search=Algorithms
    

    Pagination

    GET /students?page=21
    

    Limit the response fields

    Use `fields` params to let client decide which fields should be returned in response.
    
    GET /students?fields=id,firstName   
    
  6. Use Path Aliases to group multiple query parameters

    Consider packaging multiple params into easily navigate-able paths. As in below example /students/enrolled can be a path alias to get all enrolled students, which otherwise will be represented with several query parameters like /students?active=true&enrolled=true&year=2017

    GET /students/enrolled
    
    GET /students/graduated
    
  7. How about actions that does not fit into simple CRUD operations

    Some operations like below can not be mapped to world of CRUD and to a particular resource.

    • login
    • logout
    • print
    • transfer funds
    • cancel a booking

    So, how to represent the endpoints for these operations? Recommended:

    • First, try to see if the action can fit on to an existing resource or there is a need for new resource.

    For example, cancel a booking can be mapped to a booking resource and transfer funds can be mapped on to a accounts resource.

      -- to cancel a booking
      DELETE /bookings/101  
      PUT /bookings/101
      
      -- to transfer funds from one account to another, and also gives options to extend with other services
      POST /accounts/100?service=transfer
      POST /accounts/100?service=statements    
    
    
    • If not, try to designate a 'controller' resource for each operation.

      For example login and logout can be mapped to a URI like below

        // we are trying to group login and logout on to a `auth`.
        POST /auth/login
        POST /auth/logout
      
        or
      
        POST /auth?action=login
        POST /auth?action=logout
      
      

      A transfer of funds and print can be grouped in to a servces endpoint.

        POST /services/print
        POST /services/transfer
      
  8. How about retrieving multiple resources in single GET

    A multi-resource search does not really make sense to be applied to a specific resource's endpoint. In this case, GET /search would make the most sense even though it isn't a resource.

    In special cases like above, it is fine to have a special endpoints like /search.

  9. Versioning

    Use major version in the URL. Optionally represent minor version using headers.

    POST /api/v1/students/
    POST /api/v1.0/students/
    
    POST /api/v2.1/students/
    

    If your API changes frequently, use an HTTP header to represent the minor version. Other techniques to consider would be to use a date based minor version as header values.

    "X-API-VERSION" : "1.1"
    "X-VERSION" : "20171228"
    
  10. When to use Path Params vs Request Params

Path params are used to identify a specific resource or resources, while query parameters are used to sort/filter those resources. And also paths tend to be cached, parameters tend to not be.

It is recommended to use any required parameters in the path, and any optional parameters should certainly be query string parameters. Putting optional parameters in the URL will end up getting really messy when trying to write URL handlers that match different combinations.

Request

  1. Make use of standard request headers, only introduce custom headers if required

    Using HTTP standard headers leads less confusion and also the API will comply to standards. List of headers HTTP 1.1

  2. Try to use commonly used non-standard headers when standard headers does not fit your requirement

    This Wikipedia page will be a good start: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields

  3. When to use Query Params Vs Headers

    Usually query parameters will have a strong relationship to the resource under request. For example: 'student_id' belongs to 'student' resource. If the parameter under consideration has a strong affinity to a resource, then using it as a header is not the right choice.

    Custom headers should only be used for things that don't involve the name of the resource, the state of the resource or the parameters directly affecting the resource. Http headers should be used to send contextual information about the request to the resource.

    For example: a Supervisor and an Employee both request Timesheet resource, but employee need to see only his/her timesheets while supervisor should be able to see all his/her reportee's.

    The context employee/supervisor(auth) is a good candidate for header whereas querying for timesheets in a given month (month=Jan) is a good candidate for query parameter.

    Header parameters are typically added out-of-band by applications like user agents and/or proxies. If you need to allow message intermediaries to add the parameter to the message, then it should be located in the header.

    Query string is reserved for end users/applications that are invoking resource. On the other hand headers are reserved for applications and intermediaries that are facilitating this invocation.

  4. Generate a unique Request ID for each received request and pass it along with response

Processing

  1. Always validate a request before start processing it

    //TODO

  2. Always return meaningful error codes and/or messages

    //TODO

Response

  1. Use Http Status codes to send the request status

    The HTTP standard provides over 70 status codes to describe the return values. They can be grouped as below:

    • 1XX: Informational
    • 2XX: Successful
    • 3XX: Redirection
    • 4XX: Client Error
    • 5XX: Server Error

    Most of the APIs would need to deal with the below codes:

    • 200 (OK): To indicate that the request is succeeded.
    • 201 (Created): To indicate resource created successfully
    • 201 (Accepted): To indicate the request is accepted for processing, but the processing has not been completed. Its purpose is to allow a server to accept a request for some other process (perhaps a batch-oriented process)
    • 204 (No Content): The server has fulfilled the request but does not need to return an entity-body. Useful when a resource is deleted
    • 304 (Not Modified): To Indicate a resource is not modified. Client can use cached data.
    • 400 (Bad Request): To indicate the request was invalid or can not be served. Exact error should be explained in the payload.
    • 401 (Unauthorized): To indicate the request requires authentication.
    • 403 (Forbidden): To indicated the request is OK but is refusing or the access is not allowed
    • 404 (Not found): To indicate there is no resource behind the URI
    • 422 (Unprocessable Resource):
    • 500 (Internal Server Error):
    • 501 (Not Implemented): To indicate the server does not support the functionality required to fulfill the request.
  2. Have all APIs to return consistent response format

    This will enable the client to look up for information in the response in a uniform and consistent way.

    You can come up with your own standard response format, below is an example.

     {
       "status": null,
       "errors": null,
       "meta": null,
       "data": null,
       "links": null
     }
  3. Wrap API response into a data container object

    It is a good practice to wrap the response data into a container object like data

     {
       "status": {
    
       },
       "errors": null,
       "data": {
         "id": 1234,
         "firstName": "James",
         "lastName": "Bond",
         "email": "james.bond@007.com"
       }
     }
  4. Include status section in the response

    It is a good practice to include status object in the response body along with the standard http status codes.

    {
      "status": {
        "code": 400,
        "message": "Bad Request"
      },
      "errors": [],
      "data": null
    }
  5. Include errors section in the response

    It is a good practice to return the list of errors in the response body along with the http status codes. Use an object like errors to pass on the errors.

    {
     "status": {
       "code": 201,
       "message": "User Created Successfully"
     },
     "errors": [ 
        {
          "code": "ERR001",
          "description": "Validation Error",
          "target": "Email Address"
        }
      ],
      "data": null
     }
  6. Distinguish Errors and Faults

    • Errors: Classified as client passing an invalid data and the API is correctly rejecting that data.

      Errors do not contribute to the overall API availability. These are typically 4XX errors.

      Examples include invalid format, missing required params, invalid path, method not supported, invalid credentials etc..

    • Faults: Classified as API failing to correctly return response to a valid request.

      Faults to contribute to the overall API availability. These are typically 5XX errors.

  7. Use Pagination where required

    As data grows, so do collections. Planning for pagination is important for all APIs.

    APIs that return collections must support pagination.

Resource Creation

  1. Use POST to create resources

    When creating new resources, POST should be used.

  2. Allow to creating a collection of resources instead of one in a single request

    POST /students

    [
      {
        "firstName": "James",
        "lastName": "Bond",
        "email": "james.bond@007.com"
      },
      {
        "firstName": "James",
        "lastName": "Cooper",
        "email": "james.cooper@007.com"
      }
    ]

    Instead Of:

    {
      "firstName": "James",
      "lastName": "Bond",
      "email": "james.bond@007.com"
    }
  3. Let your API generate the resource IDs at server side, Do not rely on client to send unique resource ID

    Do not support accpeting the Ids of the resource being created in the request.

    [
      {
        "Id": 1001,
        "firstName": "James",
        "lastName": "Bond",
        "email": "james.bond@007.com"
      },
      {
        "Id": 1002,
        "firstName": "James",
        "lastName": "Cooper",
        "email": "james.cooper@007.com"
      }
    ]
  4. Return the created / modified state as response of create or update response.

    Its always useful and specific cases like the resource 'id' which the client need later to lookup the created resource.

    POST /students

    [
      {
        "firstName": "James",
        "lastName": "Bond",
        "email": "james.bond@007.com"
      },
      {
        "firstName": "James",
        "lastName": "Cooper",
        "email": "james.cooper@007.com"
        }
    ]

    Response:

    [
      {
        "id": 1234,
        "firstName": "James",
        "lastName": "Bond",
        "email": "james.bond@007.com"
      },
      {
        "id": 1235,
        "firstName": "James",
        "lastName": "Cooper",
        "email": "james.cooper@007.com"
      }
    ]
  5. Use HATEOS to return the newly created resource URL.

    Which helps the clients to navigate and locate the resource easily.

    Response:

    [
      {
        "id": 1234,
        "firstName": "James",
        "lastName": "Bond",
        "email": "james.bond@007.com",
        "links": [
          {
          "rel": "self",
          "href": "http://host:port/xyz/students/1234"
          }
        ]
      },
      {
        "id": 1235,
        "firstName": "James",
        "lastName": "Cooper",
        "email": "james.cooper@007.com",
        "links": [
          {
          "rel": "self",
          "href": "http://host:port/xyz/students/1235"
          }
        ]
      }
    ]

Getting Resources

  1. When querying collection and applying filtering/sorting/pagination follow below order

    • Filtering: filter the collection
    • Sorting: Sort the filtered collection
    • Pagination: sorted collection is paginated
  2. Use HATEOS to represent pagination data

    Use HATEOS to pass along the links to the pages for the client to navigate the pages easily. Send pre-built links to next, prev, current, first, last pages.

    Response:

    {
    "status": {
       "code": 200,
       "message": "Operation successful"
    },
    "errors": null,
    "data":[
      {
        "id": 1234,
        "firstName": "James",
        "lastName": "Bond",
        "email": "james.bond@007.com",
        "links": [
          {
          "rel": "self",
          "href": "http://host:port/xyz/students/1234"
          }
        ]
      },
      {
        "id": 1235,
        "firstName": "James",
        "lastName": "Cooper",
        "email": "james.cooper@007.com",
        "links": [
          {
          "rel": "self",
          "href": "http://host:port/xyz/students/1235"
          }
        ]
      }
    ],
    "links": [
       {
          "rel": "self",
          "href": "http://host:port/xyz/students?page=5&count=20"
       },
       {
          "rel": "prev",
          "href": "http://host:port/xyz/students?page=4&count=20"
       },
       {
          "rel": "next",
          "href": "http://host:port/xyz/students?page=6&count=20"
       },
       {
          "rel": "first",
          "href": "http://host:port/xyz/students?page=1&count=20"
       },
       {
          "rel": "last",
          "href": "http://host:port/xyz/students?page=121&count=20"
       }
     ]
    }
  3. Use meta fields to to indicate the information on the results

    It is also a good practice to include the meta fields like number of records page number offset etc. in the response along with the results.

Updating Resources

Deleting Resources

About

Summary of the best practices to follow in designing the RESTful APIs

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published