DRY

Web関連の技術の事、食事/お酒の事、旅行の事など

Rails + Grape と nginx + リバースプロキシでRESTfulなAPIサーバーを作ってみる

Rails + Grape と nginx + リバースプロキシでRESTfulなAPIサーバーを作ってみる

題名の通り、RESTfulなAPIサーバーを作りたくて最近の私の流れからRubyでやろうかなと。

ruby-toolboxAPI_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で動いているので一応記載。

ディレクトリ構成はこんな感じにしました。

f:id:ke-16:20140911185352p:plain

通常の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の設定でアクセス元が何も変更すること無く バージョンを切り替えられるよう対応したいと思います。