We developed several platforms for our customer and also their clients. So we have realized API interfaces for several applications, from JS interface (such as AngularJS or React apps) to mobile apps for iOS and Android (native or hybrid with Ionic for example).
We shared our experience about API design during the RubyDay 2014 (slides and video) but, after two years, there are been several changes and novelty: this is the first post about how to design a good API with tips, examples and technical implementation (especially for Ruby on Rails, the 5th version of which contains an API mode), as well as theoretic considerations and practices.
First of all in our product we break up the application into multiple services, where each of them has a single responsibility: they’re like Lego building blocks.
The API design isn’t simple: is itself a product and the customers are other developers, so we follow a steady design: REST.
Representational State Transfer (REST)
Representational State Transfer (REST) is a term introduced and defined by Roy T. Fielding in 2000 as part of his doctoral dissertation “Architectural Styles and the Design of Network-based Software Architectures”.
Fielding described REST as a software architecture style for distributed systems such as the World Wide Web. REST nowadays is one of the most popular design in web services API and, perhaps, a “fashion statement”.
The are few basic design principles:
- Client Request - Server Response scenario
- Use explicitly HTTP methods
- Be completely stateless
- Expose directory/hierarchical structure-like URIs
- It can be cache-able
- Hard to misuse
- Uniform interface
- in the application domain we have only resources like entities
- we have a unique identifier URL for each resource and collection of resources
- the resources manipulation is through these representations
- each message contains enough information to process it (self-descriptive messages)
- hypermedia as the engine of application state (HATEOAS)
- Remember what the API is designed for, alias “Treat others the way you wish to be treated”:
everything must be simple (easy to learn), intuitive (usable, even if without reading the documentation) and consistent (easy to extend)
An API REST call is simple
1
GET https://www.domain.com/api/v1/resources
GET
: verb / methodhttps://www.domain.com/api/v1
: API endpointresources
: resource (what you are trying to access)
The resource in the URI is used to obtain multiple endpoints that are used to represents resources rather than making every call to a single endpoint.
A small tip: the API endpoint should contain also the version because after a while you should support more than one version together (a native app needs longer update cycle and you cannot deploy a native client to all customers at the same time).
Otherwise you can set a generic API endpoint for all versions and the client uses the HTTP Header as versioning strategy using Accept-Version
or Accept
.
1
2
3
curl -H "Accept-Version:v1" https://www.domain.com/api/resources
curl -H "Accept: application/json; version=1" https://www.domain.com/api/resources
curl -H "Accept:application/vnd.welaika-v1+json" https://www.domain.com/api/resources
A good way is the Stripe approach to API versioning: the URL contains the mayor version (for example https://www.domain.com/api/v1
) and the client can request a sub-version with HTTP Headers.
1
curl -H "Accept-Version:v1.6" https://www.domain.com/api/v1/resources
REST verbs
The verb represents the action to be performed on the resource(s) and it corresponds to CRUD operations
POST
means CREATE- Used to create a resource
GET
means READ- Used to request (read) informations, like a resource or a resources collection
PUT
/PATCH
means UPDATE- Used to update a resource
DELETE
means DELETE- Used to delete a resource
Thanks to the verbs, you can use concrete naming and not action-verbs to describe your resources.
For example a SOAP / RPC way could be
getResources()
getResource(1)
createResource(...)
updateResource(1, ...)
deleteResource(1)
By contrast, in a RESTful approach you interact with the resource using HTTP verbs
GET /resources/
GET /resources/1
POST /resources/
PUT /resources/1
DELETE /resources/1
A small tip: use the plural as resource, for example resources
instead of resource
.
Relationship
You should be careful with nested relationship: you should use them only if a resource can exists within another one, for example
GET /movies/1/actors
: list all the actors of the movie with id 1GET /movies/1/actors/1
: get the actor with id 1 of the movie with id 1POST /movies/1/actors
: create a new actor for the movie with id 1PUT /movies/1/actors/1
: update the actor with id 1 of the movie with id 1DELETE /movies/1/actors/1
: delete the actor with id 1 of the movie with id 1
Don’t exceed with nested levels of resources!
Response
HTTPS Status Code
Each response must have the right HTTP status code to help the client to decide how to act. They have been established, agreed upon (RFC 7231).
Usually we spend a lot of time picking the right status code trying to be more semantic.
The status codes summary is:
- 10x: informational
- 20x: everything is OK. Hurray!!!
- 30x: go there
- 4xx: it’s YOUR fault
- 50x: it’s OUR fault
20x - Successful
200 OK
- Basic success code. Right for the most cases
- Especially used on successful first
GET
requests orPUT
/PATCH
updated content
201 Created
- It indicates that the resource has been created
- Typically replying to
PUT
andPOST
requests - You should specify URI of new resource in
Location
header
202 Accepted
- It indicates that the request has been accepted for processing
- Typically used as answer to an asynchronous processing call
204 No Content
- The request succeeded and the server have no intended return. Usually sent after a successful
DELETE
- You should use a
200
code when the result is an empty array/collection because the204
one it’s intended for input actions - Another problem with the
204
use for empty collection is the client needs extra code to check for that case and skip the parsing instead of parse an empty valid array
- The request succeeded and the server have no intended return. Usually sent after a successful
206 Partial Content
- The returned resource is incomplete
- Typically used with paginated resources
4xx - Client Error
400 Bad Request
- Generic error for a request that cannot be processed due to malformed syntax of the request (not of the resource). You shouldn’t get confused with the
422
HTTP status code
- Generic error for a request that cannot be processed due to malformed syntax of the request (not of the resource). You shouldn’t get confused with the
401 Unauthorized
- I don’t know you: tell me who are you before asking me anything
403 Forbidden
- Your rights are not enough to access this resource
404 Not Found
- The resource you are requesting doesn’t exists. You shouldn’t get confused with the
410
HTTP status code.
- The resource you are requesting doesn’t exists. You shouldn’t get confused with the
405 Method Not Allowed
- The method used in the request is not supported or relevant on this resource.
406 Not Acceptable
- There is nothing to send that matches the Accept-* headers. For example, you have requested a resource with XML (shame on you!) but there is only JSON available
410 Gone
- The resource you are requesting does not exist anymore (but in the past it existed)
422 Unprocessable Entity
- The resource you sent in a
PUT
/PATCH
/POST
request can’t be accepted, for example because of a missing required field or a not acceptable value in a field - Even if the
422
is part of the WebDAV extension, from several years it has become a reasonable alternative to the400
HTTP status code: for example it’s used in JSON API.
- The resource you sent in a
429 Too Many Requests
- Chill out, bro. You are stressing me out. Take a break.
- It has been introduced with RFC 6585
- In a good post about Best Practices for Designing a Pragmatic RESTful API, the author suggested that the server should include the following HTTP headers:
X-Rate-Limit-Limit
: The number of allowed requests in the current periodX-Rate-Limit-Remaining
: The number of remaining requests in the current periodX-Rate-Limit-Reset
: The number of seconds left in the current period before the reset. Don’t use a timestamp with unnecessary information: the client needs only to know when it can send a new request again.
50x - Server Error
500 Internal Server Error
- The request seems right, but a problem occurred on the server. The client cannot do anything about that.
510 Not implemented
- You make a request to an endpoint that hasn’t been implemented. Yet.
Query and Filter string
Filter and search
You must to use ‘?’ and the name of the attribute to filter resources
1
GET /resources?type=bill&id_user=007&title=qwerty
You may use the “Google way” to perform a search on multiple fields.
1
GET /resources/?q=qwerty+ytrewq
For common queries you can add an alias
1
GET /resources/released
Pagination
You may use a range query parameter. Pagination is mandatory: a default pagination has to be defined, for example
1
https://www.domain.com/api/v1/resources?page=7
The client should be able to define also how many resources should be in each page
1
https://www.domain.com/api/v1/resources?page=7&per-page=15
The response should contain the Link
HTTP header and other informations about the collection as the proposed RFC-5988 standard for Web linking.
1
2
3
4
5
6
Link: <https://www.domain.com/api/v1/resources?page=1>; rel="first",
<https://www.domain.com/api/v1/resources?page=123>; rel="last",
<https://www.domain.com/api/v1/resources?page=7>; rel="next",
<https://www.domain.com/api/v1/resources?page=5>; rel="prev"
Total: 4321
Per-Page: 15
Sort
Use the query parameter sort
to sort resources.
1
?sort=field1,fieldN
By default resources are sorted in ascending order. Use the minus in a specific attribute to sort resources in descending order
1
?sort=-field1,-fieldN
So you can obtain a complex sorting in ascending and descending order
1
GET /movies?sort=id,-rating,-name,updated_at
Representations
Clients don’t have direct access to resources: they only see their representations (the response of the API) and it can be in XML or in JavaScript Object Notation (JSON).
The format of the representation is managed with content negotiation in a pure RESTful way: in the Accept
header of the request, in order of preference. Usually the default format is JSON.
For example Accept: application/json, text/plain
and not /api/v1/resources.json
.
In the next post we will see how to design a good JSON representation.