APIs are needed for various reasons, such as by mobile apps to interact with backend CRUD operations. Creating maintainable APIs is not a simple task. As the application grows, so does the need to version and maintain the API.
In this tutorial, we’ll have a look at Grape, a micro-framework for creating maintainable REST-like APIs in Ruby.
From grape’s Github page:
Grape is a REST-like API micro-framework for Ruby. It’s designed to run on Rack or complement existing web application frameworks such as Rails and Sinatra by providing a simple DSL to easily develop RESTful APIs. It has built-in support for common conventions, including multiple formats, subdomain/prefix restriction, content negotiation, versioning and much more.
Our Project
We’ll be using Rails and grape together to create the API. It will expose the Employee model to other applications, consuming the back-end CRUD operations for that model.
The API can be used by rich web applications, like the ones created using AngularJS, Backbone.js, etc. or native mobile applications to create, edit, update, and delete employees.
Here are the API endpoints we’ll be creating:
//Get all employee details
GET /emp_api
//Add an employee
POST /emp_api/ {name:"Jay", address:"Kerala, India", age:"34"}
//Delete an employee
DELETE /emp_api/1
//Update an employee
PUT /emp_api/1
Getting Started
Let’s start by creating a Rails application and installing the Grape gem. I’m using Rails 4.1.6.
Create a new Rails project, skipping the bundle install
.
rails new emp_api --skip-bundle
Once the project files are all created, navigate to the project directory (emp_api
). Open the Gemfile and include the grape gem at the end of the file.
gem 'grape'
Next, install all the required gems using Bundler:
bundle install
Generate an Employee model for the basic CRUD operations to be exposed by the API:
rails g model EmpData name:string address:string age:integer
rake db:migrate
All the API files will live inside the app folder. Create a new folder called api inside emp_api/app/.
Inside emp_api/app/api create another folder called employee, and a file in that directory called data.rb. This file holds the class to access the Employee model.
Inside emp_api/app/api/ create another file called api.rb where we’ll mount the class defined in emp_api/app/api/employee/data.rb.
As per the instructions on the Grape GitHub pages, since we have placed our API code in app/app, that directory needs to be added to the load/autoload paths. Set it up in config/application.rb as shown below:
require File.expand_path('../boot', __FILE__)
require 'rails/all'
Bundler.require(*Rails.groups)
module EmpApi
class Application < Rails::Application
## Newly Added code to set up the api code
config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
end
end
Creating the API
Grape APIs are Rack applications that are created by subclassing Grape::API
. Let’s get started by creating the first API endpoint to retrieve all employee details. Open up app/api/employee/data.rb and create the module class as shown:
module Employee
class Data < Grape::API
resource :employee_data do
desc "List all Employee"
get do
EmpData.all
end
end
end
end
We need to access the Employee::Data
class functionality inside the API class. So, we’ll use mount
to make Employee::Data
class accessible inside the API
class.
So, open up app/api/api.rb and mount Employee::Data
class API < Grape::API
prefix 'api'
version 'v1', using: :path
mount Employee::Data
end
Open up app/config/routes.rb and include the following code to route the API calls to our root address:
Rails.application.routes.draw do
mount API => '/'
end
Fire up the rails server:
rails server
Open a new terminal and try :
curl http://localhost:3000/api/v1/employee_data.json
You’ll see []
as response, since we don’t have any data yet. We don’t have any way to add employees, so let’s fix that.
Add another API endpoint to create a new employee. We’ll need certain values to create an employee, such as name, address, and age. Grape allows us to define whether these parameters are required or optional in the API itself.
Open app/api/employee/data.rb and add the following code to create the API endpoint for employee creation.
desc "create a new employee"
## This takes care of parameter validation
params do
requires :name, type: String
requires :address, type:String
requires :age, type:Integer
end
## This takes care of creating employee
post do
EmpData.create!({
name:params[:name],
address:params[:address],
age:params[:age]
})
end
Restart the server and use curl
to post a request to the endpoint:
curl http://localhost:3000/api/v1/employee_data.json -d "name=jay;address=delhi;age=25"
Now, we should have an employee:
curl http://localhost:3000/api/v1/employee_data.json
[{"id":1,"name":"jay","address":"delhi","age":25,"created_at":"2014-10-27T16:43:49.303Z","updated_at":"2014-10-27T16:43:49.303Z"}]
Bingo.
The delete and update API for the employee model follow the similar pattern to our read and create endpoints. The delete endpoint requires the employee ID:
# app/api/employee/data.rb
desc "delete an employee"
params do
requires :id, type: String
end
delete ':id' do
EmpData.find(params[:id]).destroy!
end
Restart the Rails server and test the API by deleting the employee we created earlier.
curl -X DELETE http://localhost:3000/api/v1/employee_data/1.json
{"id":1,"name":"jay","address":"delhi","age":25,"created_at":"2014-10-27T16:43:49.303Z","updated_at":"2014-10-27T16:43:49.303Z"}
Now, if you check the employee listing api, you should get a empty response.
Next, in order to update an employee details, we’ll need to pass the employee ID and the values to be updated. Add the following code to create an API endpoint to update the employee address:
# app/api/employee/data.rb
desc "update an employee address"
params do
requires :id, type: String
requires :address, type:String
end
put ':id' do
EmpData.find(params[:id]).update({
address:params[:address]
})
end
But we don’t have an employee to update. Remember, we deleted the last employee, so we’ll need to add one back in order to update it.
Restart the server and add a new employee first,
curl http://localhost:3000/api/v1/employee_data.json -d "name=roy;address=kerala;age=25"
{"id":2,"name":"roy","address":"kerala","age":25,"created_at":"2014-10-27T16:59:54.090Z","updated_at":"2014-10-27T16:59:54.090Z"}
Now, use the following command to update the address of the new employee:
curl -X PUT http://localhost:3000/api/v1/employee_data/2.json -d "address=mumbai"
true
Wrapping It Up
In this tutorial, we got started creating a simple CRUD API using the Grape framework. Detailed documentation regarding Grape is available on their GitHub pages.
Code from this tutorial is available on GitHub.
Frequently Asked Questions (FAQs) about Building APIs with Grape
How do I install Grape in my Ruby application?
To install Grape in your Ruby application, you need to add the Grape gem to your Gemfile. Open your Gemfile and add the following line: gem 'grape'
. Then, run bundle install
in your terminal to install the gem. After the installation, you can include Grape in your application by adding require 'grape'
at the top of your Ruby file.
How can I define routes in Grape?
In Grape, routes are defined within the API class. You can define a route by specifying the HTTP method (get, post, put, delete), the path, and a block that will be executed when the route is hit. For example, to define a GET route at ‘/hello’, you would write: get '/hello' do 'Hello World' end
.
How can I handle parameters in Grape?
Grape provides a params
method that you can use to access parameters in your routes. You can define required and optional parameters using the requires
and optional
methods respectively. For example, to require a ‘name’ parameter in a POST route, you would write: requires :name, type: String
.
How can I handle errors in Grape?
Grape provides a rescue_from
method that you can use to handle exceptions in your API. You can specify the type of exception to rescue from and a block that will be executed when the exception is raised. For example, to rescue from a RecordNotFound
exception, you would write: rescue_from ActiveRecord::RecordNotFound do |e| error_response(message: e.message, status: 404) end
.
How can I version my API in Grape?
Grape provides several strategies for versioning your API, including path, header, and parameter. You can specify the versioning strategy and the versions of your API using the version
method. For example, to version your API using the path strategy, you would write: version 'v1', using: :path
.
How can I return JSON responses in Grape?
By default, Grape returns responses in the format specified in the ‘Accept’ header of the request. However, you can force Grape to return responses in JSON format by adding format :json
at the top of your API class.
How can I validate parameters in Grape?
Grape provides several methods for validating parameters, including requires
, optional
, exactly
, and at_least
. You can specify the type of the parameter, whether it is required or optional, and additional validation options. For example, to require a ‘name’ parameter of type String, you would write: requires :name, type: String
.
How can I use middleware in Grape?
Grape allows you to use Rack middleware in your API. You can add middleware to your API using the use
method. For example, to use the Rack::Cors
middleware, you would write: use Rack::Cors do allow do origins '*' resource '*', headers: :any, methods: [:get, :post, :put, :delete, :options] end end
.
How can I test my Grape API?
You can test your Grape API using any Ruby testing framework, such as RSpec or MiniTest. Grape provides a Grape::API::TestCase
class that you can use to write tests for your API. For example, to test a GET route at ‘/hello’, you would write: get '/hello' assert last_response.ok? assert_equal 'Hello World', last_response.body
.
How can I document my Grape API?
Grape provides a desc
method that you can use to document your API. You can specify the description of a route, the parameters it accepts, and the response it returns. For example, to document a GET route at ‘/hello’, you would write: desc 'Returns a greeting' get '/hello' do 'Hello World' end
.
Jay is a Software Engineer and Writer. He blogs occasionally at Code Handbook and Tech Illumination.