フルスタックの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のエンドポイントを記述します。
require 'grape'
class HelloAPI < Grape::API
format :json
get '/' do
# paramsでパラメータを取得
{ 'hello': 'world', 'params': params }
end
end
rackの設定ファイルを以下のように記述します。
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