As we at Monterail are no longer just a Rails shop, our projects have grown in size presenting us with an array of new challenges. All the Single Page Applications we code need reliable APIs versioning to work properly. Some of the bigger apps that we manage, built with Service Oriented Architecture pattern, demand a scalable way for our software to communicate between various components.
New challenges demand new solutions — we’d like to share one with you that has worked well for us.
You might think that it's difficult to code APIs that are simple to maintain and honestly, we thought so too. However, as some of us have recently discovered, an excellent tool already exists to solve this problem.
It's called Grape.
If you haven't heard of it, Grape is a REST-based framework for building APIs in Ruby that was designed to run on Rack as well as to fit into existing Rails or Sinatra applications. The article below, which is the first of four that we're planning, is a summary of our experiences using Grape for our projects. When we started using Grape there were no clear guidelines for how to get everything up and running, so we had to simplify the process and solve the problem on our own. After getting our hands a little dirty, we came up with the instructions laid out below that show how we think Grape is best approached.
To begin, install Grape as a gem in your application. As you probably suspect, to do so you'll simply need to put this code in your Gemfile:
# Gemfile
gem "grape"
Run bundle install
and remember to restart rails server.
Structuring code for API versioning
The goal is simple — we want to have the API version that we need in the path:
/api/v1/hussars.json - API version 1
/api/v2/hussars.json - API version 2
This is a pretty standard and failsafe way of API versioning and while it is possible to do this with Grape, the solution is not as straightforward as you may think.
We also want to have all API-related codebase under the API
module, as well as different versions in their own submodules API::V1
and API::V2
.
Let's start at the deepest level of the code: the actual controller
:
# app/controllers/api/v1/hussars.rb
module API
module V1
class Hussars < Grape::API
version 'v1' # path-based versioning by default
format :json # We don't like xml anymore
resource :hussars do
desc "Return list of hussars"
get do
Hussar.all # obviously you never want to call #all here
end
end
end
end
end
The code above shows a very simple Grape resource whose path ends with /v1/hussars.json
.
Let's assume we have some other resources like these:
/v1/wings.json
asAPI::V1::Wings
inapp/controllers/api/v1/wings.rb
/v2/hussars.json
asAPI::V2::Hussars
inapp/controllers/api/v2/hussars.rb
Now, for each API version we need an aggregate class that mounts all of its resources:
# app/controllers/api/v1/base.rb
module API
module V1
class Base < Grape::API
mount API::V1::Hussars
mount API::V1::Wings
end
end
end
The same goes for app/controllers/api/v2/base.rb
.
We also need one more class that will aggregate all API versions:
# app/controllers/api/base.rb
module API
class Base < Grape::API
mount API::V1::Base
mount API::V2::Base
end
end
Finally, we can mount API::Base
in routes.rb
.
# config/routes.rb
Monterail::Application.routes.draw do
# ...
mount API::Base => '/api'
# ...
end
This way we get routing like this:
/api/v1/hussars.json -> API::V1::Hussars
/api/v1/wings.json -> API::V1::Wings
/api/v2/hussars.json -> API::V2::Hussars
To sum up this section, the file structure should look as follows:
app/
controllers/
api/
v1/
hussars.rb - API::V1::Hussars
wings.rb - API::V1::Wings
base.rb - API::V1::Base
v2/
hussars.rb - API::V2::Hussars
base.rb - API::V2::Base
base.rb - API::Base
This may seem like a few too many classes for such a simple case, but as you'll see in the next section, taking this extra step will pay off pretty quickly.
Reusable components
If you take a closer look at the API::V1::Hussars
class, you will notice this:
version 'v1'
format :json
Due to the way Grape works, a variant of this definition needs to be in every class that inherits and defines any resources from Grape::API
. Having multiple classes in your code, however, can result in code duplication and the possibility of mismatching resources.
The clearest solution is to use a shared module that may look like this:
# app/controllers/api/v1/defaults.rb
module API
module V1
module Defaults
# if you're using Grape outside of Rails, you'll have to use Module#included hook
extend ActiveSupport::Concern
included do
# common Grape settings
version 'v1'
format :json
# global handler for simple not found case
rescue_from ActiveRecord::RecordNotFound do |e|
error_response(message: e.message, status: 404)
end
# global exception handler, used for error notifications
rescue_from :all do |e|
if Rails.env.development?
raise e
else
Raven.capture_exception(e)
error_response(message: "Internal server error", status: 500)
end
end
# HTTP header based authentication
before do
error!('Unauthorized', 401) unless headers['Authorization'] == "some token"
end
end
end
end
end
Then simply include the shared module in the resource module:
# app/controllers/api/v1/hussars.rb
module API
module V1
class Hussars < Grape::API
include API::V1::Defaults
# ...
end
end
end
Think of it as your APIs application_controller.rb
; all the reusable components should go inside this shared module. At Monterail, we also use this pattern to support different authorization methods for different scopes in the application, a common example being when we deal with admins and casual users.
Swagger integration
Swagger is a great way of documenting an API that allows you to produce, visualize, and consume your very own RESTful services. Surprisingly, implementing Swagger only requires some HTML5 and a lot of JavaScript, so you can run it virtually anywhere. Integrating Swagger with Grape will make your workflow easier because your API will be documented completely, right out of the box! How cool is that?
To use Swagger in your Rails application, add grape-swagger
gem to Gemfile
:
# Gemfile
gem 'grape-swagger'
Then add one line to the API::V1::Base
module:
# app/controllers/api/v1/base.rb
module API
module V1
class Base < Grape::API
# ...
add_swagger_documentation base_path: "/api",
api_version: 'v1',
hide_documentation_path: true
end
end
end
Finally, you’ll need to download and configure Swagger UI. To do this, copy dist
directory to public/api/docs
.
To make Swagger UI work with grape-swagger gem we need to put /api/swagger_doc.json
as url
in public/api/docs/index.html
file at line 22:
// public/api/docs/index.html
21. window.swaggerUi = new SwaggerUi({
22. url: "/api/swagger_doc.json", // <---- HERE
23. dom_id: "swagger-ui-container",
After you do this you'll be able to visit http://localhost:3000/api/docs
and test out your API directly from the browser. Feel free to try it anytime; it's a really great way to debug your application.
Summary
That's it for now, folks.
As you can see, Grape is a tremendously powerful tool that's easy to learn and implement into your workflow, especially when it’s combined with Swagger, ROAR or Her. We want to save some stuff for you to read in future posts though, so we'll hold off on discussing those topics until later. As we mentioned in the introduction, we're going to publish a handful of posts detailing everything that we've learned about Grape. For now, we can tell you that the next post in this series will focus on how you can enhance your API. We’ll follow up that article by taking you on an insightful journey into the world of API caching. So stayed tuned — it’ll be quite a trip!
We're always keen to know what you think about the solutions that we provide here on Codetunes. Did you find this introduction to Grape useful? Has it changed the way that you think about APIs in Rails applications? (If so, we're extremely happy, because that’s why we’re publishing this series.) Or maybe you're convinced that you can figure out a better solution than ours? If so, prove it!
We'd love to hear your feedback.
Meet Ruby on Rails experts
We’ve been doing Ruby on Rails since 2010 and our services quality and technical expertise are confirmed by actual clients. Work with experts recognized as #1 Ruby on Rails company in the World in 2017 by Clutch.