Beautiful REST

API Design

Brian Retterer

@bretterer

 

brian@stormpath.com

Bad REST API design

/getAccount?id=3

/createAccount

/deleteUser?id=3

/updateAccount?id=3&json=1

/getAllAccounts

/searchForAccount?fname=homer

/getAccountComments?id=3&return=json

/getAccount?id=3&withComments=1

/getAllAccounts?format=json

/searchForAccount?fname=homer&return=xml

/deleteAccount?id=3

/addCommentByAccount?id=3&comment=This+API+SUCKS

/getAccount?id=3&return=json

/getAccount?id=3&withComments=1&fmt=json

/getAccountComments?id=3

/updateAccount?id=3

/getAllAccounts?json=true

Bad

REST
API

HATEOAS

"A REST API should be entered with no prior knowledge beyond the initial URI (bookmark) and set of standardized media types that are appropriate for the intended audience (i.e., expected to be understood by any client that might use the API). From that point on, all application state transitions must be driven by client selection of server-provided choices that are present in the received representations or implied by the user’s manipulation of those representations."  - Dr. Fielding

WHAT?

What is a good REST API?

  • Discoverable and self documented
  • Represent Instance Resources and Collections
  • Use HTTP verbs
  • Keep it simple where possible (KISS Method)

Lets
Talk
Verbs

Behavior

Create, Read, Update, Delete

!= 1:1

Post, Get, Put, Delete

Behavior

  • GET = READ

  • DELETE = DELETE

  • HEAD = Headers Only, No Body

  • OPTIONS = All available options for endpoint

POST and PUT can be used for create and update

PUT FOR CREATE

Identifier is known by Client:

PUT /applications/{clientSpecifiedId}

{
	...
}

PUT FOR UPDATE

Full Replacement:

PUT /applications/{existingId}

{
	"name": "Marvel Comics",
	"description": "We are the best!"
}

POST FOR CREATE

On Parent Resource

POST /applications

{
    "name": "DC Comics",
    "description": "We are the best!"
}

POST FOR UPDATE

On Instance Resource:

POST /applications/abc123

{
    "description": "Ok Marvel is better"
}

Rebuilding
Our
Example

Rebuilding Our Example

/users

/users/3

/users/3/comments

/comments

GET: List all users

POST/PUT: Create User

GET: List the user

POST/PUT: Update the user

DELETE: Delete the user

GET: List the users Comments

POST/PUT: Create a comment for User

GET: List all comments

Search:

/users?fname=homer

Pagination:

/users?offset=1&limit=10

Media Types

  • Request: Accept Header
  • Response: Content-Type Header

 

Accept: application/json

Content-Type: application/json

Domain Design

https://example.io/developer/api/public/rest

 

vs.

 

https://api.example.io

Versioning

https://api.example.io/?version=1

 

vs.

 

https://api.example.io/v1

 

RESPONSES

ION Working Group

http://ionwg.org/draft-ion.html

Getting Single User

                                GET /users/3


200 OK
{
    "meta": {"href": "https://example.io/users/3"},
    "fname": "Homer",
    "lname": "Simpson"
}

Getting All Users

                                GET /users


200 OK
{
    "meta": {"href": "https://example.io/users},
    "items": [{
        "meta": {"href": "https://example.io/users/3"},
        "fname": "Homer",
        "lname": "Simpson"
    }, {
        "meta": {"href": "https://example.io/users/4"},
        "fname": "Bart",
        "lname": "Simpson"
    }]
}

Linked Resources

                                GET /users/3


200 OK
{
    "meta": {"href": "https://example.io/users/3},
    "fname": "Homer",
    "lname": "Simpson",
    "comments": {
        "meta": {
            "href": "https://example.io/users/3/comments"
        }
    }
}

Discoverable Forms

                                GET /users

200 OK
{
    "meta": {"href": "https://example.io/users"},
    "items": [...],
    "create": {
        "meta": {
            "href": "https://example.io/users",
            "rel": ["create-form"],
            "method": "POST"
        },
        "items": [
            {"name": "fname"},
            {"name": "lname}
        ]
    }
}

Discoverable Forms

                                GET /users

200 OK
{
    "meta": {"href": "https://example.io/users"},
    "items": [...],
    "create": {...}
    "search": {
        "meta": {
            "href": "https://example.io/users",
            "rel": ["search-form"],
            "method": "GET"
        },
        "items": [
            {"name": "fname"},
            {"name": "lname}
        ]
    }
}

The Gateway (example.io/)

                                GET /

200 OK
{
    "meta": {"href": "https://example.io/"},
    "users": {
        "meta": {
            "href": "https://example.io/users",
            "rel": ["collection"]
        }
    }
}

How to respond

with errors

Errors Should...

  • be as descriptive as possible
  • provide a way to get more info
  • be clear about http error code
  • have reference to internal info

Error Message

                                POST /users


409 Conflict

{
    "status": 409,
    "code": 40924,
    "property": "email",
    "message": "A user with the email `homer@thesimpsons.com` already exists.",
    "developerMessage": "A user with the email `homer@thesimpsons`
              already exists. If you have a stale local cache,
              please expire it now",
    "moreInfo": "https://docs.example.io/errors/40924"
}

401 VS 403

  • 401 "Unauthorized" really means Unauthenticated
  • 403 "Forbidden" really means Unauthorized

Side Notes

Date/Time/Timestamp

                                {
    ...
    "createdAt": "2016-10-19T17:55:00.123Z"
    ...
}

Use UTC (Zulu) time

Side Notes

Resource Extensions

                                https://example.io/users/3.xml
https://example.io/users/3.txt
https://example.io/users/3.html

Adding an extension will override the Accept header

Side Notes

Allow Method Overrides

                                POST /accounts/3?_method=DELETE

This should be done if you expect your users to use the web browser or some way that does not allow for anything verbs other than GET and POST

Side Notes

ID's

  • Avoid auto incrementing ids
  • Should be globally unique
  • Good Candidate: UUID

Beautiful REST

API Design

Brian Retterer

@bretterer

 

brian@stormpath.com

Thank You!

Brian Retterer | brian@stormpath.com | @bretterer