Rails + Grape と nginx + リバースプロキシでRESTfulなAPIサーバーを作ってみる
Rails + Grape と nginx + リバースプロキシでRESTfulなAPIサーバーを作ってみる
題名の通り、RESTfulなAPIサーバーを作りたくて最近の私の流れからRubyでやろうかなと。
ruby-toolboxのAPI_Buildersなどで見るとGrapeというのが一番人気なようでしたのでコレにしました。
この公式のGitHubにサンプルがたくさんあるのも良い点かと思います。
それで公式サイト以外にも検索して、Grapeのお作法みたいのをいろいろ調べたのですが、幾つかやり方とか書き方があるみたいでコレが正解とういうのはハッキリとは無さそうです。
ですので私のやり方以外にも多々書き方はあるかと思います。
更に、私はまだまだRuby on Rails + somethingは初心者の域を出ていないので、、、
以上よりGrapeを使用してAPIを作成しました。
またRailsを絶対に使う必要は無いと思いますが、結局多くの場合DBと連動するのでやはりmigrateが優れているRailsを使用しました。
(今は他のFWの学習コストを掛けたく無いなというもあります、、、)
なのでまずはいつも通りRailsの環境を用意します。
gem 'rails', '4.1.5' # For Grape API gem 'grape' gem 'grape-rabl' gem 'grape-jbuilder'
- その他多数。
- railsは4.1.5で動いているので一応記載。
ディレクトリ構成はこんな感じにしました。
通常のRailsの構成に、 api ディレクトリを足した形にしました。
検索するとcontrollersの中にapiディレクトリを切ってる方もいらっしゃいましたが特に制約は無いように思います。
今回は例として、Userに関する情報を扱うAPIとカテゴリ情報を扱うAPIを定義してみます。
以下編集していくファイルです
app/endpoint.rb
まずはGrape::APIを継承しているEndpoint::APIというクラスを作成します。
これでv1以下に設置されているAPIのmountを行い、定義されていないAPIのpathはすべて404で拾うようにしました。
require 'v1/user_api.rb' require 'v1/category_api.rb' module Endpoint class API < Grape::API mount UserApi::APIv1 mount CategoryApi::APIv1 route :any, '*path' do error!({ error: 'Not Found', detail: "No such route '#{request.path}'", status: '404' }, 404) end end end
config/routes.rb
大本のroutesは先ほどのEndpoint::APIに "/api" でアクセスするよう向けてあげます
Rails.application.routes.draw do mount Endpoint::API => '/api' end
こんな感じになります。
$ rake routes Prefix Verb URI Pattern Controller#Action endpoint_api /api Endpoint::API
app/v1/user_api.rb
実際のuserに関する処理を行うAPIの実体ですね。 ファイル名はrailsの命名ルールに則って、 "_api" を付けてます。
module UserApi class APIv1 < Grape::API format :json default_format :json version "v1", using: :path helpers do # Model User のStrong parameterをここで定義 def user_params ActionController::Parameters.new(params).permit(:id, :name, :device_id, :status, :signed_out) end end resource :user do # ここで定義したresourceがURLになります。(=/api/v1/user) desc "Return each user data" # GET /api/v1/user/:id get ':id' do User.find(params[:id]) end desc "Return all user datas" # GET /api/v1/user get do User.all end desc "Update a status" params do requires :id, type: String, desc: "Status ID." requires :status, type: String, desc: "Your status." end put ':id' do current_user.statuses.find(params[:id]).update({ status: params[:status] }) end desc "Delete a status" # Don't allow to delte user from api desc "Entry new user" # POST /api/v1/user # For example, the request should be like this # curl -d '{ # "id": "39784", # "name": "test_user_39784", # "device_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", # "status": "", # "sigined_out": false # }' -X POST -H "Content-Type:application/json, Accept-Version:v1" https://localhost:3000/api/v1/user params do requires :id, type: String requires :name, type: String requires :device_id, type: String, regexp: /^[0-9A-Za-z_-]+$/ optional :status, type: String, desc: "Your status." optional :signed_out, type: Boolean, default: false end post do @user = User.new(user_params); @user.save! # Return created status 201 end end end end
REST設計
機能 | method + URL |
---|---|
ユーザ一覧を取得 | GET /api/v1/user |
1ユーザを取得 | GET /api/v1/user/1 |
新しいユーザを追加 | POST /api/v1/user |
既存のユーザを修正 | PUT /api/v1/user/1 |
既存のユーザを削除 | DELETE /api/v1/user/1 |
このRESTの思想に沿って、それぞれ定義してあります。
例では、APIからのユーザ削除は受け付けない(定義してない)など。
登録する時の "# POST /api/v1/user" などが一番需要があるかと思いますが、 "params do" 内を見て頂くと この例では、POSTで引数にjson形式のデータを受け取る定義になっています。
user_api.rb上部にて
default_format :json version "v1", using: :path
jsonの中身は、id, name, device_idは必須としていて、statusとsigned_outは無くても良くしてあります。
またdevice_idは正規表現で形式を絞ってあります。
このような形でsave!で例外が発生しなければ、response:status_codeを201で返します。
さらに、 "helpers do" 自由にhlper関数を定義できるのでここではUserテーブルのStrong Parameterを定義してあります。
それで後はAPIをV2にバージョンアップしたければ、それぞれクラス毎にアップデータできます。
概要が飲み込めれば比較的簡単に短時間でAPIが作成できるので、とても便利なモジュールだと思います。
ただこのままですとURLにバージョンが入っていてイケてないので、次のエントリーでnginxの設定でアクセス元が何も変更すること無く バージョンを切り替えられるよう対応したいと思います。