フロントエンドエンジニアの松井です。
サーバーサイドからAPIをもらって、リクエストを投げる事は日々業務でやっていますが、APIそのものを基礎的なものでも手を動かして作りながら、RESTサービス全体がどう動いているのかの理解を深めようとした時の学びを今回記事にしました。
VideoBRAINのサーバーサイドはRubyが採用されてますが、今回自分はJavaScriptで動かせるNode.js + Express
で、DataBaseにはSQLite3
を使ってAPIを立てました。
サーバーとは?
サーバーもソフトウェアで、ネットワークで繋がったコンピュータ上で、サーバー自身がもっているデータやサービスなどをクライアントのリクエストに応じて提供する。
APIとは?
APIと一言で言っても、いろんな場面で使われていて、場面場面で微妙に意味が違いますが、やり取りの決まりごとという解釈が個人的に一番腑に落ちます。例えば、https://hoge.com/weather
というURLにGETメソッドでリクエストして天気情報を取得するというやり取りの決まりごとがあればそれがAPIです。
RESTとは?
RESTはREpresentational State Transfer
の略で、Webのアーキテクチャスタイル(設計思想)。ネットワークシステムの代表的なアーキテクチャスタイルであるクライアント/サーバから派生したもの。
RESTの原則は主には以下の4つがあります。
1. リソースのアドレス可能性
そのアプリケーションが提供する情報やデータを全て「リソース」という考え方で表現する。そのリソースはURIによって一意に指し示す事ができると言う事。
例えばこんなAPIがあったとします。
GET: https://api.XXXXXX.com/v5/users/12345/comments
userID=12345のcommentsデータをGETメソッドで取得できると言う事がこのURIから読み取る事ができます。
2. ステートレス性
ステートレス性はセッション等のクライアントの「アプリケーション状態」をサーバー側が持たないと言う事。
Video BRAINを例にとると、
フロントからHTTPリクエストを送る時にKey
とToken
をheaderに毎回付けて送る事でAPIとの認証を行っている。
また、リクエスト毎に編集している動画のID, シーンのIDなどを全て含めている。
という風に、サーバとのやりとりで必要な情報を毎回全てリクエストに載せているのはステートレスサーバーだからです。
ステートレスサーバーの利点はリクエストを受けてレスポンスを返したら完結なので、サーバー負荷が減らせる。1ユーザーがずっとコネクションを貼っている状態がなくなることで、リクエスト待ちを減らせるようになる。
3. 接続性
ある情報に「別の情報へのリンク」を含めることができること。そして、リンクを含めることで「別の情報に接続すること」ができること。
Video BRAINを例にとると、
レスポンスデータの中にテキスト画像や、素材のURL
を含んでいる事があり、それをがこの接続性
に該当します。
4. 統一インターフェース
リソースの取得、作成、更新、削除といった操作は、すべてHTTP
で定義されているメソッドを利用すること。よく使うのは、GET(取得)
、POST(登録)
、PUT(更新)
、DELETE(削除)
の4種類です。
RESTの設計ではURIが重要
URIはUniform Resource Identifier
の略で、直訳すると統一リソース識別子。
つまりリソースを同じルールで一意に識別できるID/名前をつける事です。
このURI(エンドポイントとも呼ぶ)設計において、リソースを「どう操作するのか」はHTTPメソッドによって決めます。
つまりはURIとHTTPメソッドはそれぞれが名詞と動詞の関係になる様に設計すべきと言われています。
何(名詞)をどうする(動詞)で表されていて、URIを見ただけで何のAPIなのかが分かる、つまり人間が読んで理解できるURIになる様に設計する事が大事と言われています。
リソースには基本的には名詞の複数形のみ
を使う、api
という単語を含める、バージョン
を含めるなどのURI設計の注意点を考慮して、自分はNode.jsでこの様なAPIを立てました。
https://reactexpress-app-matsui.herokuapp.com/api/v1
GET:/memos 何を?→ memosを どうする?→ GET(取得)する
const express = require('express'); const router = express.Router(); const sqlite3 = require('sqlite3'); const db = new sqlite3.Database('memo_data.db'); router.get('/', (req, res, next) => { db.serialize(() => { db.all("select * from memos", (err, rows) => { if (!err) { const data = { content: rows } res.json({ data }); } }) }) });
POST:/memos 何を?→ memosに どうする?→ POST(追加)する
router.post('/', (req, res, next) => { const tx = req.body.text; db.run('insert into memos (text) values (?)', tx, (err) => { if (!err) res.send('OK') }) });
PUT:/memos/:ID 何を?→ memosの:IDを どうする?→ PUT(更新)する
router.put('/:id', (req, res, next) => { const id = req.params.id const tx = req.body.text; const q = "update memos set text = ? where id = ?"; db.run(q, tx, id, (err) => { if (!err) res.send('OK') }) });
DELETE:/memos/:ID 何を?→ memosの:IDを どうする?→ DELETE(削除)する
router.delete('/:id', (req, res, next) => { const id = req.params.id const q = "delete from memos where id = ?"; db.run(q, id, (err) => { if (!err) res.send('OK') }) });
実際にAPIを立てるまでに辿ったステップ
さて、このAPIを立てるまでに、まず始めにNode.js単体でアプリケーションを作りました(テンプレートエンジンを使ってクライアント画面を生成し、HTMLを返すサーバーサイドレンダリングのフルスタックアプリ)
↓
次にDataBase(SQLite3)と連携させて値を永続的に保存出来る様にする
↓
それからサーバー側をAPI化させてJSONを返すだけにし、クライアント側をReactに切り出しました(フロントエンドとバックエンドの疎結合化)。それで完成したのがこちらです。
https://quirky-perlman-c9420b.netlify.app/
本当に簡易的なメモアプリですが、構成は以下の様になっています。
HTTPステータスコード
HTTPにおいてWebサーバからのレスポンスの意味を表現する3桁の数字からなるコード。今回自分が立てたAPIではExpressがステータスコードを自動設定してくれたのですが、通常はバックエンドエンジニア自身でHTTPステータスの番号を指定するみたいです。
Video BRAINでは、データをきちんと返す際の200番はフレームワークが自動で設定するものを使い、データを返さない時やエラーを返す時は自分たちでHTTPステータスの番号を指定して返しているそうです。
1xx:Informational 情報
2xx:Success、すべて成功
3xx:リダイレクト
4xx:クライアント側のエラー
5xx:サーバー側のエラー
さまざまなWebアプリケーションの開発に活用されているREST APIですが、問題点もあります。
REST APIの問題点
・クライアントでレスポンス形式を指定できない為、不要なデータもフェッチしてしまう
・複数リソースが必要な複雑な画面に、複数のAPIリクエストが必要になる。
・エンドポイントが肥大化して管理が大変。
・フロント、バックエンドでインターフェースの擦り合わせが大変
これらの問題を解決するために生まれたのがGraphQL
です。
GraphQLとは
GraphQLとはFacebookが2015年に公開したAPI向けに作られたクエリ言語。
RESTはAPIの設計原則なのに対して、GraphQLは言語(クエリ言語、スキーマ言語)や型(スカラー型等)が標準化された規格になります。
そもそもクエリ言語とは、コンピュータやデータベースに問い合わせてデータを取得する言語。SQLなどがそれに当たる。GraphQLはAPIに問い合わせてデータを取得したり、書き換えたりする。
メリット
・GraphQLでは、クライアントが必要なデータの構造を定義することができ、サーバーからは定義したのと同じ構造のデータが返される。したがって、必要以上に大きなデータが返されるのを防ぐことができクエリの効率が良い。
・GraphQLのコードが仕様書的に機能するので、フロント側とのコミュニケーションコストを減らせる。
・エンドポイントが1つで、エンドポイントのURL定義ファイルが増えなくていい。
当然メリットだけではなく画像や動画などの大容量バイナリの扱いが難しい、データクエリ処理の多くがサーバーサイドに移行されるので、サーバー側の作業が大変などのデメリットもあるみたいです。REST APIからGraphQL APIに移行するとなれば当然フロント側でも対応が必要になるので容易には行かないと思いました。
まとめ
今回はAPIを立てて、フロントからのリクエストを受けて、DataBaseとSQL文でやりとりして、JSONを返すというバックエンド側を含めたRESTサービス全体を経験する事が目的だったので本当にミニマムな実装とデプロイでしたが、今までバラバラだった点の知識がぐっと繋がりました。
とはいえ、まだまだサーバーもデータベースも学ぶことは多岐に渡るので、基本を学んだ程度ではありますが、自分の担当範囲の外側の理解が深まりました。
木を見て森を見ずの状態で、視野が狭い状態で仕事するのは良くないと思います。 サービス全体の解像度を上げれば連携の精度も上がる。これは別にエンジニアの仕事に限った事ではないので引き続き学んでいきたいと思います。