Theory
Ivan Novakov
What are the typical features of a RESTful API?
What is REST?
REpresentational State Transfer
A PhD dissertation by Roy Fielding
A core set of principles, properties and constraints describing the architectural style of the World Wide Web.
Focus on component roles and interactions rather than implementation details.
200 OK
Content-Type: text/html
<html>
<body>
<h1>Title</h1>
<p>John Smith</p>
<!-- ... -->
</body>
</html>
200 OK
Content-Type: application/json
{
"id": 123,
"title": "Title",
"author": "John Smith",
...
}
REST != API
REST is the foundation of the World Wide Web.
GET /posts/123 HTTP/1.1
Accept: text/html
Request
GET /posts/123 HTTP/1.1
Accept: application/json
Response
REST principles
Richardson Maturity Model:
REST and HTTP
HTTP as a transport layer.
HTTP as a tunneling mechanism for remote interaction.
SOAP, XML-RPC, ... they (may) also use HTTP.
Resources
Everything is a resource!
Resource is an object, entity, ... anything, which can be described using a noun.
...or a collection of resources.
Identifiers
Every resource has a unique identifier (URI/URL).
Resource URLs usually resemble directory-like hierarchical structures.
https://api.news.example.com/v1/articles/123
https://api.finance.example.com/v2/currencies/USD
https://api.directory.example.com/v1/users
https://api.directory.example.com/v1/users?location=Prague
/articles
/articles/123
/articles/123/tags
/articles/123/tags/elections
Sub-resources
Resources dependent on the main resource:
/articles/123/paragraphs
/articles/123/paragraphs/1
/articles/123/tags
/articles/123/tags/elections
/users/jsmith/certificates
/users/jsmith/roomNumber
Resources related to the main resource:
Much better than:
/paragraphs?article=123
Resources and identifiers
/articles/123
/articles/123/author
/authors
/authors/456
/authors/456/publications
/categories
/categories/789
/categories/789/articles
/server/restart
/rpc/getStatus
/rpc/updateStatus
/rpc/getUsersByName
/article/123/publish
/article/123/delete
Resource:
Not a resource:
Verbs
Resources are manipulated through verbs - GET, POST, PUT, PATCH, DELETE, ...
The usage of these verbs should follow closely the HTTP specification.
GET
GET /articles/123 HTTP/1.1
Accept: application/json
POST
POST /articles HTTP/1.1
Content-Type: application/json
{
"title": "A new title",
"author": "John Smith",
...
}
PUT
PUT /articles/123 HTTP/1.1
Content-Type: application/json
{
"title": "A new title",
"author": "John Smith",
...
}
PATCH
PATCH /articles HTTP/1.1
Content-Type: application/json
{
"title": "A new title"
}
DELETE
DELETE /articles/123 HTTP/1.1
Verbs
Verb | /articles | /articles/123 |
---|---|---|
GET | Fetch a collection | Fetch a resource |
POST | Create a new resource | |
PUT | Update a resource | |
PATCH | Partially update a resource | |
DELETE | Delete a resource |
HTTP status codes
A RESTful service uses the HTTP status code as a primary indicator about the success of a request.
2xx
Success
3xx
The client should perform an additonal action
4xx
Error caused by the client
5xx
Error caused by the server
Error reporting
400 Bad Request
Content-Type: application/json
{
"error": "invalid input",
"message": "The username should be more than 3 characters long.",
}
503 Service Unavailable
Content-Type: application/json
{
"error": "service down",
"message": "Cannot connect to the database",
"details": {
"db": "ORA-12150: TNS:unable to send data"
}
}
Headers
Use HTTP headers for any metadata you need to transfer between the client and the server.
Metadata - any data, which are not part of the resource, but are somehow part of the interaction:
Content negotiation
GET /users/123 HTTP/1.1
Accept: application/json
200 OK
Content-Type: application/json;charset=utf-8
Content-Length: 180
{
"username": "jsmith",
...
}
Authentication and authorization
GET /users/123
Accept: application/json
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
401 Unauthorized
WWW-Authenticate: Basic realm="My Service"
GET /users/123
Accept: application/json
Request
Second request
Response
Cache control
GET /users/123
Accept: application/json
200 OK
Content-Type: application/json
Last-Modified: Fri, 03 Jun 2016 14:30:30 GMT
{ ... }
GET /users/123
Accept: application/json
If-Modified-Since: Fri, 03 Jun 2016 14:30:30 GMT
304 Not Modified
Content-Length: 0
Request
Request
Response
Response
Custom headers
X-Merck-<name>: <value>
X-Merck-APIKey: 1234567890
X-Merck-Some-Other-Header: foo
Current naming recommendation:
But the usage of "X-" prefixed headers has been deprecated!
Not quite relevant for us. Anyway, use some unique prefix, when defining custom headers.
Query parameters
/users?location=Prague
/users?location=Prague&firstName=Ivan
Filtering
Paging
/users?page=12&pageSize=10
/users?limit=12&offset=110
Sorting
/users?sortBy=firstName&sortDest=ASC
/users?sortBy=employeeNumber&sortDest=DESC
Combined
/users?location=Prague&firstName=Ivan&sortBy=employeeNumber&page=12
Hypermedia (HATEOAS)
Hypermedia As The Engine Of Application State
Hypermedia (HATEOAS)
200 OK
Content-Type: application/json
{
_links: {
self: {
href: "/users/123"
},
manager: {
href: "/users/456"
}
}
username: "jsmith",
...
}
200 OK
Content-Type: application/json
Link: </users/123>;rel=self,</users/456>;rel=manager
{
"username": "jsmith",
...
}
Hypermedia (HATEOAS)
200 OK
Content-Type: application/json
[
{
"_links": { "self": { "href": "/users/123" } },
"username": "jsmith"
},
{
"_links": { "self": { "href": "/users/456" } },
"username": "foobar"
}
...
]
GET /users
Accept: application/json
Representation
The JSON (XML, YAML, CSV, ...) you get from a RESTful endpoint is not the resource, but its representation.
With content negotiation you can be quite flexible when choosing the right representation for your API.
Choosing representation
HAL
Hypertext Application Language
HAL
Hypertext Application Language
{
"_links": {
"self": { "href": "/employees/123456" },
"manager": { "href": "/employees/789012" }
},
"employeeId": 123456,
"firstName": "Ivan",
"lastName": "Novakov",
"email": "ivan.novakov@company.com"
}
Single resource
HAL
Hypertext Application Language
{
"_links": {
"self": { "href": "/employees/123456" }
},
"employeeId": 123456,
"firstName": "Ivan",
"lastName": "Novakov",
"email": "ivan.novakov@company.com",
"_embedded": {
"manager": {
"_links": {
"self": { "href": "/employees/789012" }
},
"employeeId": 789012,
"firstName": "John",
"lastName": "Smith"
}
}
}
Single resource with embedded subresource
{
"_links": {
"self": { "href": "/employees/123456" }
},
"employeeId": 123456,
"firstName": "Ivan",
"lastName": "Novakov",
"email": "ivan.novakov@company.com",
"_embedded": {
"department": {
"_links": {
"self": { "href": "/departments/123" }
},
"id": 123,
"name": "IT Department",
"description": "The department running all the computers"
},
"manager": {
"_links": {
"self": { "href": "/employees/789012" }
},
"employeeId": 789012,
"firstName": "John",
"lastName": "Smith"
}
}
}
Single resource with embedded subresource
{
"_links": {
"self": { "href": "/employees?page=4" },
"first": { "href": "/employees" },
"next": { "href": "/employees?page=5" },
"previous": { "href": "/employees?page=3" },
"last": { "href": "/employees?page=120" }
},
"count": 10,
"total": 1250,
"_embedded": {
"employees": [
{
"_links": { "self": { "href": "/employees/41" } },
"employeeId": 41
},
{
"_links": { "self": { "href": "/employees/42" } },
"employeeId": 42
},
{
"_links": { "self": { "href": "/employees/43" } },
"employeeId": 43
},
...
]
}
}
Collection
Some representation formats
Consistency
Resources should be consistent in the way they return data.
An API request should (always) return one of the following:
Consistency
GET /articles --> collection
GET /articles/123 --> resource
GET /articles/123/author --> resource
GET /articles/123/tags --> collection
GET /articles/123/tags/foo --> resource
POST /articles --> returns the newly created resource or
--> 201 Created + redirect to the new resource
PUT or PATCH /articles/123 --> returns the modified resource
DELETE /articles/123 --> 204 No Content
Versioning
Why should your API support different versions?
Backward compatibility!
Versioning
URL:
https://iapi.merck.com/comet-otc/v1/orders
https://iapi.merck.com/comet-otc/v2/orders
Custom request header:
GET /comet-otc/orders
X-Merck-API-Version: 2
Content-Type / Accept header:
GET /comet-otc/orders
Accept: application/vnd.merck.api.comet-otc.v2+json
200 OK
Content-Type: application/vnd.merck.api.comet-otc.v2+json
{ ... }
Versioning
Design
Do you really need a REST API?
When you don't need an API:
But you can have an API to manage the process.
API vs. DB
API | DB |
---|---|
GET /resource/123 | SELECT * from table WHERE id = 123 |
POST /resource | INSERT INTO table VALUES (...) |
PATCH /resource/123 | UPDATE table SET field = value, ... WHERE id = 123 |
DELETE /resource/123 | DELETE FROM table WHERE id = 123 |
API vs. DB
When the API is just a thin layer above the DB:
API vs. DB
APIs should provide a certain level of abstraction
Discussion
Backup
AuthN / AuthZ
Client authenticaton - API key (client ID, client secret, ...)
User authentication - OAuth v2, Open ID Connect, JSON Web Token, ...
GET /comet-otc/orders
X-Merck-APIKey: 4ef87329b14a314a9549fa26a5e3931f
GET /comet-otc/v2/orders
X-Merck-APIKey: 4ef87329b14a314a9549fa26a5e3931f
Authorization: Bearer aa355d09e6a9d0f9e791274f163870be
Security
Confidential vs. public client applications
Mobile app --> public
Browser based HTML5 app --> public
Web app (on a remote server) --> private
Security
Always use HTTPS!
Sensitive values should not be sent as a part of the URL.
GET /comet-otc/v2/orders?apikey=aa355d09e6a9d0f9e791274f163870be
Avoid Basic Auth if possible and use OAuth2 instead. Use standard AuthN/AuthZ mechanisms.
Use rate limiting both on for the client side and the backend side.
Validate all input!