Skip to content

Instantly share code, notes, and snippets.

@philsturgeon
Last active May 23, 2022 12:29
Show Gist options
  • Star 68 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save philsturgeon/5465246 to your computer and use it in GitHub Desktop.
Save philsturgeon/5465246 to your computer and use it in GitHub Desktop.
API Golden Rules

Never Expose DB Results Directly

  1. If you rename a field, then your users are fucked. Convert with a hardcoded array structure.
  2. Most DB drivers [for PHP] will show integers as numeric strings and false as "0", so you want to typecast them.
  3. Unless you're using an ORM with "hidden" functionality, people will see passwords, salts and all sorts of fancy codes. If you add one and forget to put it in your $hidden array then OOPS!

Use the URI sparingly, and correctly

  1. Use the query string for paired params instead of /users/id/5/active/true. Your API does not need to be SEO optimised.
  2. ?format=xml is stupid, use an Accept: application/xml header. I added this to the CodeIgniter Rest Server once for lazy people, and now people think it's a thing. It's not.

Resources are EVERYTHING

  1. You're always either asking for one resource, or multiple. If it's one, just return that data as an array. If you've asked for multiple, then return them in a plural parent element (such as "users").
  2. Two resources in different locations should look identical (your iPhone dev will love you for this). This could be /me/friends and /users/5/friends, or embeded data.
  3. If I want multiple resources in one call, then give them to me. /users/X,Y,Z in a "users" array works nicely.
  4. If you ask for multiple and some results exist, shove that in a "users" array.
  5. If you ask for multiple but find none, then a 404 makes sense.

JSON, XML or shut up

  1. Don't spend forever trying to make your system output everything under the sun. Sure you can output lolcat, but you don't need to.
  2. Tell your users to send you JSON or XML in the body. Messing around with application/x-www-form-urlencoded just for $_POST['foo'] is no good, especially when any decent framework (like Laravel 4) will allow Input::json('foo') anyway.
  3. No payload parameters. I've seen APIs accept application/x-www-form-urlencoded with a json={} parameter. If you think that is a good idea it's time you re-train as a yoga teacher or something, the stress is effecting your judgement.

Authentication

  1. OAuth 2 is the shit. A few people wrote ranty arguments about how it's insecure, because they weren't using SSL.
  2. Use SSL.
  3. Make sure your OAuth 2 implementation is spec compliant, or you're going to have a bad time. This one is.

Caching

  1. Your API needs a shorter memory than my favorite fruit is watermelon. No state, no sessions, no IP recognition. Don't guess, let them tell you who they are with their access token.
  2. Caching should only happen on popular endpoints where there is no user context, and you know it's not going to change in the timeframe.
  3. The Cache-Control header let's people know if they can (or should) cache stuff. If other devs ignore those headers then it's their problem.

Background all the things

  1. "When the user sends us an image, resize it, upload it to S3, send an email then show a confirmation". Nope. That should be at least one background job, preferably 3, with an IMMEDIATE response. Ditch off to one "image_processing" job to get things going.
  2. Create an "email" job, a "sms" job and a "APN" job for example, so you can generically send out all sorts of contact stuff from your API, your other jobs, etc without infecting your API with all that stuff.
  3. Put your workers on a different server. Your API server is busy enough handling HTTP requests in responses, don't make it think about anything else.

Pagination

  1. Do this. So many people just dump back an "get all this data" response and forget that "N" gets pretty big over time.
  2. Add a "paging" element, which has a "next" or "previous" URL if there are more on either side.
  3. Don't make the client do math.
  4. OUTPUT TOTALS. I'm looking at you Facebook. Why do I have to poll "next" 20 times to manually count() how many friends a specific user has when you obviously know the answer already? GAH!

Response Codes

  1. Give me an actual error message, not just a code. "Oh yay, a E40255 just happened" --Nobody.
  2. Use 410 instead of 404 if it's been deleted, or blocked, or rejected. "This content has been removed." > "Um, no clue bro."

Documentation

  1. If you have well written documentation, you get to say RTFM a lot.
  2. Versioned API's are easiest to keep up to date, because they don't change (unlike the Facebook API, which might as well be nightly).
  3. Use a tool. Swagger-PHP + Swagger-UI does the trick.

Testing

  1. Write tests, and get Jenkins to automate them.
  2. If tests aren't automated, they might as well not exist.
  3. Unit-test your components and models and whatnot.
  4. No need to mock the DB, make a "testing" server and beat it hard, just mock FB/Twitter/"Foo-External API" calls.
  5. Use something like Behat to send example JSON bodies and check what sort of responses you get.
  6. TEST FOR ERRORS, not just success. Try and trigger every guard statement.
  7. If tests aren't automated, they might as well not exist. Nope, not a copy/paste error.

Version your API like an adult

  1. Sure throwing a v1/ subfolder into your app/controllers or whatever might seem like a real clever way of doing things, but how are you gonna merge v1.0 tweaks with v1.1 and v2.0?
  2. Nginx comes with a handy Map module, map custom Accept headers to a variable.
  3. Those headers can look like this: application/vnd.com.example-v1.0+json. Gross? Whatever.
  4. Use this variable to map to a different directory in your virtual host config, and these each have their own Git branch. Something like: "set $api_path api-v$api_version;" followed by a "root /var/www/$api_path/public;"
  5. Merge changes upwards to as many codebases that share a common history, don't try and copy and paste changes like a dummy.
  6. All rules in this section mean 1.0 could be PHP, 2.0 could be Node (you hipster you) and 3.0 could be Scala (I dont even...) and only your minor versions need to worry about merging changes upwards.
@mattattui
Copy link

Regarding your documentation suggestions, we use Guzzle for our API clients, same way the AWS SDK 2 does. Turns out Guzzle's JSON service description files are very, very close to the input that SwaggerUI displays. So with just a little fiddling, SwaggerUI can browse the service description file that Guzzle consumes (or I suppose conversely, one could generate a Guzzle service description file from the compiled Swagger UI input file). Kinda neat having the documentation track the API client (neater still to have the client & documentation generated from the server code, if anyone wants a project!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment