What "They" Should Have Told You About API Development
Phil Sturgeon
@philsturgeon
1. Documentation
2. REST or RPC
3. arbitrary advice
Easier than you think
- DON'T DO IT BY HAND
- Version control with code
- API Blueprint 👍🏼
- RAML / Swagger 🙌🏼
- Just write YAML or MD
- Attributes / Data Structures
- JSON Schema
![](https://s3.amazonaws.com/media-p.slid.es/uploads/588583/images/3087391/aglio-place-list.png)
Iterate, improve, and agree up on your contracts before you write any code!
Document First
Documentation without tests is like code without tests; You're just hoping it works well enough that you don't f**k everything up.
Testing
Broken documentation should fail the build.
Testing
Testing
$ npm install -g dredd
$ dredd init
$ dredd
info: Beginning Dredd testing...
fail: GET /products/1 duration: 17ms
fail: body: At '/data/attributes/volume' Missing required property: volume
complete: 4 passing, 1 failing, 0 errors, 10 skipped, 15 total
complete: Tests took 274ms
- Mocks are a fake API, generated from your documentation
- Share mocks to get integration feedback early
- Automatically generated by Apiary
- Free alternative for API Blueprint: drakov
Mocking
Mocking
$ npm install -g drakov
$ drakov -f apiary.apib
$ ngrok http 3000
$ http GET http://xxxxxx.ngrok.io/products --json
1. Documentation
2. REST or RPC
3. arbitrary advice
Don't confuse REST for a metric of quality
Making an API 100% RESTful is hard
Do you actually need REST?
Maybe RPC would be more appropriate?
RPC = Commands
REST = Resources
Using commands to interact with resources is awful
And vice versa
RPC: GET /listCheeseburgers
REST: GET /cheeseburgers
RPC: POST /createCheeseburger
REST: POST /cheeseburgers
RPC: POST /updateCheeseburger
REST: PATCH /cheeseburgers/1
RPC: POST /deleteCheeseburger
REST: DELETE /cheeseburgers/1
RPC: POST /consumeCheeseburger
REST: urm...?
Actions that trigger state changes could be RPC...
... but maybe you could PATCH on fields with a state machine?
module Api
module States
class Trip
include Statesman::Machine
state :locating, initial: true
state :in_progress
state :complete
state :canceled
transition from: :locating, to: [:in_progress, :canceled]
transition from: :in_progress, to: [:complete, :canceled]
after_transition(from: :locating, to: :in_progress) do |trip|
trigger_some_action(trip)
end
# ...
end
end
end
There is a reason Slack use RPC...
So probably RPC For commands then?
RPC: Remote Procedure call
- Do this random thing
- Here's some stuff to help you do that thing
- Poking a black box
- Great for events/commands/etc
- Kinda like a stored procedure
REST: representational state transfer
- Hard to be entirely RESTful
- Some folks try too hard
- Can also do actions, but as afterthoughts
- If RPC is like a stored procedure, REST is like an ORM
- Without HATEOAS you just have a very pretty RPC API
![](https://s3.amazonaws.com/media-p.slid.es/uploads/588583/images/3093291/have_both.png)
1. Documentation
2. REST or RPC
3. arbitrary advice
Maybe you don't need HATEOAS
Hypermedia helps your API outlive your startup
TDD is the easiest way to build a complex HTTP API
Describe-it syntax helps you write complex tests easily
RSpec.describe 'Avatars' do
let(:user) { create(:user) }
describe 'POST /avatars' do
let(:png_path) { 'spec/fixtures/files/avatar.png' }
let(:png_file) { File.read(Rails.root.join(png_path)) }
context 'when the user already had an avatar' do
let!(:old_avatar) { create(:avatar, user: user ) }
it 'deletes the old avatar for the user. No soft delete.' do
payload = {
avatars: {
image_url: 'http://fake-ride/example.png'
}
}
post '/avatars', payload
expect(user.reload.avatar).not_to eql(old_avatar)
end
end
end
end
RSpec.describe 'Avatars' do
describe 'POST /avatars' do
context 'direct image uploads' do
it 'will fail if content-type is invalid' do
headers = oauth_headers(user_token).merge({
'CONTENT_TYPE' => 'some/crap'
})
post '/avatars', nil, headers
expected_response = {
'errors' => [{
'code' => 11_506,
'title' => "The root key is missing from the payload.",
'status' => 400,
'details' => 'avatars'
}]
}
expect(parsed_response).to eq(expected_response)
expect(response).to have_http_status(:bad_request)
end
end
end
end
![](https://s3.amazonaws.com/media-p.slid.es/uploads/588583/images/3093271/verbose_reporter.png)
Don't let clients hit staging for dev
?include=literally,eve
rything,in,the,goddam,database,what,is,happening,so,slow,help,me,database,server,is,on,fire
![](https://s3.amazonaws.com/media-p.slid.es/uploads/588583/images/3093186/ebook.jpg)
apisyouwonthate.com
What They Should Have Told You About API Development
By Phil Sturgeon
What They Should Have Told You About API Development
- 2,336