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を実際に運用していてバージョンを上げたこととか無いのですが、こちらの方が綺麗な構成にできるので..。
viewはこちら。
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の開発とテスト周りを書く予定。