LoginSignup
26
30

More than 5 years have passed since last update.

爆速で軽量なRubyのREST APIを作る方法

Last updated at Posted at 2018-09-30

フルスタックのRuby on Railsを使うのではなく、RESTfulなAPIをPythonのflaskのような手軽さで構築する方法を調べました。

下準備

今回はRuby on Railsで使われている一部のフレームワークを使います。

  • Ruby 2.5
  • Grape (RESTライクなAPI構築のためのフレームワーク)
  • rack (HTTPリクエスト処理のためのフレームワーク)

Rubyはあらかじめインストールしておいてください。
Grapeとrackはgemを使ってインストールすることができます。

$ gem install grape
$ gem install rack

APIの実装

Grapeを使って、APIのエンドポイントを記述します。

hello.rb
require 'grape'

class HelloAPI < Grape::API
  format :json
  get '/' do
    # paramsでパラメータを取得
    { 'hello': 'world', 'params': params }
  end
end

rackの設定ファイルを以下のように記述します。

config.ru
require './hello' # hello.rbが同じディレクトリに格納されていること
run Rack::Cascade.new [HelloAPI]

起動

config.ruの存在するディレクトリで、以下のコマンドを実行します。

$ rackup

http://localhost:9292
にアクセスして{"hello":"world","params":{}}が表示されることを確認。
http://localhost:9292?a=1
にアクセスすると{"hello":"world","params":{"a":"1"}}パラメータがそのまま返却されます。

非常に簡単に軽量なAPIを作成することができました。

(参考) 自動再起動

rerunというツールを利用するするとソースコードの変更を自動的に検知して再起動してくれます。

$ gem install rerun
$ rerun rackup

(参考) よく使う機能

Request Body

Request Bodyはenv['api.request.body']で取得できます。

post '/' do
  body = env['api.request.body']
  return body # Response BodyにそのままRequest Bodyを返す
end
$ curl -X POST http://localhost:9292/ -H 'Content-Type:application/json' -d '{"OK": 0}'
{"OK":0}

Pathパラメータの取得

paramsハッシュから取得できます。

get 'users/:user_id' do
  id = params[:user_id]
  return { user_id_param: id }
end

Queryパラメータの取得

pathと同じくparamハッシュから取得できます。

get '/' do
  name = params[:name]
  return { name_param: name }
end
$ curl -X get http://localhost:9292/?name=taro -H 'Content-Type:application/json'
{"name_param":"taro"}

Headerの取得

headersハッシュから取得できます。

get '/' do
  id = headers['X-Account-Id']
  return { account_id: id }
end
$ curl -X GET http://localhost:9292/ -H 'X-Account-Id:123'
{"account_id":123}

ステータスコードの指定

put `/` do
  status 405
  return { message: 'not support' }
end
$ curl -v -X PUT http://localhost:9292/ -d 'xxx'
(省略)
< HTTP/1.1 405 Method Not Allowed
< Content-Type: application/json
< Content-Length: 25
(省略)
{"message":"not support"}

(参考) 簡単な例

サンプルでユーザの管理APIを作りました。

呼び出し例

  • 全てのユーザの取得
$ curl -X GET http://localhost:9292/users
[{"id":1,"name":"alice","age":20},{"id":2,"name":"bob","age":25}]
  • ユーザのname検索
curl -X GET http://localhost:9292/users?name=bob
[{"id":2,"name":"bob","age":25}]
  • あるユーザの取得
$ curl -X GET http://localhost:9292/users/1
{"id":1,"name":"alice","age":20}
  • ユーザの作成
$ curl -X POST http://localhost:9292/users -H 'Content-Type:application/json' -d '{"name": "charlie", "age": 18 }'
{"name":"charlie","age":18,"id":3}
  • ユーザの更新
curl -X PATCH http://localhost:9292/users/3 -H 'Content-Type:application/json' -d '{"age": 19 }'
{"name":"charlie","age":19,"id":3}
  • ユーザの置換
curl -X PUT http://localhost:9292/users/3 -H 'Content-Type:application/json' -d '{"name": "dave", "age": 30 }'
{"name":"dave","age":30,"id":3}
  • ユーザの削除
curl -X DELETE http://localhost:9292/users/1

コード例

require 'grape'

class Main < Grape::API
  format :json

  alice = { id: 1, name: 'alice', age: 20 }
  bob = { id: 2, name: 'bob', age: 25 }
  users = [alice, bob]

  get '/users' do
    name = ''
    name = params[:name] if params[:name].present?
    status 200
    return users.filter{|user| user[:name].include? name }
  end

  get '/users/:user_id' do
    id = params[:user_id]
    user = users.find{|user| user[:id] == id.to_i }
    if user.present? then
      status 200
      return user
    else
      status 404
      return { message: "Not Found User: #{id}" }      
    end
  end

  post '/users' do
    body = env['api.request.body'].symbolize_keys
    if body[:name].present? && body[:age].present? then
      body[:id] = users.map{|user| user[:id]}.max + 1
      users << body
      status 201
      return body
    else
      status 400
      return { message: "'name' and 'age' must be required" }
    end
  end

  patch '/users/:user_id' do
    body = env['api.request.body'].symbolize_keys
    id = params[:user_id]
    user = users.find{|user| user[:id] == id.to_i }

    if user.nil? then
      status 404
      return { message: "Not Found User: #{id}" } 
    end

    user[:name] = body[:name] if body[:name].present?
    user[:age] = body[:age] if body[:age].present?
    status 200
    return user
  end

  put '/users/:user_id' do
    id = params[:user_id]
    body = env['api.request.body'].symbolize_keys
    user = users.find{|user| user[:id] == id.to_i }

    if user.nil? then
      status 404
      return { message: "Not Found User: #{id}" } 
    end

    if body[:name].nil? || body[:age].nil? then
      status 400
      return { message: "'name' and 'age' must be required" }
    end

    user[:name] = body[:name]
    user[:age] = body[:age]
    status 200
    return user
  end

  delete '/users/:user_id' do
    id = params[:user_id]
    user = users.find{|user| user[:id] == id.to_i }

    if user.nil? then
      status 404
      return { message: "Not Found User: #{id}" } 
    end

    users.reject! {|user| user[:id] == id.to_i }

    status 204
    return ''
  end

end

参考文献

https://github.com/ruby-grape/grape
https://github.com/rack/rack

26
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
30