Source: https://www.youtube.com/watch?v=5WXYw4J4QOU
POST
,GET
,PUT
,DELETE
!= CRUD- no verbs -> just nouns for Resources:
/applications
or/applications/123
- Possible Responses: Collection of Resources (e.g. with Links) or One instance of a Resource
- Keep your API course grained to be scaleble to future requirements
- For Resources: Be as specific as possible:
/customers
vs./newsletter-customers
and/registered-customers
PUT
for Create: Can be used when the Client has the ability to create an identifier for the Resource himself. But it has to contain a full replacement of the dataset:PUT /applications/123
PUT
for Update: Has to contain the a full replacement of the dataset (Full Update Operation): The reason why is thatPUT
has to be Idempotent (??? Does this make sense ???)POST
for Create: The request goes to a parent resource like:POST /applications
The response has to be201 Created
and have a header withLocation
of the just created resource:https://api.myapp.com/applications/123
POST
for Update: Only change one property of the resource (Partial Update): Reponse should be200 OK
.POST
is not idempotent. You can't call it multiple times and expect the resource to be identical after eaach request- Media-Types: For format specification and parsing rules of the response body for the client. For Request:
Accept
-Header (Client tells the server what format it wants). For Response:Content-Type
-Header (Contains the MIME-Type of the body being sent: e.g.application/json
or custom MIME-Types likeapplication/foo+json
(the body is structured to the foo media type specification) orapplication/foo+json;application
(add addons/fragments to express not only the format but also the resource inside of it: This is a foo JSON document that happens to be an application Ressource) - Base-URL:
https://api.foo.com
(simpler -> better as customers (developers) want the easiest path to adoption) vs.https://www.foo.com/dev/service/api/rest
- Versioning: URL
https://api.foo.com/v1
(simpler -> pragmatic approach) vs. Media-Typeapplication/json+foo;application,v=1
(URL doesn't change but it's hard for client developers to implement / understand it) - Resource Formats:
- Use camelCase notation for properties
- Date/Time/Timestamp: There is a standard: Use it: ISO 8601:
{ "createdTimestamp": "2017-11-15Tt18:10:24.343Z" }
and use UTC! - HREF: Every Resource contains its own unique link in the response body:
{ "href": "https://api.foo.com/v1/accounts/123" }
- Response Body:
GET
should return a response body always (Captain Obvious).POST
should contain it when its feasable to have the most recent version of that object which might be slightly different than the request object: Return it by default and the client always gets the newest version of that object (or as an alternative: give the power to the client with:?\_body=false
to let him decide whether or not he wants the object back)
- Content Negotiation: Get the content that you want as a client:
Accept
-Header:Accept: application/json, text/plain
vs. Resource Extension: Like/applications/123.json
orapplications/123.csv
(it conventionally overrides theAccept
-Header - Resource References (Linking):
- Instance Reference:
GET /accounts/123
{ "href": "https://api.foo.com/v1/accounts/123", "name": "Tony", ... }
- Collection Reference:
GET /accounts/123
{ "href": "https://api.foo.com/v1/accounts/123", "name": "Tony", ..., "directory": { "href": "https://api.foo.com/v1/directories/345" } }
- Instance Reference:
- Reference Expansion (aka Entity Expansion or Link Expansion):
GET /accounts/123?expand=directory
{ "href": "https://api.foo.com/v1/accounts/123", "name": "Tony", ..., "directory": { "href": "https://api.foo.com/v1/directories/345", "name": "Avengers", ... } }
For Caching: Query-Parameters are taking into account for caching rules, so this is being cached under this specific URL - Partial Representations:
GET /accounts/123?fields=name,surname,directory(name)
- Pagination: Collection Resource supports querystring params: Offset, Limit:
/applications?offset=50&limit=25
- Example:
GET /accounts
{ "href": "https://api.foo.com/v1/accounts", "offset": 0, "limit": 25, "first": { "href": "https://api.foo.com/v1/accounts?offset=0" }, "previous": null, "next": { "href": "https://api.foo.com/v1/accounts?offset=25" }, "last": { "href": "..." }, "items": [ { "href": "https://api.foo.com/v1/accounts/1" }, { "href": "https://api.foo.com/v1/accounts/2" }, { "href": "https://api.foo.com/v1/accounts/3" }, ... ] }
- Example:
- Many to Many: Each n:m Mapping is a Resource: e.g. Group to Account: A Group contains Accounts and an Account contains Groups: The Ressource would be
GroupMembership
:- Example:
GET /groupMemberships/678
{ "href": "https://api.foo.com/groupMemberships/678", "account": { "href": "https://api.foo.com/accounts/123" }, "group": { "href": "https://api.foo.com/groups/234" }, ... }
- Example:
- Another Example with the Ressource "Account": It contains the groups directly as well as the groupMemberships:
GET /accounts/134
{
"href": "https://api.foo.com/account/123",
"name": "Tony",
...,
"groups": [{ "href": "https://api.foo.com/groups/234" }],
"groupMemberships": { "href": "https://api.foo.com/groupMemberships?accountId=123" }
}
- Errors: Contain as much Information as possible (Developers are your customers)
- Example:
POST /directories
->409 Conflict
{ "status": 409, "code": 40924, "property": "name", "message": "A directory named 'Avengers' already exists.", "developerMessage": "A directory named 'Avengers' already exists. If you have a stale local cache, please expire it now.", "moreInfo": "https://www.foo.com/docs/api/errors/40924" }
- Example:
- IDs: Should be globally unique. Avoid sequential numbers (possible security risk). Good candidates: UUIDs or Url64
- HTTP Method Overrides: If clients doesn't support anything other than GET or POST:
POST /accounts/123?_method=DELETE
- Caching & Concurreny Control: ETag == Version Number for a specific Ressource:
- Example:
- Server (initial response):
ETag: "2344afa23432"
- Client (later request):
If-None-Match: "2344afa23432"
- Server (later response):
304 Not Modified
- Security: Avoid sessions when possible (stateless scales better). Authorize based on Ressource content, and not on URLs (they can change). Use an existing protocol like OAuth2, Basic Auth over SSL
- Maintenance: Use HTTP Redirects (e.g. for moving Ressources -> migrate URLs or deprecate them). Create abstraction layers in your code to minimize change in the API. Use well defined custom Media-Types (If you can do it: It is most resilient to changes over time).