Building APIs You Won't Hate (Phil Sturgeon)

Created: 2023-07-27T02:05:05-05:00

Return to the Index

Use fake data to avoid embarassments.

Create your database and synthesize test data before writing the API.

Enumerate all you need the API to do.

Gather the lsit of needs and simplify to a list of actions.

"List" endpoints to give paginated listings.

Not every action needs a new verb.

Resources: GET /user/33

Sub-resources: GET /user/33/post/5

Avoid auto-increment; prefer UUID (or similar) identifiers when they must face a user. Auto-increments invite user scraping and leaking information about API usage.

POST vs PUT: mostly the same thing, but PUT is idempotent.

Prefer POST for one-off requests and PUT when you are uploading an entire record.

Prefer plural in all cases. Avoid the temptation to combine singular and plurals. Ex, it is always "/users" and not ("/users" and "/user.")

Allow endpoints to retrieve multiple records. For example GET /users/1,2.

Have endpoints to POST a batch of outbound messages all at once. Ex. POST /messages should allow uploading multiple outbound messages that have queued up.

Avoid using clever routing conventions; just list the routing table out manually, in a single file somewhere.

PHP users love using POST and the formdata content type. This is because PHP will parse that and put fields in $_POST for them. But you should avoid this whole shindig.

Most modern systems speak JSON.

Avoid XML unless you know you will make good use of it.

Return a single JSON object for single values, but arrays of objects if there are multiple values.

For authentication: remember Bearer tokens, OAuth2, etc.

Make use of HTTP response codes if your API is using HTTP.

Error codes are for machines, messages are for humans. Make sure to include both. Don't make clients parse the error string to figure out what is going on.

Book author likes to do behavior testing with cucumber files and the "behat" PHP package.

Controllers should not be combined with ORMs. Keep your code separated and clean.

Stability: when you rename something and old clients don't work anymore.

Presentation objects: remember Presentation-Abstraction-Control. Create presentation objects that convert your data to user-facing formats.

Consider only supplying data fields the client has asked for.

Foreign keys: when you answer a query with IDs that have to be found elsewhere. For example when asking for a user's posts, getting a list of post ID's.

Document your API and include example code.

Versioning: encode the version in the URL somewhere, such as a /v1/ path fragment. Or put the version in a request header so clients can identify what version they are speaking.

Facebook migrations: using a collection of feature flags to enable or disable behaviors. Any breaking change requires a feature flag which then gates responses. Clients use tokens to identify themselves. The token identifies what feature flags are present for the client. When a client is prepared to update it uses a new token with different flags, or reassigns the flags on its tokens, and so participates in the new version. Eventually the flags are deprecated and removed and old tokens are either blocked or are fed new data they no longer expect.

Namespaces

Wrap results in namespaces to account for meta-data.

{
"data": {
"fnord": "fnord"
},
"hello i am metadata": "hello!"
}

Also use namespaces within subobjects in case those have meta-data also.

{
"data": {
"fnord": "fnord",
"children": {
"data": {
"henlo i am cat": "bolb!"
}
}
},
"hello i am metadata": "hello!"
}