Stuff I've Been Part Of

  • The League of Extraordinary Packages
  • PHP The Right Way
  • CodeIgniter
  • PyroCMS
  • FuelPHP
  • PHP-FIG
  • PancakeApp
  • Build APIs You Won't Hate

From Bristol 🇬🇧

HORROR STORIES!

Don't Do Slow Stuff

  • Tell clients where to get updates
  • Sockets, web hooks, or even long-polling!

Is Your Async Code Actually Quicker?

  • Maybe put that image straight on S3
  • Redis maybe doesn't need 1 megabyte files going into the queue
  • Definitely don't base64 encode them!

The most daft performance issue

Restrict pagination limit size!


if ($limit < 1 || $limit > 100) {

  $limit = 100;

}

Don't let an unpaginated endpoint be a DDoS vector.

Includes are cool

But, restrict what people can include.

?include=literally,everything,in,
the,goddam,database,what,is,
happening,so,slow,help,me,database,
server,is,on,fire,agggghhhhhh

Use SSL

Come on folks, it's free.

Go to letsencrypt.org

Instagram Didn't

Mazin Ahmed

Runscope helps you catch liars

Server Errors on 200

  • Nothing to do with REST
  • RPC should use status codes properly too!
  • Runscope, New Relic etc. will not report
🤖 Blorp, I'm a robot, and I have no idea what this means!

{
  "error": {
    "errors": [
      {
        "domain": "youtube.parameter",
        "reason": "missingRequiredParameter",
        "message": "No filter selected.",
        "locationType": "parameter",
        "location": ""
      }
    ],
    "code": 400,
    "message": "No filter selected."
  }
}
					
🙋 Hi, I'm a human and I have no idea what this means!

{
  "error": {
    "errors": [
      {
        "domain": "youtube.parameter",
        "reason": "missingRequiredParameter",
        "message": "No filter selected.",
        "locationType": "parameter",
        "location": ""
      }
    ],
    "code": 400,
    "message": "No filter selected."
  }
}
					
Oh I'm missing the 'filter' parameter?
WRONG!

Needed to add &mine=true to URL...

🤔 😫

Talking over HTTP

  • Expect the worst from everyone
  • The response might not contain JSON
  • The response might be empty
  • The response might never come!

def get_something(id)
response = JSON.parse(client.get('/api/something/'+id))

if response['RESPONSE']['CODE'] == "SUCCESS"
  return response
elsif response['RESPONSE']['CODE'] == 'NOT FOUND'
  return response
else
  message = response['RESPONSE']['APIERROR']
  raise SomeError, "Error from third-party API: #{message}."
end
end
					
  • This will explode in so many ways

Talking over HTTP

  • If it's not JSON, catch that exception

def get_something(id)
response = JSON.parse(client.get('/api/something/'+id))

if response['RESPONSE']['CODE'] == "SUCCESS"
  return response
elsif response['RESPONSE']['CODE'] == 'NOT FOUND'
  return response
else
  message = response['RESPONSE']['APIERROR']
  raise SomeError, "Error from third-party API: #{message}."
end
rescue JSON::ParserError => e
raise HttpServerError, "Service returned invalid JSON data"
end
					

Talking over HTTP

  • The response might be empty if its a timeout

def get_something(id)
response = JSON.parse(client.get('/api/something/'+id))

if response.nil?
  raise HttpServerError, "Service returned an empty response"
end

# ... success or fail

rescue JSON::ParserError => e
raise HttpServerError, "Service returned invalid JSON data"
end
					

Talking over HTTP

  • The response might be a weird shape

def get_something(id)
response = JSON.parse(client.get('/api/something/'+id))

if response.nil?
  raise HttpServerError, "Service returned an empty response"
end

unless response['RESPONSE'] && response['RESPONSE']['CODE']
  raise HttpServerError, "Service returned an unexpected response: #{response}"
end

# ... success or fail

rescue JSON::ParserError => e
raise HttpServerError, "Service returned invalid JSON data"
end
					

Documentation is not optional

Learn how to build docs: bit.ly/api-doc-video