Skip to content

Instantly share code, notes, and snippets.

@kayohlu
Last active April 28, 2017 02:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kayohlu/7b020c89f98cef671c8a to your computer and use it in GitHub Desktop.
Save kayohlu/7b020c89f98cef671c8a to your computer and use it in GitHub Desktop.
How To: Set Up a Grape API on Rack

How To: Set Up a Grape API on Rack

This is a step by step guide on how I setup a Grape API on Rack.

1. Dependencies

Our API will have a few dependencies:

These are all gems that most people are familiar with, hopefully. Click on the links to find out more.

First lets create our project folder: mkdir grape_app

Create a Gemfile with the dependencies listed above. It should looks like this:

source 'https://rubygems.org'

gem 'grape'
gem 'grape-swagger'
gem 'rack-cors'
gem 'activerecord'
gem 'sqlite3'

Run bundle install to install the dependencies which will give us everything we need to create our application.

2. Mounting on Rack

To have our API accepting HTTP requests we are going to mount it on top of Rack. Rack provides a minimal interface between webservers that support Ruby and Ruby frameworks.

Create a config.ru file and make it look like this:

# config.ru
require 'rack'
require 'grape' # require the Grape library

class API < Grape::API
  get :hello do
    {hello: "world"}
  end
end

run API # Mounts on top of Rack.

A little explanation is need at this point. Our API class is our API essentially, it does nothing more than respond to a GET request and return an XML response that looks like this:

<hash>
  <hello>world</hello>
</hash>

Once we have this file created and you run rackup you can visit http://localhost:9292/hello you'll see the above response.

There we have it, a very naive API mounted on top of Rack. Simple enough so far, right? Let's keep going and make this more professional.

3. Adding ActiveRecord

What good is an API without a DB and ORM? I'm quite familiar with ActiveRecord from working with Rails a lot, so I'm going to use this as our API's ORM. At the moment I'm not concerned with running our API in different environments so I'm just going to use sqlite as our DB for convenience.

Rails is great because it makes working with the DB extremely convenient, from it's rake tasks to migrations. I want to be able to have these conveniences in our Grape API.

ActiveRecord contains a module called DatabaseTasks for the drop, create, and migrate functions that we use as rake tasks. This module needs to know, what our current environment is (development or production), our db dir(the folder that will contains all the migrations and schema.rb), where the DB configuration file is (our database.yml), and our migrations folder (db/migrate).

Let's create our folder structure before adding the tasks to our project. Run: mkdir -p db/migrate config

Add our database configuration, just like you normally would when working with a Rails project. Create a database.yml file in config/ that looks like this:

development:
  adapter: sqlite3
  database: development.sqlite3

To use the the drop, create, and migration rake tasks we need to create a Rakefile.rb. This file will gather all the environment and project information the DatabaseTask module requires to run the DB functions, and include our rake tasks to execute them. Create a Rakefile.rb and copy the code below into it.

require 'bundler/setup'
require 'active_record'

## Start tasks setup.

# The next few lines of code will allow us to use ActiveRecord tasks like
# db:migrate, etc. outside of rails.

include ActiveRecord::Tasks

db_dir = File.expand_path('../db', __FILE__)
config_dir = File.expand_path('../config', __FILE__)

DatabaseTasks.env = ENV['RACK_ENV'] || 'development'
DatabaseTasks.db_dir = db_dir
DatabaseTasks.database_configuration = YAML.load(File.read(File.join(config_dir, 'database.yml')))
DatabaseTasks.migrations_paths = File.join(db_dir, 'migrate')

task :environment do
  ActiveRecord::Base.configurations = DatabaseTasks.database_configuration
  ActiveRecord::Base.establish_connection DatabaseTasks.env
end

load 'active_record/railties/databases.rake'

## End tasts setup.

Try and run rake db:create and you will encounter error that says uninitialized constant ActiveRecord::Tasks::DatabaseTasks::Rails. This is because we are using sqlite. Thankfully there is a fix courtesy of the comments here https://gist.github.com/drogus/6087979. This is also where I found the code that allows us to use the DB rake tasks we are used to in Rails. By adding a few extra lines to our Rakefile.rb we can work around this problem. These lines are:

module Rails
  def self.root
    File.dirname(__FILE__)
  end

  def self.env
    ENV['RACK_ENV'] || 'development'
  end
end

Your Rakefile.rb should look something like this now:

require 'bundler/setup'
require 'active_record'

## Start tasks setup.

# The next few lines of code will allow us to use ActiveRecord tasks like
# db:migrate, etc. outside of rails.

# Redefining the Rails module and overriding the root and env methods to fix the
# errors mentioned here: https://gist.github.com/drogus/6087979
module Rails
  def self.root
    File.dirname(__FILE__)
  end

  def self.env
    ENV['RACK_ENV'] || 'development'
  end
end

include ActiveRecord::Tasks

db_dir = File.expand_path('../db', __FILE__)
config_dir = File.expand_path('../config', __FILE__)

DatabaseTasks.env = ENV['RACK_ENV'] || 'development'
DatabaseTasks.db_dir = db_dir
DatabaseTasks.database_configuration = YAML.load(File.read(File.join(config_dir, 'database.yml')))
DatabaseTasks.migrations_paths = File.join(db_dir, 'migrate')

task :environment do
  ActiveRecord::Base.configurations = DatabaseTasks.database_configuration
  ActiveRecord::Base.establish_connection DatabaseTasks.env
end

load 'active_record/railties/databases.rake'

## End tasts setup.

At this stage all we can really do is create and drop a DB. How do we add migrations?

There are two gems that I've found that can achieve this but neither of them seemed to work the way I wanted them to. So to work around this I wrote my own rake task to generate a migration.

Which looks exactly like this:

namespace :db do
  desc "Generates a migration file in db/migrate/"
  task :migration, :name do |t, args|
    File.open("db/migrate/#{Time.now.getutc.strftime('%Y%m%d%H%M%S')}_#{args[:name]}.rb", 'w') do |f|
        f.write("class #{args[:name].classify} < ActiveRecord::Migration
  def up
  end

  def down
  end
end")
    end
  end
end

Just add that to your Rakefile.rb and away you go!

Finally we are ready to add ActiveRecord to our API. All we need to do is require the active_record library, set up the logger, and create the connection to the DB Change your config.ru to looks like this:

# config.ru
require 'rack'
require 'grape'
require 'active_record'


# Enable ActiveRecord logging.
ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)

# DB Connection
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'development.sqlite3')


class API < Grape::API
  get :hello do
    {hello: "world"}
  end
end

run API

As you can see we added three new lines. The first line requires the ActiveRecord libraray, the second sets up our ORM logging, and the third uses ActiveRecord to establish a connection to our development DB.

If you wanted you could, create a migration and define a model inside the config.ru and happily work away but this is not very maintainable. We need to add some structure to our project.

4. Adding More Structure

.
├── Gemfile
├── Gemfile.lock
├── Rakefile.rb
├── app
│   ├── api.rb
│   ├── models
│   │   └── user.rb
│   ├── models.rb
│   ├── mutations
│   ├── routes
│   │   └── user_routes.rb
│   └── routes.rb
├── config
│   └── database.yml
├── config.ru
├── db
│   ├── migrate
│   │   └── 20150718184431_initial_migration.rb
│   └── schema.rb
└── development.sqlite3

Resources

http://intridea.github.io/grape/docs/index.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment