RailsでWeb APIを作成する方法とメリット🤔💭

# まず始めに

おはようございます。こんにちは。こんばんは！
[DMM WEBCAMP Advent Calendar](https://qiita.com/advent-calendar/2021/infratop)の最終日を担当させていただきます

メンターのtanidaです！

今回はRailsで典型的なWeb APIサーバーを構築するハンズオンになります！ よろしくお願いします！

＊あまり細かい用語の説明はしませんが随時更新していく予定です。

この記事で完成したRails APIのソースコードです。

https://github.com/digitter/rails_api_sample

途中で利用するReactのサンプルコードです。

https://github.com/digitter/react-sample

# この記事の対象者

- Ruby on Railsでアプリを作ったことがある人。

- Ruby on Railsの環境構築ができる人。

- Rails の API モードでサーバーの構築がしたい人。

# 典型的なREST APIを作成！

## Ruby, Railsのバージョン

- Ruby 2.6.3

- Rails 6.1系
>webpacker使わないので別に5系でもいいですがソースコードはこのバージョンです。

## APIサーバーを作成

お好きなディレクトリでrails newを実行してください。

APIモードでRailsをつくるので`--api`オプションを使います。

```
$ rails new rails_api_sample --api

$ cd rails_api_sample

# 必須のオプションではありませんが -j4 つけると少しインストールが速くなります。
$ bundle install -j4

$ rails s
```

rails s後に以下のような画面が出ていればOK ↓

`http://localhost:3000` の部分はご自身のものに合わせてください。

＊Cloud9を利用されている方は `Preview > Preview Application` から別タブで開いてください。

![スクリーンショット 2021-12-25 1.11.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240416/97d7efa7-f9e6-060e-65ed-cf3e69de54b7.png)

APIサーバーを作ったことがない人向けに簡単に説明するとRailsのViewがないバージョンです。

Viewに当たる部分は、別のアプリケーションで担います。

近年だとReactやVueやAngulerとかが流行ってます。(個人的にはReactが好きです。)

## scaffoldでサクッとRouting, Controller, Model, Migrationを用意

scaffoldでサクッとRouting、Controller、Model、Migrationファイルをサクッと作成します。
controller内には自動でindex, show, create, update, destoryに相当するアクションも追記されます。

(繰り返しますがAPIモードなのでViewはありませんよ。)
(APIモードだとnewアクションは不要だと判断されてcontroller内にもありません。)

RailsのサクッとCRUD機能を作れちゃう辺り本当に便利だなぁとつくづく思います。

```
$ rails g scaffold book title:string body:text
```

以下を実行してstringのtitle、textのbodyというカラムを持ったbooksテーブルを作成します。

```
$ rails db:migrate
```

/booksのパスにアクセスしてすべてのBookの情報を取得してみます。

```
$ rails s
```

ブラウザか、curlコマンドで `/books` のパスでアクセスしてみてください。

`http://localhost:3000` の部分はご自身のものに合わせてください。

＊Cloud9を利用されている方は `Preview > Preview Application` から別タブで開いてください。

![スクリーンショット 2021-12-26 1.55.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240416/a8a1e3d9-c4f7-00b6-9f99-40c695e5542e.png)

まだデータを作成していないので、レスポンス結果は空ですね。

＊ Cloud9を利用されている方は3000の部分は8080で

```
$ curl http://localhost:3000/books

[]
```

## seedファイルでBookデータを3個作成

seeds.rbに以下を追記

```db/seeds.rb
3.times do |i|
  Book.create(title: "タイトル #{i}", body: "内容 #{i}")
end
```

DBにデータを作成！

```
$ rails db:seed
```

もう一度curlでRailsに問い合わせするとデータがちゃんと3個返ってきました。

(BooksControllerのindexアクションにアクセスしています。)

```
$ curl http://localhost:3000/books

[{"id":1,"title":"タイトル 0","body":"内容 0","created_at":"2021-12-24T16:33:35.507Z","updated_at":"2021-12-24T16:33:35.507Z"},
{"id":2,"title":"タイトル 1","body":"内容 1","created_at":"2021-12-24T16:33:35.512Z","updated_at":"2021-12-24T16:33:35.512Z"},
{"id":3,"title":"タイトル 2","body":"内容 2","created_at":"2021-12-24T16:33:35.516Z","updated_at":"2021-12-24T16:33:35.516Z"}]
```

# curlコマンドでAPIに問い合わせてCRUD体験

curlコマンドでRailsにアクセスしてみます。

初めてcurlコマンドを見る方は、見るのも嫌になるかもしれないですが簡単にいうとターミナルからHTTPリクエストしているだけです。

**curlコマンドの書き方を覚えてほしいのではなくRailsにアクセスすると、
以下のようなことができるんだなぁと理解できればそれでひとまずはOKです！**

- データをすべて取得
- データの一部を取得
- １つのデータを更新
- １つのデータを削除

ここで理解していただきたいのは

**APIサーバー（Rails）にアクセスすると上記のCRUDな機能が利用できるんだなぁ〜ということだけです。😌**

＊留意点
>Cloud9を利用されている方は http://localhost8080 でアクセスしてください。

## データを取得してみましょう

すべてのBookを取得してみます。
(BooksControllerのidexアクションにアクセスしています。)

```
$ curl http://localhost:3000/books

[{"id":1,"title":"変更したのだ","body":"変更したのだ","created_at":"2021-12-24T16:33:35.507Z","updated_at":"2021-12-24T16:52:33.141Z"},
{"id":2,"title":"タイトル 1","body":"内容 1","created_at":"2021-12-24T16:33:35.512Z","updated_at":"2021-12-24T16:33:35.512Z"},
{"id":3,"title":"タイトル 2","body":"内容 2","created_at":"2021-12-24T16:33:35.516Z","updated_at":"2021-12-24T16:33:35.516Z"}]
```

Bookを一つ取得してみます。
(BooksControllerのshowアクションにアクセスしています。)

```
$ curl http://localhost:3000/books/1

{"id":1,"title":"変更したのだ","body":"変更したのだ","created_at":"2021-12-24T16:33:35.507Z","updated_at":"2021-12-24T16:52:33.141Z"}
```

## データを作成してみましょう

新しいBookを作成してみます。
(BooksControllerのcreateアクションにアクセスしています。)

curlコマンドの雑な説明をすると以下を指定しています。

- -H はHTTPのヘッダの指定
- -XはHTTP verbの指定
- -d(-data)はデータの指定

```
$ curl -H "Content-Type: application/json" -X POST -d '{"title": "hoge", "body": "hoge"}' http://localhost:3000/books

{"id":5,"title":"hoge","body":"hoge","created_at":"2021-12-24T16:41:24.118Z","updated_at":"2021-12-24T16:41:24.118Z"}
```

確認すると一個データが増えてますね。

```
$ curl http://localhost:3000/books

[{"id":1,"title":"タイトル 0","body":"内容 0","created_at":"2021-12-24T16:33:35.507Z","updated_at":"2021-12-24T16:33:35.507Z"},
{"id":2,"title":"タイトル 1","body":"内 容 1","created_at":"2021-12-24T16:33:35.512Z","updated_at":"2021-12-24T16:33:35.512Z"},
{"id":3,"title":"タイトル 2","body":"内容 2","created_at":"2021-12-24T16:33:35.516Z","updated_at":"2021-12-24T16:33:35.516Z"},
{"id":4,"title":"hoge","body":"hoge","created_at":"2021-12-24T16:40:36.498Z","updated_at":"2021-12-24T16:40:36.498Z"},
```

## データを削除してみましょう

id 4 の　book　を削除してみます。
(BooksControllerのdestroyアクションにアクセスしています。)

```
curl -X DELETE http://localhost:3000/books/4
```

データが一つ消えてますね。

```
$ curl http://localhost:3000/books

[{"id":1,"title":"タイトル 0","body":"内容 0","created_at":"2021-12-24T16:33:35.507Z","updated_at":"2021-12-24T16:33:35.507Z"},
{"id":2,"title":"タイトル 1","body":"内 容 1","created_at":"2021-12-24T16:33:35.512Z","updated_at":"2021-12-24T16:33:35.512Z"},
{"id":3,"title":"タイトル 2","body":"内容 2","created_at":"2021-12-24T16:33:35.516Z","updated_at":"2021-12-24T16:33:35.516Z"}]
```

## データの更新をしてみましょう

id 1 の Bookの更新をしてみます。
(BooksControllerのupdateアクションにアクセスしています。)

```
$ curl -H "Content-Type: application/json" -X PATCH -d '{"title": "変更したのだ", "body": "変更したのだ"}' http://localhost:3000/books/1

{"title":"変更したのだ","body":"変更したのだ","id":1,"created_at":"2021-12-24T16:33:35.507Z","updated_at":"2021-12-24T16:52:33.141Z"}
```

データが確かに変更されていますね。

```
$ curl http://localhost:3000/books

[{"id":1,"title":"変更したのだ","body":"変更したのだ","created_at":"2021-12-24T16:33:35.507Z","updated_at":"2021-12-24T16:52:33.141Z"},
{"id":2,"title":"タイトル 1","body":"内容 1","created_at":"2021-12-24T16:33:35.512Z","updated_at":"2021-12-24T16:33:35.512Z"},
{"id":3,"title":"タイトル 2","body":"内容 2","created_at":"2021-12-24T16:33:35.516Z","updated_at":"2021-12-24T16:33:35.516Z"}]
```

# APIサーバー...RailsからViewを無くしてるのに何が嬉しいん？🤔

## 別のアプリと、Railsを組み合わせられます

マイクロサービス化と呼ばれるやつです。

機能ごとに小さなアプリを作り、組み合わせることで大きな機能を構築していく設計手法です。

実際どんな感じか図で見ていきましょう。

### **Viewに別のアプリを使う**

RailsのViewだと正直UI,　UXのレベルに限界があります。

見た目をゴリゴリに動かしたりエレガントにしたい場合はJavaScriptのライブラリであるJQueryを使うのもありですが、RailsのView自体を使わないことも多かったりします。

流行りでいうとJavaScriptのライブラリ、フレームワークにあたるReact, Vue, Angularがあります。

ReactのフレームワークのNext.js, Gatsyby.jsやVueのフレームワークのNuxt.jsなども多くの企業で導入されている傾向があります。

![スクリーンショット 2021-12-25 2.23.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240416/ffce10ac-e8b6-d08a-2d5f-21723c89c22c.png)

### **API to APIなやりとりが出来る**

例えば、決済関連のデータの扱いを別のRails APIに担当してもらったり役割分割できます。

カスタマイズ性が上がって変化に強い設計ができるわけですね。

![スクリーンショット 2021-12-25 2.37.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240416/b88f036f-3e40-1fc0-2ebc-0db2b3e97f9e.png)

# ここまでのまとめ

Railsで典型的なJSON形式でデータを返すAPIサーバーを作成してみました！

ざっくりとメリットデメリットにも図を用いて説明してみました！

**Railsは爆速でAPIサーバーを作れる！** 最高ですね。

# APIサーバーとReactを繋げる

これまでAPIサーバーを作成する方法とメリットについて学びました。

しかし、APIサーバーだけだとどのように実際のプロダクトで利用されているかが分かりにくいと思います。

そこで **RailsのViewの代わりになる別のアプリを作成してみましょう。**

![スクリーンショット 2021-12-25 2.23.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240416/ffce10ac-e8b6-d08a-2d5f-21723c89c22c.png)

Viewを担う別のアプリとしてJavaScriptのライブラリである **React** を用います。

## 前提

- Reactがどういうものなのかは説明していません。

>ぜひ[公式サイト](https://ja.reactjs.org/)をご参照ください。

- ES2015以降のJavaScriptを知っていないとコードを理解するのは難しいです。

>2015年以降のJavaScriptを学びたい方はぜひ[JS Primer](https://jsprimer.net/)を見ることをお勧めします。

- ご使用のマシンに[Node.js](https://nodejs.org/ja/)がインストールされている必要があります。

>ターミナルで、node -v を実行してバージョンが出たらインストールできています。
>インストールされていない方は公式からLTS(long term support)版をインストールしていれば良いでしょう。

- Cloud9の人はすみませんこれからやる方法ではできません。😞

## CORSの設定をしてRails APIサーバーを起動

CORSとは何かを簡単にいうと、どこからのリクエストを許可するかを決めています。

Rails は `http://localhost:3001` で起動して

React は `http://localhost:3000` で起動していきます。

プロトコル、ドメイン、ポートのどれかが一致していないもの同士で通信のやり取りをする場合は、CORSの設定が必要になります。

今回はポートが異なるのでやはりCORSの設定が必要になります。

参考
>https://developer.mozilla.org/ja/docs/Web/HTTP/CORS

- Gemfileで以下を追記

```
gem 'rack-cors'
```

```
$ bundle install
```

- config/initializers/cors.rbを開いて以下を追記

```config/initializers/cors.rb

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://localhost:3000' # ← ここからのアクセスを許可します。

    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end
```

3001ポートで起動します。

```
$ rails s -p 3001
```

## Reactアプリを起動する

まずNode.jsがインストールされているか確認します。

```
$ node -v
v16.13.1
```

作成済みのReactアプリケーションがあるのでcloneして立ち上げます。
[サンプルコード](https://github.com/digitter/react-sample)

```
$ git clone git@github.com:digitter/react-sample.git

$ cd react-sample

$ npm install

$ npm run start
```

APIサーバーからBookの情報を取ってきて表示するようにしてあります。

Reactが3000ポートで起動するのでアクセスすると。。。

![スクリーンショット 2021-12-26 22.06.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240416/8ef44984-90d0-8ec2-5559-2e2acb2cd5ba.png)

おまけでボタンをクリックするとモーダルウィンドウで詳細を見れたり、閉じたりできるようにもしています。

Reactを使うとエレガントなViewにできますよ😄

![スクリーンショット 2021-12-28 22.42.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240416/a497c9af-3af0-0697-e717-74305692c242.png)

# まとめ

APIサーバーとReactを繋げてみました。

このようにAPIとフロントエンドの別アプリでやり取りするという設計は多くのアプリケーションで利用されています。

~~# GraphQL関連を期待してた人へ（🙏😞🙏）~~

~~この記事は今回のWeb APIと比較してGraphQLサーバーを使ってみたかったのですが、~~
~~当方の時間の確保が難しく `RailsのWeb APIサーバの作成`、 `Reactとやり取りする` ところまでに止めました。~~

~~予めカレンダーのタイトルを見ていてGraphQL関連の内容を期待していた人はごめんなさい。🙏😞🙏~~

~~Railsで作るGraphQLサーバーの話は年末までに別記事でまとめてみようと思います。~~
~~（この記事をストックしていただけたら新しい記事を出した時に通知がいくようにします。）~~

皆さんがAPIと仲良くなってくれると幸いです。₍₍(ง˘ω˘)ว⁾⁾**API**

この記事の続きとして[Rails APIでGraphQLサーバーの構築](https://qiita.com/digitter/items/a2b40553afe401cf1d64)もしていますので興味がある方はぜひご参照ください。

- **12/26**
  - Reactと繋げるところまで完了
- **12/28**
  - Reactアプリの見た目をちょっとだけいい感じに変更(モーダルウィンドウで表示)
  - Date time をYYYY年MM月DD日で出力するように変更(Moment.jsの後継ライブラリLuxonを使用)

- **12/30**
  - RailsでGraphQLサーバー構築体験🤔💭

https://qiita.com/digitter/items/a2b40553afe401cf1d64