Skip to content

Instantly share code, notes, and snippets.

@rosiehoyem
Last active August 29, 2015 14:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rosiehoyem/2a38083497e2edde7dce to your computer and use it in GitHub Desktop.
Save rosiehoyem/2a38083497e2edde7dce to your computer and use it in GitHub Desktop.
API Best Practicies
#API Best Practices
##Routes
Restricting Routes
resources :zombies, only: [:index, :show]
resources :humans, except: [:destroy, :edit, :update]
##Subdomain
Keeping our API under its own subdomain allows load balancing traffic at the DNS level.
resources :zombies, constraints: { subdomain: 'api' }
resources :humans, constraints: { subdomain: 'api' }
or
constraints subdomain: 'api' do
resources :zombies
resources :humans
end
http://api.cs-zombies.com/zombies
http://api.cs-zombies.com/humans
/etc/hosts
127.0.0.1 cs-zombies-dev.com
127.0.0.1 api.cs-zombies-dev.com
makes these urls available on local machine
http://cs-zombies-dev.com :3000
http://api.cs-zombies-dev.com :3000
Prefix Verb
zombies GET
POST
new_zombie GET
###URI Pattern
/zombies(.:format)
/zombies(.:format)
/zombies/new(.:format)
Controller#Action
zombies#index {:subdomain=>"api"}
zombies#create {:subdomain=>"api"}
zombies#new {:subdomain=>"api"}
##Namespace
config/routes
namespace :api do
resources :zombies
end
app/controllers/api/zombies_controller.rb
module Api
class ZombiesController < ApplicationController
end
end
config/routes
constraints subdomain: 'api' do
namespace :api do
resources :zombies
end
end
creates:
http://api.cs-zombies.com/api/zombies
or removes the duplication in the namespace
constraints subdomain: 'api' do
namespace :api, path: '/' do
end
end
and creates:
http://api.cs-zombies.com/zombies
##GET Requests
Important characteristics:
• Safe - it should not take any action other than retrieval.
• Idempotent - sequential GET requests to the same URI should not generate side-effects.
module API
class ZombiesController < ApplicationController
def index
zombies = Zombie.all
render json: zombies
end
end
end
The to_json method serializes all properties to JSON
zombies.to_json = {"id":5,"name":"Joanna","age":null,"created_at":"2014-01-17T18:40:40.195Z","updated_at":"2014-01-17T18:40:40.195Z","weapon":"axe"}
###Curl
curl http://api.cs-zombies-dev.com:3000/zombies
Flags
-I option to only display response headers
-H option to send custom request headers
-X option specifies the method
###Media Types
Media types specify the scheme for resource representations.
class ZombiesController < ApplicationController
def index
zombies = Zombie.all
respond_to do |format|
format.json { render json: zombies, status: 200 }
format.xml { render xml: zombies, status: 200 }
end
end
end
Rails ships with 21 different media types out of the box.
Mime::SET.collect(&:to_s)
##POST Requests
A couple of things are expected from a successful POST request:
• The status code for the response should be 201 - Created.
• The response body should contain a representation of the new resource.
• The Location header should be set with the location of the new resource.
201 - Created means the request has been fulfilled and resulted in a new resource being created
def create
episode = Episode.new(episode_params)
if episode.save
render json: episode, status: 201, location: episode
end
end
204 - No Content means the server has fulfilled the request but does not need to return an entity-body
422 - Unprocessable Entity means the client submitted request was well-formed but semantically invalid.
Rails checks for an authenticity token on POST, PUT/PATCH and DELETE.
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with:
end
config/environments/test.rb
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
Some successful responses might not need to include a response body. Ajax responses can be made a lot faster with no response body.
def create
episode = Episode.new(episode_params)
if episode.save
render nothing: true, status: 204, location: episode
end
end
The head method creates a response consisting solely of HTTP headers.
def create
episode = Episode.new(episode_params)
if episode.save
head 204, location: episode
end
end
(or 'head :no_content')
Unsuccessful Requests
def create
episode = Episode.new(episode_params)
if episode.save
render json: episode, status: :created, location: episode
else
render json: episode.errors, status: 422
end
end
500 - Internal Server Error means the server encountered an unexpected condition which prevented it from fulfilling the request
##Versioning
###Versioning Using the URI
config/routes.rb
namespace :v1 do
resources :zombies
end
namespace :v2 do
resources :zombies
end
app/controllers/v1/zombies_controller.rb
module V1
class ZombiesController < ApplicationController
before_action ->{ @remote_ip = request.headers['REMOTE_ADDR'] }
def index
render json: "#{@remote_ip} Version One!", status: 200
end
end
end
If an app strictly serves a web API, it’s ok to use ApplicationController as the base class.
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action ->{ @remote_ip = request.headers['REMOTE_ADDR'] }
end
Error Handling
Development Tools
Postman
[Understanding REST Headers and Parameters](http://www.soapui.org/Best-Practices/understanding-rest-headers-and-parameters.html)
###Documentation
###Testing
Tools: Use Rspec Requests w/ Webmock
Rails API Integration Testing
Rails API Testing Best Practices
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment