1. Background
REST
(English: Representational State Transfer
Representational State transfer) describes a schema-style network system, such as a Web application.
At present, the Internet is flooded with RESTful API
articles about how to design (for convenience, " RESTful API
shorthand for" API
), but there is no "universal" design standard: How to Authentication? What is the API format? Should your API include version information? When you start to write an app, especially when the backend model is finished, you have to design and implement the public API part of your app. Because once published, the API will be difficult to change.
In order to design an API that is easy to use, easy to deploy, and flexible enough, this article is born.
2. Basic Requirements for API design
Many of the ideas on the web about API design are "college", they may be more theoretical, but sometimes derail the real world (so I'm a liberal). So my goal in this article is to start from a practical point of view and give the best practices of API design for current Web applications. Of course, as the basis of design, a few of the principles must be adhered to:
- Comply with standards when the standards are reasonable.
- The API should be programmer-friendly and easy to enter in the browser's address bar.
- The API should be simple, intuitive, easy to use and elegant at the same time.
- The API should be flexible enough to support the upper UI.
- The API design weighs several of these principles.
It is important to emphasize that the API is the programmer's UI, and like any other UI, you must carefully consider its user experience!
3. Use RESTful URLs and action.
Although I said before I did not have a universal API design standard. But there is one that is universally recognized and adhered to: RESTful
design principles. It was presented by Roy Felding (fifth chapter in his "Web-based Software Architecture" paper). The core principle of rest is to split your API into logical resources . These resources are manipulated via HTTP ( GET
,,, POST
PUT
DELETE
).
4. How should I split these resources?
Obviously from an API user's point of view, " resources " should be a noun. Even if your internal data model and resources already have a good correspondence, API design you still do not need to expose them to one-on. The key here is to hide the internal resources and expose the necessary external resources.
In SupportFu
, resources are ticket
, and user
group
.
Once you have defined the resources you want to expose, you can define the actions that are allowed on the resource, and the corresponding relationships between those actions and your API:
Get/tickets # Get Ticket list
GET/TICKETS/12 # to view a specific ticket POST
/tickets # Create a new ticket
PUT/TICKETS/12 # Update Ticket 12.
DELETE/TICKETS/12 #删除ticekt 12
It can be seen REST
that the advantage of using HTTP is to make full use of the powerful implementation of the ( CURD
Create ( Create
), update ( Update
), read (Read
) and delete () Delete
) functions of the resource. And here you just need a endpoint:/tickets, no other naming rules and URL rules, cool!
5. The singular plural of this endpoint
One rule to follow is that although it seems awkward to use complex numbers to describe a resource instance, unifying all of them endpoint
makes your URLs more structured using complex numbers. This makes it easier for API users to understand and easier for developers to implement.
How do I handle associations? There is also a description of how to handle the management principles between resources REST
:
Get/tickets/12/messages-retrieves (Enquiry) List of messages for ticket #12
get/tickets/12/messages/5-retrieves message #5 for ticket #12
Post/tickets/12/messages-creates a new message in ticket #12
put/tickets/12/messages/5-Updates message #5 for ticket #12
patch/tickets/12/messages/5-partially updates message #5 for ticket #12
delete/tickets/12/messages/5-deletes message #5 for ticket #12
Where, if this association and resource are independent, we can save the corresponding resource in the output representation of the resource endpoint
. The user of the API can then find the relevant resources by clicking on the link. If the association and resources are closely linked. The output of the resource indicates that the corresponding resource information should be saved directly. (for example, if the message resource exists independently, the get/tickets/12/messages on the above will return a link to the corresponding message; If the message does not exist independently, he and ticket are attached, The above API call returns a direct return message)
6. Non-conformance to curd operation
For this puzzling question, here are some workarounds:
Refactor your behavior action
. When your behavior does not require parameters, you can put the active
corresponding to activated
this resource, (update use patch
).
Treated with sub-resources. For example: github
on, to one gists
star operation: PUT /gists/:id/star
and Cancel star operation: DELETE /gists/:id/sta
R.
Sometimes it action
is not difficult to correspond with a resource for example search
. Then let's do it. I don't think the user of the API /search
will have much of a problem with this URL (he's easy to understand, after all). Just note that you can write clearly in the document.
7. Always use SSL (secure Sockets Layer Security socket)
SSL is always used, without exception. Your app doesn't know who to be, and what the situation is. Some are safe, some are not. Using SSL can reduce the cost of authentication: You only need a simple token () to token
be able to be authenticated, rather than having the user sign each request every time.
It is important to note that non-SSL URL access is not redirected to the SSL URL.
8. Documentation
Documentation is as important as the API itself. Documents should be easy to find and open (hiding them in a PDF or saving them in a place where they need to be logged in is not very good). The document should have an example of displaying requests and outputs: either by clicking on the link or by means of curl. If there is an update (especially a public API), the document should be updated in a timely manner. The documentation should have a schedule and details about when to discard an API. It's a good way to use mailing lists or blog entries.
9. Versioning
Adding version information to the API effectively prevents users from accessing the updated API, while also allowing for a smooth transition between different major versions. There is a controversy over whether to put the version information into the URL or the request header: API version should be included in the URL or in a header. Academia says it should be put in the header, but if you put it in the URL we can access the resources across versions.
The method used by strip is good: It has the main version information in its URL, and the request of the first two sides has the child version information. This makes the URL stable during the change of the child version. Change is sometimes unavoidable, and the key is how to manage change. Complete documentation and a reasonable schedule make it easier for API users to use.
10. Results filter, sort, search:
URLs are best as short as possible, and results are filtered, sorted, and search-related functions should be implemented via parameters (and also easily implemented).
filtering : Provides uniform parameters for all interfaces that provide filtering functionality. For example: you want to limit get /tickets
The return result: only those states are returned open
ticket–get /tickektsstate=open
here state
is the filter parameter.
sort : As with filtering, a good sort parameter should be able to describe the collation rather than the business-related. Complex collations should be implemented by combining:
Get/ticketssort=-priority-retrieves a list of tickets in descending order of priority
Get/ticketssort=-priority,created_at-retrieves a list of tickets in descending order of priority. Within a specific priority, older tickets is ordered first
In the second query here, the collation has more than one rule that is combined with a comma interval.
Search : Sometimes a simple sort is not enough. We can use search technology (Elasticsearch and Lucene) to implement (still as a parameter to the URL).
- Get/ticketsq=return&state=open&sort=-priority,created_at-retrieve the highest priority open tickets Mentioning the word ' return '
For frequently used search queries, we can create aliases for them, which will make the API more elegant. For example:
get /ticketsq=recently_closed -> get /tickets/recently_closed.
11. Domains that limit API return values
Sometimes the API consumer does not need all the results, and it should be possible to limit the vertical when the horizontal limit is placed (for example, the top ten of the value return API results). And this function can effectively improve the network bandwidth usage and speed. You can use the fields query parameter to limit the domains returned, such as:
- Get/ticketsfields=id,subject,customer_name,updated_at&state=open&sort=-updated_at
12. Update and create operations should return resources
PUT
, POST
and PATCH
operations often have some side effects when working with resources: for example created_at
, timestamps updated_at
. To prevent the user from making multiple API calls (for this update), we should return the updated resource ( updated representation
.) For example: After the POST
operation, 201 created
a status code is returned, and a URL to the new resource is included as the return header.
13. Need for "HATEOAS" (Hypermedia as the Engine of application state)
Online about whether to allow users to create a new URL has a big objection (note is not to create a resource-generated URL). The REST
HATEOAS
endpoint
behavior should be defined in the metadata return value of the resource when it is developed to describe and interact with it.
(the author thinks that Hateoas is not mature yet)
14. Provide JSON as return format only
Now it's time to compare XML
and json
. XML
that is lengthy, difficult to read, and not suitable for various programming language parsing. Of course XML
There is the advantage of extensibility, but if you just serialize it to internal resources, then his extended advantage is not going to work out. Many applications ( youtube
, twitter
, box
) have started to abandon XML
. Give the trend map on Google:
Of course if you use users inside the majority of enterprise users, then may need support XML
. If this is the case, you have another question: is the http
type in your request to be media
synchronized with the accept
head or with url
? In order to facilitate ( browser explorability
), it should be in url
(the user as long as they spell url
it good). The best way to do this is to use .xml
or .json
the suffix.
15. Naming Methods
is the serpentine command (underscore and lowercase) or hump named (mixed with uppercase and lowercase letters to form the names of variables and functions)? If you use JSON then the best thing to do is JAVASCRIPT
to follow the naming method-that is, the camel name method. If you are writing a library in multiple languages, it is best to follow the recommendations of those languages, java
C#
using camels, python
and ruby
using snake
.
Personal opinion: I always feel that the snake command better make some, of course, there is no theoretical basis for this. Some people say that snake name read faster, can reach 20%, do not know true and false http://ieeexplore.ieee.org/xpl/articleDetails.jsptp=&arnumber=5521745
16. Use pretty print format by default, using gzip
Just use the return results of the spaces from the browser always feel disgusting. Of course you can provide the parameters on the URL to control the use of "pretty print", but it is more friendly to turn on this option by default. The loss on the extra transmission will not be too great. Conversely if you forget to use gzip then the transfer efficiency will be greatly reduced and the loss greatly increased. Imagine a user is debugging so the default output is readable--without having to copy the results to any other software in the format--it's cool to think about it, isn't it?
Here is an example:
$ curl https://API.github.com/users/veesahni > with-whitespace.txt$ ruby -r json -e ‘puts JSON JSON.parse(STDIN.read)‘ < with-whitespace.txt > without-whitespace.txt$ gzip -c with-whitespace.txt > with-whitespace.txt.gz$ gzip -c without-whitespace.txt > without-whitespace.txt.gz
The output is as follows:
- without-whitespace.txt-1252 bytes
- with-whitespace.txt-1369 bytes
- without-whitespace.txt.gz-496 bytes
- with-whitespace.txt.gz-509 bytes
In the example above, the extra space makes the result more than 8.5% (gzip not used), instead only 2.6% more. It is said that Twitter has reduced 80% (Link:https://dev.twitter.com/blog/announcing-gzip-compression-streaming-apis) by using Gzip after its streaming API transfer.
17. Use "envelope" (envelope) only when needed
Many APIs return results like this:
{ "data" : { "id" : 123, "name" : "John" }}
The reason is simple: this can easily extend the return result, you can add some paging information, some meta-information of the data, etc.-this is useful for those API users who are not easily accessible to the return header, but with the development of "standards" ( cors
and http://tools.ietf.org/html/rfc5988#page-6
all of them beginning to be added to the standard), I personally recommend not doing that.
18. When to use envelope?
There are two situations that should be used envelope
. If the API consumer does not have access to the return header, or the API needs to support cross-domain requests (through jsonp
).
jsonp
The request contains a function parameter in the URL of the request callback
. If this parameter is given, then the API should return 200 and put the real state into the return value (wrapped in an envelope), for example:
callback_function({ status_code: 200, next_page: "https://..", response: { ... actual JSON response body ... }})
Similarly, to support an API consumer that cannot return a header to a method, envelope=true
such a parameter can be allowed.
19. Use JSON as input on Post,put,patch
If you agree with what I said above, then you should decide to use json
as all the API output formats, then we'll consider the input data format of the API next.
Many APIs use URL-encoded formats: Just like the format of URL query parameters: Simple key-value pairs. This method is simple and effective, but it has its own problem: it has no concept of data type. This allows the program to parse the Boolean and integer based on the string, and there is no hierarchy – although there are some conventions about hierarchy information that are json
still not very useful compared to the support hierarchy itself.
Of course, if the API itself is simple, then using the URL format input is fine. But for complex APIs you should use json
. Or simply use it uniformly json
.
Note json
when using the transfer, the request header is added: Content-Type:application/json
., otherwise throws a 415 exception (unsupported media type).
20. Paging
The paging data can be put into "envelopes", but with the improvement of the standard, I now recommend putting paging information link header
inside: http://tools.ietf.org/html/rfc5988#page-6.
link header
the API used should return a series of well-assembled URLs instead of letting the user spell it out for themselves. This is especially important in cursor-based paging. For example, a document from GitHub
Link:
21. Automatic loading of related resourcesMany times, it is very useful to automatically load related resources, which can greatly improve the efficiency. But this is contrary to the principle of restful. To do so, we can add parameters to the URL: embed
(or expend
). embed
can be a comma-separated string, for example:
GET /ticket/12embed=customer.name,assigned_user
The corresponding API return values are as follows:
{ "id" : 12, "subject" : "I have a question!", "summary" : "Hi, ....", "customer" : { "name" : "Bob" }, assigned_user: { "id" : 42, "name" : "Jim", }}
It is worth reminding that this feature can sometimes be complex and can cause N+1 SELECT
problems.
22. Overriding the HTTP methodSome clients can only issue simple GET
and POST
request. In order to take care of them, we can rewrite the HTTP request. There is no standard here, but a common way is to accept the X-HTTP-Method-Override
request header.
23. Speed LimitTo avoid flooding requests, it is important to set speed limits on the API. RFC 6585
The HTTP status code is introduced for this purpose 429(too many requests)
. After adding the speed setting, the user should be prompted, as to how the standard is not indicated, but the popular method is to use the HTTP return header.
Here are a few of the required return headers (according to Twitter's naming conventions):
- X-rate-limit-limit: Number of concurrent requests allowed for the current time period
- X-rate-limit-remaining: The number of requests reserved for the current time period.
- X-rate-limit-reset: Number of seconds remaining in the current time period
24. Why use the remaining seconds of the current time period instead of the timestamp?Timestamps save a lot of information, but also contain a lot of unnecessary information, users only need to know that there are only a few seconds left to send the request again so that also avoids the clock skew
problem.
Some APIs use UNIX
format timestamps, and I don't recommend doing that. Why? HTTP already specifies the use RFC 1123
time format
25. Authentication Authenticationrestful API
Is stateless , which means that the user's request for authentication is irrelevant to the cookie and the session, and each request should contain a verification certificate.
By using ssl
we can not provide the user name and password every time: we can give the user back a randomly generated token
. This makes it extremely convenient for users who use the browser to access the API. This method is applicable to the user can first pass the user name-password authentication and get token
, and can copy the returned token
to a later request. If inconvenient, can be used OAuth 2
to carry out token
the safe transfer.
jsonp
the supported API requires an additional authentication method because the jsonp
request cannot be sent as normal credential
. In this case, you can add parameters to the query URL: access_token
. Note the problem with the URL parameter is that most of the network servers currently have query
parameters saved to the server log, which can be a major security risk.
Note that the above mentioned is only three transmission token的
methods, the actual transmission token
may be the same.
26. CachingHTTP provides a self-signed cache framework. What you need to do is add some return header information when you return and add input validation when you accept the input. There are two basic methods:
ETag: When a request is generated, it is added in the HTTP header ETag
, which contains the checksum and hash value of the request, and this value should change as the input changes. If the input HTTP request contains IF-NONE-MATCH
a header and a ETag
value, then the API should return the 304 not modified
status code instead of the regular output.
last-modified: And the etag
same, just one more time stamp. Returned to the head Last-Modified
: contains the timestamp RFC 1123
, and it is IF-MODIFIED-SINCE
consistent. There are three kinds of date formats in the HTTP specification, and the server should be able to handle them.
27. Error HandlingJust as the HTML error page can display an error message, the API should also return a readable error message – It should be consistent with the general resource format. The API should always return the appropriate status code to reflect the status of the server or request. API error codes can be divided into two parts, 400 series and 500 series, and400 series indicate client error : such as bad Request format. The 500 series represents a server error . The API should return at least all of the 400 series errors in the json
form. This should be true if the 500 series errors are possible. Errors in JSON format should contain the following information: A useful error message, a unique error code, and any possible detailed error description. As follows:
{ "code" : 1234, "message" : "Something bad happened :-(", "description" : "More details about the error here"}
Yes PUT
, POST
PATCH
The input checksum should also return the corresponding error message, for example:
{ "code" : 1024, "message" : "Validation Failed", "errors" : [ { "code" : 5432, "field" : "first_name", "message" : "First name cannot have fancy characters" }, { "code" : 5622, "field" : "password", "message" : "Password cannot be blank" } ]}
HTTP Status Code 200 ok - 成功返回状态,对应,GET,PUT,PATCH,DELETE. 201 created - 成功创建。 304 not modified - HTTP缓存有效。 400 bad request - 请求格式错误。 401 unauthorized - 未授权。 403 forbidden - 鉴权成功,但是该用户没有权限。 404 not found - 请求的资源不存在 405 method not allowed - 该http方法不被允许。 410 gone - 这个url对应的资源现在不可用。 415 unsupported media type - 请求类型错误。 422 unprocessable entity - 校验错误时用。 429 too many request - 请求过多。
Finish
RESTful API Design Best Practices