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.
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 likeCreate
,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.
-
Make sure to have a unique identifier for each
Resource
For example:
Student ID
forStudent
resource. This will help locate a specific resource using its ID. -
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}
-
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
-
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
-
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
-
Use
Path Aliases
to group multiple query parametersConsider 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
-
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
- 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 abooking
resource andtransfer funds
can be mapped on to aaccounts
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
andlogout
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
andprint
can be grouped in to aservces
endpoint.POST /services/print POST /services/transfer
-
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
. -
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"
-
When to use
Path Params
vsRequest 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.
-
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
-
Try to use
commonly
used non-standard headers when standard headers does not fit your requirementThis Wikipedia page will be a good start: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
-
When to use
Query Params
VsHeaders
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
orthe 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 anEmployee
both requestTimesheet
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.
-
Generate a unique
Request ID
for each received request and pass it along with response
-
Always validate a request before start processing it
//TODO
-
Always return meaningful error codes and/or messages
//TODO
-
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.
-
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 }
-
Wrap API response into a
data
container objectIt 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" } }
-
Include
status
section in the responseIt 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 }
-
Include
errors
section in the responseIt 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 }
-
Distinguish
Errors
andFaults
-
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.
-
-
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.
-
Use
POST
to create resourcesWhen creating new resources,
POST
should be used. -
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" }
-
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" } ]
-
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" } ]
-
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" } ] } ]
-
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
-
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" } ] }
-
Use
meta
fields to to indicate the information on the resultsIt 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.