注意:今回の記事で載せているコードは読者に具体的なコードのイメージを持たせる目的で書いている。それ故に、実際にブラウザ上で実行しても動作しない点には注意してほしい。より専門的ににGraphQLとRESTの違いを学びたいならLogRocketの記事とApolloの記事を参考に。
今回の記事では、Web APIの開発に重宝されるRESTとGraphQLの違いを解説する。
注意:今回の記事で取り扱う「API」は「Web API」を意味する。
RESTとGraphQLの議論に入る前に、まずはAPIについて説明する必要がある。
Wikipediaによると、API(Application Programming Interface)は以下のように定義されている。
APIは各種システム/サービスがそのシステム/サービスを利用するアプリケーションに対して公開するインタフェースである。APIの重要な役割は、システム/サービス提供者が公式に仕様(外部仕様)を定義し、管理している各種機能を利用するための操作方法(インタフェース)を提供することである。
要は、APIはアプリケーション間で情報をやり取りするためのインターフェイスである。APIを使うことで、あるシステムが他のシステムの機能やデータに直接アクセスできるようになるのだ。
APIが果たす役割は以下の2つである。
REST(REpresentationalStateTransfer) APIは、Web上のシステム間で情報をやり取りするための一般的な設計パラダイムの一つである。RESTは、リソースを識別してそのリソースの状態をHTTPメソッドで操作するための設計減速を定義する。
REST APIの特徴は以下の通り。
それぞれ順番に解説する。
REST APIはリソース指向だ。リソース(Resource)とは何らかの情報の塊で、この情報はURL(Uniform Resource Locator)で識別される。例えば、あるユーザを意味するリソースのURLはhttps://api.example.com/users/123となる。
REST APIでは、以下のようなHTTPメソッドを使ってリソースを操作する。
| HTTPメソッド | 意味 |
|---|---|
GET | 情報を取得する |
POST | 情報を送信する |
PUT | 情報を上書きする |
DELETE | 情報を削除する |
REST APIはステートレスである。これは、それぞれのリクエストがそれぞれ独立していて、前のリクエストの状態を引き継がないということになる。これにより、サーバの設計がシンプルになって拡張性が向上する。
REST APIのレスポンスは、原則JSONあるいはXML形式で出力される。それ故に、様々なプログラミング言語から簡単に利用できる。
例えば、REST APIでユーザ情報を取得するJavaScriptのコードは以下のようになる。
const fetch=require('node-fetch');fetch('https://api.example.com/users/123').then(response=> response.json()).then(user=>console.log(user)).catch(err=>console.error(err));上述のコードは、https://api.example.com/users/123のリソースをGETメソッドで取得し、その結果をJSON形式でパースする。
このように、REST APIはWeb上のシステム間で情報をやり取りするためのシンプルで一般的な方法を提供する。
REST APIのリソースはURLで一意に識別される。例えば、特定のユーザの情報を取得するには以下のようなリクエストを行う。
fetch('https://example.com/users/123').then(response=> response.json()).then(data=>console.log(data));REST APIはHTTPメソッドを用いてリソースを操作する。ユーザ情報の更新はPUTメソッドで実行できる。
fetch('https://example.com/users/123',{method:'PUT',body:JSON.stringify({name:'New Monster Name'})}).then(response=> response.json()).then(data=>console.log(data));REST APIでは、特定のユーザの名前だけが欲しい場合でも、すべての情報が取得される。また、ユーザとその住所に関する情報を一度に取得するには2つの異なるリクエストをしなければならない。
fetch('https://example.com/users/456').then(response=> response.json()).then(data=>console.log(data));fetch('https://example.com/users/456/address').then(response=> response.json()).then(data=>console.log(data));REST APIを変更する際には新しいバージョン、あるいは新しいエンドポイントを作らなければならない。
fetch('https://example.com/v2/users/123').then(response=> response.json()).then(data=>console.log(data));GraphQLはAPIを開発するためのクエリ言語だ。2015年にFacebook(現在のMeta社)が、RESTの問題を解決するために開発した。RESTとの最大の違いは、クライアントが必要なデータの構造を任意で定義できる点にある。(詳細は後述)
REST APIでは、HTTPメソッドに従ってCRUDを軸にデータの取得、編集や削除を行う一方で、GraphQLではデータの問い合わせ(Query)、書き換え(Mutation)をサポートする。
GraphQLの概念とRESTのCRUDをそれぞれ照らし合わせると以下のようになる。
GraphQLでは、クライアントがクエリ内容を記述したdocumentを送信し、GraphQLサービスがクエリを実行して結果を出力する。documentはGraphQL独自の言語(これをGraphQL query languageという)で書く。
GraphQL APIでは、クライアントが必要なデータだけを具体的に要求できる。
fetch('https://example.com/graphql',{method:'POST',headers:{'Content-Type':'application/json','Accept':'application/json'},body:JSON.stringify({query:` { user(id: 456) { name address { country city } } }`})}).then(response=> response.json()).then(data=>console.log(data));GraphQL APIはREST APIのようなバージョン管理が不要である。APIの仕様を変更したい場合は新しいフィールドを追加するだけでいい。
GraphQLはクライアントが取得するデータを任意に定義できるので柔軟性と豊かな表現が得意だ。そのため、APIが複雑になってしまいシンプルなAPIとの相性が最悪な可能性がある。
GraphQLはRESTと違って、独自のアプローチでデータを取得するためHTTPとの相性が悪い。
例えば、REST APIにはCRUDと言って、データの作成や上書き、削除などをHTTPメソッドに対応させて設計できる。ところが、GraphQL APIにおけるデータの操作はQuery(データの読み込み)とMutation(データの編集)という2種類しかない。そのため、HTTPメソッドに対応させるのが難しいのだ。
▼ 表1:REST APIの場合(CRUD)
| CRUD | 対応するHTTPメソッド |
|---|---|
| Create(新規作成) | POST |
| Read(読み込み) | GET |
| Update(上書き) | PUT |
| Delete(削除) | DELETE |
▼ 表2:GraphQL APIの場合
| 名前 | 対応するHTTPメソッド |
|---|---|
| Query(データの読み込み) | GET |
| Mutation(データの編集) | POST、PUT、DELETE |
上述の表を見てもらうとわかるが、GraphQLのMutationはCRUDにおけるRead(読み込み)以外の機能を全部担当するような形になっている。
前のパートでは、RESTとGraphQLのそれぞれの長所や短所を解説した。しかし、これだけを説明しても具体的にどう違うのかを理解するのは難しいかもしれない。
ここで、REST APIとGraphQL APIの違いを、『ドラゴンクエスト』を具体例に解説する。例えば、「特定のモンスターのデータを取得する」ケースを考えてみよう。
例えば、『ドラゴンクエスト』に登場する特定のモンスターのデータを取得する場合を考えてみよう。
REST APIの場合、リソースはURLで定義される。特定のモンスターについての情報を取得するには、そのモンスターのIDをURLに含めた`GETリクエストを送信することになる。
fetch('https://example.com/monsters/123').then(response=> response.json()).then(data=>console.log(data));ここで、他のキャラクターのデータを取得したい場合を考えると、別途そのキャラクターのIDをURLに含めたリクエストを送信する必要がある。
fetch('https://example.com/characters/456').then(response=> response.json()).then(data=>console.log(data));一方で、GraphQLの場合はクライアントがサーバに対して必要なデータを具体的に要求する。上述の情報を一度のリクエストで取得できる。
fetch('https://exmaple.com/graphql',{method:'POST',headers:{'Content-Type':'application/json','Accept':'application/json',},body:JSON.stringify({query:` { monster(id: 123) { name attack defense } charater(id: 456) { name level equipment { name power } } }`})}).then(response=> response.json()).then(data=>console.log(data));このように、GraphQLは単一のリクエストで複数のリソースを取得できる。さらに、「特定のモンスターのHPだけを取得したい」場合でも、以下のようにピンポイントで情報を取得できる。
fetch('https://exmaple.com/graphql',{method:'POST',headers:{'Content-Type':'application/json','Accept':'application/json',},body:JSON.stringify({query:` { monster(id: 123) { hp } }`})}).then(response=> response.json()).then(data=>console.log(data));RESTとGraphQLでは、「クエリの柔軟性」という観点でも大きな違いがある。
まず、RESTの例を見てみよう。例えば、「特定のモンスターの情報と、モンスターがドロップするアイテムの情報を取得する」場合を考える。これをRESTで表現するには以下のように、2つのリクエストを送信することになるだろう。
// NOTE: Node.jsで実行する場合// フェッチライブラリを使用してHTTPリクエストを行うconst fetch=require('node-fetch');// モンスターの情報を取得fetch('https://example.com/monsters/1').then(response=> response.json()).then(warrior=>console.log(warrior)).catch(err=>console.error(err));// モンスターがドロップするアイテムの情報を取得fetch('https://example.com/warriors/1/items').then(response=> response.json()).then(weapons=>console.log(weapons)).catch(err=>console.error(err));上述のコードは、2つのHTTPリクエストを非同期的に行い、それぞれのレスポンスを処理している。
一方で、GraphQLではこれらの情報を一度のリクエストで処理できる。
// NOTE: Node.jsで実行する場合// GraphQL: Apolloクライアントを使用してGraphQLクエリを送信するconst{ApolloClient,InMemoryCache, gql}=require('@apollo/client');const client=newApolloClient({uri:'https://api.example.com/graphql',cache:newInMemoryCache()});// モンスターとドロップするアイテムを同時に取得client.query({query: gql`{monster(id:1){namelevelitems{nameeffect}}}`}).then(data=>console.log(data)).catch(err=>console.error(err));上述のGraphQLのクエリは、モンスターの名前とレベル、そしてモンスターがドロップするアイテムを一回のリクエストで取得している。上述の例からわかるように、GraphQLはRESTとは違って一回のリクエストで複数のリソースを取得できるのだ。
RESTとGraphQLではエラーハンドリングの方法にも違いがある。
RESTの場合、HTTPステータスコードでエラーの種類を示す。
fetch('https://example.com/monsters/9999').then(response=>{if(!response.ok){thrownewError(`HTTP error! status:${response.status}`);}return response.json();}).then(warrior=>console.log(warrior)).catch(err=>console.error(err));上述のコードでは、もしHTTPステータスコードが200以外であればエラーを出力する。
一方で、GraphQLでは常にHTTPステータスコード200を返す。かわりに、エラーの情報はレスポンスのerrorフィールドに含まれる。
// NOTE: Node.jsで動かす場合const{ApolloClient,InMemoryCache, gql}=require('@apollo/client');const client=newApolloClient({uri:'https://api.example.com/graphql',cache:newInMemoryCache()});client.query({query: gql`{warrior(id:9999){name}}`}).then(response=>{if(response.errors){ response.errors.forEach(error=>console.error(error.message));}else{console.log(response.data);}}).catch(err=>console.error(err));上述のコードでは、レスポンスにerrorフィールドが存在するかどうかを確認している。もしエラーがあればそれぞれのエラーメッセージを出力する。エラーがなければデータを表示する。
このように、RESTとGraphQLではエラーハンドリングの方法に大きな差がある。これらの違いを理解することはエラーを適切に対応する上で重要だ。
!注意:なお、このGraphQLのエラーハンドリングの仕組みは、サーバ側での実装や設定で若干異なる場合がある。さらに、クライアント側で使うライブラリ(上述のコードの例ではApolloClient)でもエラーハンドリングの詳細は異なる。
ここまでRESTとGraphQL両方の長所や短所、具体的な違いをコードベースで解説した。RESTとGraphQLはどちらを選ぶべきか迷っているひとも少なくないだろうか。
これに対する私の回答は、「両方とも学ぶのが理想だが、RESTは必ず学ぶべき」である。理由は、多くの一般的なWebサービス、特にTwitter等の大規模なWebサービスでは以下のような理由でREST APIが活用されているからだ。
GET、POST、PUT、DELETE等)を直接利用する。故に、HTTPの特徴を最大限に活かせる。以上の点を踏まえると、これからAPI設計・開発で生計を立てたいなら最優先でRESTを学ぶべきだと断言できる。
前のパートではRESTを最優先して学ぶべき理由を詳細に解説した。今度はGraphQLを学ぶべき理由を解説する。
今回はWeb APIの開発で重宝されるRESTとGraphQL、両者の違いをコードベースで徹底解説した。両者の違いを簡潔に表でまとめると以下のようになる。
| 項目 | REST | GraphQL |
|---|---|---|
| データ取得 | URLに接続 | 1回のリクエストで複数のリソースからデータを要求できる |
| 構造 | シンプル | 複雑 |
| データの取り扱いかた | CRUD | Query、Mutation |
| 最大の長所 | シンプルさ、HTTPとの親和性 | 取得できるデータを任意で定義できる |
| 最大の短所 | データを過剰に取得する | HTTPとの相性が悪い |
| 技術の取得 | HTTPとの相性が抜群で、クライアントとサーバの理解と密接に関連しているので比較的簡単 | 独特の概念が登場するのでRESTより難易度が高め |
| データ形式 | JSON、XML | JSON |
今回の記事が、あなたの今後のWeb API設計・開発の技術選定で参考になれば幸いである。
https://ja.wikipedia.org/wiki/GraphQL
https://www.redhat.com/en/topics/api/what-is-graphql
https://blog.logrocket.com/graphql-vs-rest-api-why-you-shouldnt-use-graphql/
https://www.apollographql.com/blog/graphql/basics/graphql-vs-rest/
バッジを受け取った著者にはZennから現金やAmazonギフトカードが還元されます。
