API Design patterns
for the REST of US

MergePHP May 2023

Ian Littman / @ian@phpc.social / @iansltx

Alternate Title:
I Read Roy Fielding's dissertation
So you wouldn't have to

pro tip: start with chapter 5

What we'll cover

  • Principles of REST and what that means for your app
  • The API design process
  • General HTTP API design principles
    • URL Structure
    • Versioning
    • Status Codes
    • Headers
    • Authentication + Security

So...what's this REST thing?

  • Codified by Roy Fielding in his doctoral dissertation
  • An architectural (not code implementation!) style
  • A set of constraints that make interacting with a web-based system more predictable
  • Describes information structure on the Web, not just APIs

Constraint #1: Client-Server

Interfaces, not implementations

GET /tacos
Accept: video/mp4
{other headers here}

HTTP/1.1 200 OK
Cache-Control: public, max-age: 3600
Content-Type: video/mp4
Date: Fri, 21 Feb 2019 19:35:00 GMT
Vary: Accept-Encoding
Content-Length: 20190505
Connection: keep-alive
X-Powered-By: Scala on Scales

Constraint #2: Stateless

  • No concept of a "session" (with preferences etc. baked in)
  • Current user ID shouldn't be in URL (impacts caching!)
  • Cookies are an antipattern*
  • WebSockets aren't REST...and that's okay!

 

* They may be the best way to pass auth back and forth

Form follows function

An architecture for the web must therefore be designed with the context of communicating large-grain data objects across high-latency networks and multiple trust boundaries.

- Roy Fielding

Why large-grain data objects?

  • Amortizes communication overhead
  • More consistent format allows for more caching

Constraint #3: Cacheable

An interesting observation is that the most efficient network request is one that doesn't use the network.

- Roy Fielding

HTTP/1.1 304 Not Modified
Cache-Control: public, max-age: 3600
ETag: a1234b1234
Content-Type: application/json
Date: Fri, 21 Feb 2019 19:44:00 GMT
Vary: Accept-Encoding
Content-Length: 42
Connection: keep-alive
X-Powered-By: Python on a Plane 2.999
If-Modified-Since
If-None-Match
Vary

Software X NETWORK

  • Software architecture - design patterns without looking at impact on system behavior
  • Network analysis - protocols without looking at application design changes that could result in more of a performance difference than protocol efficiency enhancements alone could provide

Constraint #4: Uniform Interface

Since REST is specifically targeted at distributed information systems, it views an application as a cohesive structure of information and control alternatives through which a user can perform a desired task.

- Roy Fielding

Hypermedia as the engine of application state

Still an inexact science

  • REST doesn't dictate a particular hypermedia spec
  • Some include information on non-GET actions
  • Some don't
  • Make sure you're consistent

Representations vs. Resources

  • Resource
    • An identifier
    • Defined by URI
    • e.g. /users/5 or /events/upcoming or /talks?user_id=10
    • REST != pretty URLs
  • Representation
    • Multiple may be available for a given resource
    • Use content-type negotiation
    • e.g. a QR code vs. a text URI for 2FA
Accept
Content-Type
POST /tokens

HTTP/1.1 201 Created
Cache-Control: no-cache, no-store
Content-Type: application/hal+json
Date: Fri, 21 Feb 2019 19:52:00 GMT
Content-Length: 337
X-Powered-By: COBOL ON COGS 1.0.0RC3
POST /tokens
Accept: image/png

HTTP/1.1 201 Created
Cache-Control: no-cache, no-store
Content-Type: image/png
Date: Fri, 21 Feb 2019 19:52:25 GMT
Content-Length: 1337
X-Powered-By: Python on a Plane 2.999
POST /tokens
Accept: text/xml

HTTP/1.1 406 Not Acceptable
Cache-Control: no-cache, no-store
Date: Fri, 21 Feb 2019 19:52:45 GMT
Content-Length: 1337
X-Powered-By: NodeLiveScript 0.8

Constraint #5: Layered System

  • Abstraction && Encapsulation
  • REST is not a query language
  • GraphQL is...and GraphQL isn't REST
  • REST expects caches/proxies to exist inline
    • Browser cache
    • CDN

Constraint #6: Code on Demand

  • Optional
  • Allows for shifting processing effort to the client side
  • Downside: language-specific
  • An SPA or SDK served from the API qualifies here
  • e.g. codegen'd example via (the former) Sensio Melody

Core constraints of REST

  • Client-server
  • Stateless
  • Cacheable
  • Uniform Interface (HATEOS)
  • Layered System
  • Code On Demand (optional)

SometimeS full REST isn't the answer

Mix and match (RPC, GraphQL)

...but know why you're picking a tool for the job

'cuz Buzzword Driven Development is considered harmful

DOn't let your API happen by accident

  1. Figure out what workflows your users need to perform
  2. Build an API spec (e.g. OpenAPI), matching workflows to a series of API endpoints
  3. Build documentation/mock APIs explaining how to use the API to perform the workflows, see how users react
  4. Build only the API that you've spec'd
  5. Get feedback
  6. Update the API, keeping the API in sync
  7. Repeat

Write the docs!

  • Your spec by default isn't enough
  • Outdated docs may be worse than no docs
  • Focus on user flows first
  • Figure out how you're going to provide support

Remember the FIve Minute Rule

...and now for some API-Pinions

URL structure: Plural, not singular

  • Good: /users, /users/1
  • Bad: /user, /user/1
  • Break this rule if you're 100% sure you'll never have more than one of a resource, e.g. /me

URL structure: Nouns, not verbs

  • Good: /users/1/messages
  • Bad: /users/1/sendMessage
  • Also bad: /getUser?id=1
  • Use RPC if you must...
  • ...but try to model interactions as transferring representations of resources first (e.g. creating an event)

URL structure: Don't nest too deeply

  • Good: /poems?tag=bird,lenore&century=19&author=poe
  • Bad: /poems/19/poe/bird
  • Pattern: /:resource/:id/:relation (links to top level IDs)

HTTP Methods, REST verbs

  • GET: Find a thing (or a collection of things), idempotent
  • POST: Create a thing (or, if you must, do an RPC)
  • PATCH: Update a part of a thing
  • PUT
    • Update an entire thing (zero out non-provided fields)
    • Create a thing with a client-specified ID (return a 201!)
  • DELETE: Delete a thing (return a 204 or 205)
  • OPTIONS: Which methods are allowed for this endpoint?
  • Use HTTP 405 (Method Not Allowed) as applicable

HTTP Status Codes

  • Clients should assume x00 if they don't understand the exact code returned
  • 1xx - Provisional
    • 100 - Continue (streamed output)
    • 101 - Upgrade (e.g. to WebSockets, HTTP/2)
  • 2xx - Success
    • 200 - OK (don't put an error here)
    • 201 - Created (can include Location header)
    • 202 - Accepted (for async processing)
    • 203 - Non-authoritative information (caching)
    • 204 - No Content
    • 205 - Reset Content

HTTP Status Codes - 3xx, redirects

  • Location header (except for 304)
  • 300 - Multiple Choices
  • 301/308 - Moved Permanently
  • 302/307 - Found
  • 303 - DONTTRUSTME  See Other
  • 304 - Not Modified

HTTP Status Codes - 4xx, client errors

  • 400/422 - Your request format is bad
  • 401 - Who are you?
  • 402 - Pay me.
  • 403 - I know who you are. You aren't allowed here.
  • 404 - Nothing here.
  • 405 - Wrong Method.
  • 406 - I can't provide that content type.
  • 408 - Timeout
  • 409 - Conflict (e.g. when updating)
  • 410 - Something was here but isn't here now.
  • 418 - I'm a Teapot
  • 429 - Too Many Requests
  • 451 - Unavailable for Legal Reasons

HTTP Status Codes - 5xx, Server errors

  • 500 - Generic server error
  • 501 - Not implemented
  • 502 - The server behind this one is broken
  • 503 - Service Unavailable
  • 504 - The server behind this one timed out

Versioning

  • Clients shouldn't care about semver-minor versions; don't use
  • URL prefix based versioning to allow a safety valve for backwards incompatible resource changes
  • Content-Type based versioning (application/hal+json.v1) for backwards incompatible representation changes
  • Custom header if you want to do some mix of the above
  • Are you going to require a version in headers? If not, what do you default to?

Auth

  • HTTP Basic/API Keys
  • OAuth (we'll talk about OAuth 2)
    • Access Tokens
    • Refresh Tokens
    • Grant Types
      • Client Credentials
      • Auth Code
      • Implicit
      • Password

Thanks! Questions?

API Design Patterns for the REST of US - MergePHP May 2023

By Ian Littman

API Design Patterns for the REST of US - MergePHP May 2023

If you've spent long enough writing web applications, you've had firsthand experience with an API, whether internal or external, that leaves you scratching your head at best and banging your head against the keyboard at worst. To be fair, it takes more than exposing your application's database via a CRUD interface to get an API that's truly a joy to work with. We'll look at patterns, affecting everything from HTTP methods to versioning, from authentication to response codes, that'll make your API happily boring rather than uniquely frustrating. Including when to use REST, and when to pick a different design pattern that better suits the task at hand.

  • 346