API Design patterns
for the REST of US
php[tek] 2023
Ian Littman / @ian@phpc.social / @iansltx
follow along at https://ian.im/apitek23
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
- Status Codes
- Versioning
- Authentication + Security
So...what's this REST thing?
- Codified by Roy Fielding in his doctoral dissertation
- An architectural style
- A set of constraints that make interacting with a web-based system more predictable
- Describes information structure on the Web, not just APIs
- REST does not dictate
- Component implementation
- Protocol syntax
Constraint #1: Client-Server
Interfaces, not implementations
GET /gyros Accept: video/mp4 {other headers here} HTTP/1.1 200 OK Cache-Control: public, max-age: 3600 Content-Type: video/mp4 Date: Thu, 18 May 2023 16:05:00 GMT Vary: Accept-Encoding Content-Length: 20230518 Connection: keep-alive X-Powered-By: Battle Snake
Client-server interactions can/should be streamed
REST Connectors
- Client (Chrome, curl)
- Server (Apache, nginx, node)
- Cache (browser cache, Cloudflare, Varnish)
- Resolver (DNS)
- Tunnel (SOCKS, TLS via HTTP CONNECT)
REST Components
- Origin Server (Apache, nginx + FPM, node)
- Gateway (network-imposed; Squid, nginx)
- Proxy (client-selected; SSH tunnel or VPN)
- User Agent (Chrome, curl)
Constraint #2: Stateless
- No concept of a "session" (with preferences etc. baked in)
- Current user ID shouldn't be in URL (impacts caching!)
- Stateless requests are easier to reason about for cacheability
- Cookies are an antipattern*
- JWTs are less of an antipattern
- WebSockets aren't REST
* They may be the best way to pass auth back and forth
Con$traint #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, 18 May 2023 16:13:37 GMT Vary: Accept-Encoding Content-Length: 42 Connection: keep-alive X-Powered-By: An Elephpant
If-Modified-Since If-None-Match Vary
REST expects caches/proxies inline
- Browser cache
- CDN
Cache constraints
- Only for retrieval
- Only for the same representation (Vary header)
- Only unauthenticated (for shared caches)
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
Uniform Interface Constraints
- Identification of resources
- Manipulation of resources through representations
- Self-descriptive messages
- Hypermedia as the engine of application state
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
REST Data Elements
- Resource
- Resource identifier (URI/URN)
- Representation
- HTML document
- JPEG image
- MKV video
- Representation metadata
-
Content-Type
-
Last-Modified
-
-
Resource Metadata
-
Source link
-
Alternates
-
Vary
-
-
Control Data
-
If-Modified-Since
-
Cache-Control
-
A resource is a reference
Not An entity itself
Representations vs. Resources
- Resource
- An identifier
- Defined by URI
- e.g. /users/5 or /events/upcoming or /talks?user_id=10
- REST != pretty URLs
- URIs should change as little as possible
- 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: Thu, 18 May 2023 16:20:24 GMT Content-Length: 337 X-Powered-By: COBOL ON COGS 10.11.0
POST /tokens Accept: image/png HTTP/1.1 201 Created Cache-Control: no-cache, no-store Content-Type: image/png Date: Thu, 18 May 2023 16:21:26 GMT Content-Length: 1337 X-Powered-By: Bacon
POST /tokens Accept: text/xml HTTP/1.1 406 Not Acceptable Cache-Control: no-cache, no-store Date: Thu, 18 May 2023 16:22:28 GMT Content-Length: 123 X-Powered-By: Fresh Outta Angle Brackets
Hypermedia as the engine of application state
- Inline discoverability
- Choose your own adventure!
Still an inexact science
Constraint #5: Layered System
- Abstraction && Encapsulation (Middleware!)
- Examples
- Load balancing
- Authentication
- Caching
- Content Type Transformation
- Layers should be deployable independently
Constraint #6: Code on Demand
- Optional
- Allows for shifting to the client side
- Processing effort
- Less-common interaction patterns
- Examples
- An SPA or SDK served from the API
- Codegen'd example via (the former) Symfony Melody
- Downside: language-specific
Core constraints of REST
- Client-server
- Stateless
- Cacheable
- Uniform Interface
- 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
DOn't let your API happen by accident
- Figure out what workflows your users need to perform
- Build an API spec (e.g. OpenAPI), matching workflows to a series of API endpoints
- Build docs/mock APIs explaining how to use the API to perform the workflows, see how users react
- Build only the API that you've spec'd
- Get feedback
- Update the API, keeping the API docs/spec in sync
- 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
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¢ury=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 -
DONTTRUSTMESee 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
- For BC breaks
- Path: URL prefixes (/v2)
- Representation: Content-Type (application/hal+json.v1)
- Mix of both: custom header (API-Version)
- Will you require users to specify a version (for header-based)
- If not, what do you default to?
- Latest
- Earliest
- Account-scoped API version (a la Stripe)
Auth
- HTTP Basic/API Keys
- OAuth (we'll talk about OAuth 2)
- Access Tokens
- Refresh Tokens
- Grant Types
- Client Credentials
- Auth Code
- Implicit
- Device Authorization
- Password (deprecated in 2.1)
Thanks! Questions?
ian.im/apitek23 - these slides
phpc.social/@ian - me!
twitter.com/iansltx - also me
github.com/iansltx - my code!
<- rate this talk
API Design Patterns for the REST of US - php[tek] 2023
By Ian Littman
API Design Patterns for the REST of US - php[tek] 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.
- 593