Guidelines for designing RESTful APIs, with dogs.
Hello! I'm Tom Spencer (@fiznool)
When I'm not racing F1 cars,
I'm a Full-Stack Web Developer,
most recently building APIs with Node.js.
Provide data access to a wide number of consumers.
REpresentational State Transfer
Very strict set of rules, governing client-server communication
Use as a guide, not a bible
Don't be a RESTifarian...
Design or study data model
Try to represent each model as a resource
Design URLs to access and manipulate resource (CRUD)
For a data model 'dog':
/dogs
/dogs/{id}
Prefer plural form 'dogs' over singular 'dog'
GET
POST
PUT
DELETE
Retrieve
Create
Update
Delete
Resource
/dogs
/dogs/1234
Create a new dog
List (index) dogs
Batch update dogs
(Delete all dogs)
Retrieve a dog
Update a dog
Delete a dog
(error)
/api/getAllDogs
/api/getDogById?id=1234
GET /api/dogs
GET /api/dogs/1234
/api/createDog
POST /api/dogs
Everything's OK
Three types of response:
Client did something wrong
Server did something wrong
Use HTTP Status Codes.
2xx
4xx
5xx
200 (OK)
Code
Use when
Request was handled successfully and no other 2xx codes apply.
201 (Created)
Request to create a resource was successful.
Example
GET /dogs
POST /dogs
204 (No Content)
Request was handled successfully, and no data was returned.
DELETE /dogs/1234
400 (Bad Request)
Code
Use when
Passed data was invalid.
401 (Unauthorised)
Client needs to supply credentials to access resource.
Example
POST /dogs
Resource does not exist.
GET /dogs/9876
GET /dogs
404 (Not Found)
Server threw an exception.
POST /dogs
500 (Server Error)
[{
"id": 1234,
"name": "Benji"
}, {
"id": 5678,
"name": "Fenton"
}]
GET /dogs
(XML is icky).
< 200 OK
[{
"id": 1234,
"name": "Heinz"
}, {
"id": 5678,
"name": "Fenton"
}]
Get all dogs
> GET /dogs
< 200 OK
{
"id": 1234,
"name": "Heinz"
}
Get dog by ID
> GET /dogs/1234
Dog not found
< 404 Not Found
> GET /dogs/4321
< 200 OK
{
"id": 1234,
"name": "Heinz"
}
Get all dogs dressed in a costume
> GET /dogs?dress=costume
Get dogs 1-2
< 200 OK
[{
"id": 1234,
"name": "Heinz"
}, {
"id": 5678,
"name": "Fenton"
}]
> GET /dogs?offset=0&limit=2
Get dogs 3-10
< 200 OK
[]
> GET /dogs?offset=2&limit=8
Get dogs, alphabetical
< 200 OK
[ {
"id": 5678,
"name": "Fenton"
}, {
"id": 1234,
"name": "Heinz"
}]
> GET /dogs?sort=name
Get dogs, reverse alphabetical
< 200 OK
[{
"id": 1234,
"name": "Heinz"
}, {
"id": 5678,
"name": "Fenton"
}]
Notice the - to sort in descending order
> GET /dogs?sort=-name
Create a new dog
< 201 Created
{
"id": 9010,
"name": "Santa's Little Helper"
}
> POST /dogs
{
"name": "Santa's Little Helper"
}
Validation Error
< 400 Bad Request
{
"errors": [{
"field": "name",
"type": "required",
"message": "Name is required"
}]
}
> POST /dogs
{
"name": ""
}
Update an existing dog
< 200 OK
{
"id": 1234,
"name": "Hot Dog"
}
> PUT /dogs/1234
{
"name": "Hot Dog"
}
Batch update dogs
> PUT /dogs
[{
"id": 1234,
"name": "Hot Dog"
}, {
"id": 5678,
"name": "Fentonnn!"
}]
< 200 OK
[{
"id": 1234,
"name": "Hot Dog"
}, {
"id": 5678,
"name": "Fentonnn!"
}]
Validation Error
> PUT /dogs/1234
{
"name": ""
}
< 400 Bad Request
{
"errors": [{
"field": "name",
"type": "required",
"message": "Name is required"
}]
}
Dog not found
< 404 Not Found
> PUT /dogs/4321
{
"name": "Hot Dog"
}
Delete an existing dog
< 204 No Content
> DELETE /dogs/1234
Dog not found
< 404 Not Found
> DELETE /dogs/4321
Some actions can't be represented RESTfully.
For example, how to tell server to walk the dog?
(Yes, this server is smart.)
Break the rules - use a verb.
Walk the dog
< 200 OK
{
"id": 5678,
"name": "Fenton",
"status": "walking",
"walkies": "youtube.com/embed/bmpONxJ7JSw?t=15s"
}
> POST /dogs/5678/walkies
Walk the dog
Preparing your API for the outside world
Go Stateless: send a token with every request
Two suggestions:
API Key
OAuth 2.0
Return 401 (Unauthorised) if token not valid
> GET /dogs
X-Access-Token: 1234321
API Key: Send token as a header
Simple
API consumer must acquire token manually
Identify individual users
Option 2: Use OAuth 2.0
Fully automated token exchange
Complex: you need to build/integrate an OAuth server
Identify individual users
Prevent consumers from tying up your server
The dreaded DoS!
Ties into authentication: track number of requests per user for a fixed period
Prevent access if limit reached (use status code 429: Too Many Requests)
They will happen...
If validation error, send as much info to the client as feasible.
< 400 Bad Request
{
"errors": [{
"field": "name",
"type": "required",
"message": "Name is required"
}, {
"field": "breed",
"type": "enum",
"message": "Breed must be one of
Corgi, Labrador"
}]
}
field
Name of field that was invalid
type
Type of validation error that was triggered
message
Error message to be displayed
If server error, send a general failure message.
< 500 Internal Error
{
"message": "There was a problem
processing your request."
}
Since the API is a black box, it may also be useful to provide a 'developer' message, which gives instructions to the developer about what happened.
Last, but certainly not least!
What happens if you don't provide good documentation?
It will take time, but it is essential.
Tools:
Inspiration:
A Test-Driven tutorial to build an API using the guiding principles of this slide deck.