読者です 読者をやめる 読者になる 読者になる

プログラミングノート

一からものを作ることが好きなエンジニアの開発ブログです。

Rails x Grapeで簡単API開発

数年前はRailsを使ったRESTfulなAPIの作り方という方法で開発をしていたのですが、最近のプロジェクトではGrapeを採用しています。Grapeはそこまで複雑ではないのですが、開発中にこれどうすんの?的なところでハマったところもちらほらあったので、ひと通りまとめておきたいと思います。環境はRails 4.1.6、Ruby 2.1.5。

新規プロジェクト作成

今回はサンプルとしてtitleのみもったTODOの登録/取得APIを開発していきます。まずはプロジェクト作成。

$ rails new todolist -d mysql
$ cd todolist
$ rails g model todo title:string
$ rake db:create
$ rake db:migrate

GemfileにGrapeの設定を追加します。viewはjbuilderで作ってます。

gem 'grape', '~> 0.9.0'
gem 'grape-jbuilder'
group :development, :test do
  gem 'json_expressions'
end
$bundle install

APIのディレクトリ構造

各API毎にクラスファイルを分けたいのと、バージョニングに対応できるようにこのような構成にしています。これまでにAPIを実際に運用していてバージョンを上げたこととか無いのですが、こちらの方が綺麗な構成にできるので..。

f:id:ntaku:20150107124836p:plain

viewはこちら。
f:id:ntaku:20150107124841p:plain

APIの環境設定

API実装の前に諸々設定を済ませてしまいます。

config/application.rb

module Todolist
  class Application < Rails::Application
    config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
    config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
    config.middleware.use(Rack::Config) do |env|
      env['api.tilt.root'] = Rails.root.join 'app', 'views', 'api'
    end
  end
end

config/routes.rb

Rails.application.routes.draw do
  mount API => '/'
end

Rails3だとAPIのコードを修正してもRailsを再起動しないとリロードされなかったので、対応コードを入れていました。Rails4ではなくてもリロードされてる感じだけどとりあえず。
config/initializer/reload_grape.rb

if Rails.env.development?
  api_reloader = ActiveSupport::FileUpdateChecker.new(Dir['app/api/*']) do
    Rails.application.reload_routes!
  end
  ActionDispatch::Callbacks.to_prepare do
    api_reloader.execute_if_updated
  end
end

あとはrake routesしてもAPIの詳細まで表示されないので、

$ rake routes
api      /           API

rakeタスクを追加して対応。
/lib/tasks/routes.rake

namespace :api do
  desc "API Routes"
  task :routes => :environment do
    API.routes.each do |api|
      method = api.route_method.ljust(10)
      path = api.route_path.gsub(":version", api.route_version)
      puts "     #{method} #{path}"
    end
  end
end

このような形でGrape内部で定義されているAPIが全部出てきます。

$ rake api:routes
GET       /api/v1/todos(.:format)
POST      /api/v1/todos(.:format)

APIの実装(下準備編)

まずは全体の共通コードから。v1/root.rbという風にバージョン毎のルートを作成し、api.rbからマウントします。これで http://localhost:3000/api/v1/... というURLになります。

app/api/api.rb

class API < Grape::API
  prefix 'api'
  format :json
  formatter :json, Grape::Formatter::Jbuilder
  error_formatter :json, Formatter::Error
  mount V1::Root
end

app/api/v1/root.rb

module V1
  class Root < Grape::API
    version 'v1'
    mount V1::Todos
  end
end

こちらはエラーになった場合のフォーマットを定義しているコード。
app/api/formatter/error.rb

module Formatter
  module Error
    def self.call message, backtrace, options, env
      { :message => message }.to_json
    end
  end
end

APIの実装(TODO API編)

今回はこのような単純なAPIを作ります。

  method URI params 説明
取得 GET /api/v1/todos 登録されているTODOを全て返す
登録 POST /api/v1/todos title=タイトル TODOを登録する

実装はこれだけ。パラメータについてはparamsブロックで定義しておけば、不足している場合に自動的にエラーを返してくれます。便利。

module V1
  class Todos < Grape::API
    resource :todos do

      desc "TODO一覧取得"
      get '', jbuilder: 'todos/index' do
        @todos = Todo.all
      end

      desc "TODO登録"
      params do
        requires :title, type: String, desc: 'TODOタイトル'
      end
      post '' do
        Todo.create(title: params[:title])
        status 201
      end
    end
  end
end

TODO一覧取得用のviewファイル。
app/views/api/todos/index.jbuilder

json.todos do |json|
  json.array!(@todos) do |todo|
    json.id todo.id
    json.title todo.title
  end
end

curlを使って検証

$ curl -X POST -d "title=todo1" http://localhost:3000/api/v1/todos
201
$ curl -X GET http://localhost:3000/api/v1/todos
{"todos":[{"id":1,"title":"todo1"}]}

これで完成!
続編で認証APIの開発とテスト周りを書く予定。