In this article, we are going to study REST, routes, constraints, and namespaces. Before we dive into these concepts let’s take a look at how web-based services in the perspective of web users.
Let's look at an example application that uses a Web API as a service.
How does a video streaming service work?
You can create a channel say Channel A from your web application. Then you can access Channel A from your mobile app. So as you can see your Web API can provide data for all sort of different applications.
How the client & server communicate?
The communication from the client to server and back to the client of the above application can be described as follows:
Client ---> CREATE /channels video1, video2, video3 ---> Server Client <--- OK channel12 was created.------------------ Server
Client --- READ /channels/12 --------> Server Client <-- OK video1,video2,video3 --- Server
Client --- READ /channels ----------------------> Server Client <-- OK chennel10, channel11, channel12 --- Server
Client --- READ /channels/fake10 --> Server Client <-- ERROR invalid channel --- Server
Notice the pattern here: A set of commands performed on things generating responses: the very foundation of a REST API.
REST
REST stands for REpresentational State Transfer. By following a strict set of operations, REST allows us to build an infrastructure that can be supported widely with different types of applications.
Where does our API start?
A common practice for implementing our API is starting with Routes file.
config/routes.rb
resources :students
In Rails, command line let's create routes:
rake routes
This lists all the routes, URIs, Controller#actions
, and URL helpers available on our app.
Restricting Routes with options
We might not need all these routes, the resources method gives us options to restrict routes to optimize memory as well as speeds up the routing process.
config/routes.rb
resources :students, except: :destroy
Here we are excluding the destroy route above which has a DELETE method. To only create one route we can use only option.
config/routes.rb
resources :students, only: :index
This will make index as the only reachable action.
Restricting routes with multiple actions
Both :only and :except can also take an array of actions.
config/routes.rb
resources :students, only: [:index, :show]
resources :teachers, only: [:destroy, :edit, :update]
Using with_options
on routes
The with_options method is an elegant way to factor duplication out of options passed to a series of method calls.
Take a look at the routes file below:
resources :students, only: :index
resources :teachers, only: :index
resoucres :principles, only: :index
Let's use with_options so the options passed as arguments are automatically added to the resources.
Using constraints to enforce subdomain
Keeping out API under its own subdomain allows load balancing traffic at the DNS level, way faster than doing it at the application level.
config/routes.rb
resources :courses # http://school.com/courses
resources :students, constraints: { subdomain: 'api' } # http://api.school.com/students
resources :teachers, constraints: { subdomain: 'api' } # http://api.school.com/teachers
Let's use contraints to rewrite above code:
config/routes.rb
resources :courses
constraints subdomain: 'api' do
resources :students
resources :teachers
end
Network subdomain in development
Network configuration for supporting subdomains in development.
In order to develop this locally, we have done a few things in our host file. Add the following entries into your host file.
etc/hosts.rb
127.0.0.1 school-dev.com
127.0.0.1 api.school-dev.com
Now your URLs will look as follows:
http://school-dev.com:3000
http://api.school-dev.com:3000
Don't forget the port number is still required. There are other alternatives you can use:
- POW - http://pow.cx
- LVH.me - http://lvh.me
Keeping Web and API controllers organized
It's not uncommon to use the same Rails code base for both web site and Web API.
config/routes.rb
resources :pages # serves web site, not a web API
constraints subdomain: 'api' do
resources :students
end
This will render web API and the web site controllers under the same folder. It's a good practice to keep web controllers separate from API controllers. config/routes.rb
resources :pages # serves web site, not a web API
constraints subdomain: 'api' do
names :api do
resources :students
end
end
This will result in web API controllers having their own namespace.
app/controllers/api/students_controller.rb
module Api
class ZombiesController < ApplicationController
end
end
Web API controllers are part of the API module.
app/controllers/pages_controller.rb
class PagesController < ApplicationController
end
Web site controllers remain on top-level namespace
Removing duplication from the URL
Enforcing both subdomain namespace as well as API namespace, creates duplication:
config/routes.rb
constraints subdomain: 'api' do
namespace :api do
resources :students
end
end
We can eliminate the duplication by passing a path URI
config/routes.rb
constraints subdomain: 'api' do
namespace :api, path: '/' do
resources :students
end
end
We can use a short syntax by passing in the path and the constraints as options to the namespace method.
namespace :api, path: '/', constraints: { subdomain: 'api'} do
resources :students
end
API case consistency
To make api all-caps, we need to add an acronym:
config/initializers/inflections.rb
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'API'
end
Now we can write our API module in all-caps:
app/controllers/api/students_controller.rb
module API
class ZombiesController < ApplicationController
end
end