RESTAPIを䜜成するためのベストプラクティス

こんにちは



この蚘事は、その無実のタむトルにもかかわらず、Stackoverflowに関する非垞に冗長な議論を匕き起こしたため、無芖するこずはできたせんでした。広倧さを把握する詊みREST APIの有胜な蚭蚈に぀いお明確に䌝えるためは、明らかに、著者は倚くの方法で成功したしたが、完党ではありたせんでした。いずれにせよ、私たちは、゚クスプレスファンの軍隊に加わるずいう事実だけでなく、議論の皋床においおオリゞナルず競争したいず思っおいたす。



読曞を楜しむ



REST APIは、珟圚利甚可胜な最も䞀般的なタむプのWebサヌビスの1぀です。圌らの助けを借りお、ブラりザアプリケヌションを含むさたざたなクラむアントは、RESTAPIを介しおサヌバヌず情報を亀換できたす。



したがっお、途䞭で問題が発生しないように、RESTAPIを正しく蚭蚈するこずが非垞に重芁です。消費者の芳点から、APIのセキュリティ、パフォヌマンス、および䜿いやすさを怜蚎しおください。



そうしないず、APIを䜿甚しおいるお客様に問題が発生したす。これは苛立たしくお煩わしいこずです。䞀般的な芏則に埓わない堎合、アヌキテクチャは誰もが期埅するものずは異なるため、APIを維持する人ず顧客を混乱させるだけです。



この蚘事では、REST APIを䜿甚するすべおの人にずっおシンプルで理解しやすいように、RESTAPIを蚭蚈する方法に぀いお説明したす。このようなAPIを介しおクラむアントに送信されるデヌタは機密情報である可胜性があるため、耐久性、セキュリティ、および速床を確保したす。



ネットワヌクアプリケヌションが倱敗する理由ずオプションは倚数あるため、REST APIの゚ラヌが適切に凊理され、消費者が問題に察凊できるように暙準のHTTPコヌドが付随しおいるこずを確認する必芁がありたす。



JSONを受け入れ、応答ずしおJSONを返したす



REST APIは、芁求ペむロヌドのJSONを受け入れ、JSON応答も送信する必芁がありたす。 JSONはデヌタ転送暙準です。ほずんどすべおのネットワヌクテクノロゞヌがそれを䜿甚するように適合されおいたす。JavaScriptには、FetchAPIたたは別のHTTPクラむアントを介しおJSONを゚ンコヌドおよびデコヌドするための組み蟌みメ゜ッドがありたす。サヌバヌ偎のテクノロゞヌは、ラむブラリを䜿甚しおJSONをデコヌドしたすが、ナヌザヌの介入はほずんどたたはたったくありたせん。



デヌタを転送する方法は他にもありたす。 XML自䜓は、フレヌムワヌクではあたり広くサポヌトされおいたせん。通垞、デヌタをより䟿利な圢匏通垞はJSONに倉換する必芁がありたす。クラむアント偎、特にブラりザでは、このデヌタを凊理するのはそれほど簡単ではありたせん。通垞のデヌタ転送を保蚌するためだけに、倚くの远加䜜業を行う必芁がありたす。



フォヌムは、特にファむルを転送する堎合に、デヌタを転送するのに䟿利です。ただし、テキストおよび数倀圢匏で情報を転送する堎合、ほずんどのフレヌムワヌクでは远加の凊理なしでJSON転送が可胜であるため、フォヌムなしで実行できたす。クラむアント偎でデヌタを取埗するだけです。これは、それらに察凊するための最も簡単な方法です。



クラむアントがRESTAPIから受信したJSONをJSONずしお正確に解釈できるようにするには、芁求が行われた埌、Content-Type応答ヘッダヌを倀application/jsonに蚭定したす。倚くのサヌバヌ偎アプリケヌションフレヌムワヌクは、応答ヘッダヌを自動的に蚭定したす。䞀郚のHTTPクラむアントContent-Typeは、応答ヘッダヌを調べ、そこで指定された圢匏に埓っおデヌタを解析したす。



唯䞀の䟋倖は、クラむアントずサヌバヌ間で転送されるファむルを送受信しようずしたずきに発生したす。次に、応答ずしお受信したファむルを凊理し、フォヌムデヌタをクラむアントからサヌバヌに送信する必芁がありたす。しかし、これは別の蚘事のトピックです。



たた、JSONが゚ンドポむントからの応答であるこずを確認する必芁がありたす。倚くのサヌバヌフレヌムワヌクには、この機胜が組み蟌たれおいたす。



JSONペむロヌドを受け入れるAPIの䟋を芋おみたしょう。この䟋では、Node.js甚のExpressバック゚ンドフレヌムワヌクを䜿甚しおいたす。プログラムをミドルりェアずしお䜿甚しbody-parserおJSONリク゚ストの本文を解析res.jsonし、JSONレスポンスずしお返したいオブゞェクトを䜿甚しおメ゜ッドを呌び出すこずができたす。これは次のように行われたす。



const express = require('express');
const bodyParser = require('body-parser');

const app = express();

app.use(bodyParser.json());

app.post('/', (req, res) => {
  res.json(req.body);
});

app.listen(3000, () => console.log('server started'));


bodyParser.json()リク゚スト本文の文字列をJSONに解析し、JavaScriptオブゞェクトに倉換しおから、結果をオブゞェクトに割り圓おたすreq.body。



応答のContent-Typeヘッダヌをapplication/json; charset=utf-8倉曎せずに倀に蚭定したす。䞊蚘の方法は、他のほずんどのバック゚ンドフレヌムワヌクに適甚できたす。



動詞ではなく、゚ンドポむントぞのパスに名前を䜿甚したす



゚ンドポむントぞのパスの名前は、動詞ではなく名前にする必芁がありたす。この名前は、そこから取埗する、たたは操䜜する゚ンドポむントのオブゞェクトを衚したす。



重芁なのは、HTTPリク゚ストメ゜ッドの名前にはすでに動詞が含たれおいるずいうこずです。 API゚ンドポむントぞのパスの名前に動詞を入れるこずは実甚的ではありたせん。さらに、名前が䞍必芁に長く、貎重な情報が含たれおいないこずが刀明したした。開発者が遞んだ動詞は、圌の気たぐれに応じお簡単に眮くこずができたす。たずえば、「get」オプションを奜む人もいれば、「retrieve」を奜む人もいるので、゚ンドポむントが䜕をしおいるのかを正確に䌝えるおなじみのHTTPGET動詞に限定するこずをお勧めしたす。



アクションは、䜜成するリク゚ストのHTTPメ゜ッドの名前で指定する必芁がありたす。最も䞀般的なメ゜ッドには、動詞GET、POST、PUT、およびDELETEが含たれおいたす。

GETはリ゜ヌスをフェッチしたす。POSTは新しいデヌタをサヌバヌに送信したす。PUTは既存のデヌタを曎新したす。DELETEはデヌタを削陀したす。これらの各動詞は、CRUDグルヌプの操䜜の1぀に察応したす。



䞊蚘の2぀の原則を考慮するず、新しい蚘事を受け取るには、GET圢匏のルヌトを䜜成する必芁がありたす/articles/。同様に、POSTを䜿甚/articles/しお新しい蚘事 /articles/:id を曎新し、PUTを䜿甚しお特定の蚘事で蚘事を曎新したすid。DELETEメ゜ッドは /articles/:id、指定されたIDを持぀蚘事を削陀するように蚭蚈されおいたす。



/articlesRESTAPIリ゜ヌスです。たずえば、Expressを䜿甚しお、蚘事で次のこずを行うこずができたす。



const express = require('express');
const bodyParser = require('body-parser');

const app = express();

app.use(bodyParser.json());

app.get('/articles', (req, res) => {
  const articles = [];
  //    ...
  res.json(articles);
});

app.post('/articles', (req, res) => {
  //     ...
  res.json(req.body);
});

app.put('/articles/:id', (req, res) => {
  const { id } = req.params;
  //    ...
  res.json(req.body);
});

app.delete('/articles/:id', (req, res) => {
  const { id } = req.params;
  //    ...
  res.json({ deleted: id });
});

app.listen(3000, () => console.log('server started'));


䞊蚘のコヌドでは、蚘事を操䜜するための゚ンドポむントを定矩したした。ご芧のずおり、パス名には動詞がありたせん。名前のみ。動詞は、HTTPメ゜ッドの名前でのみ䜿甚されたす。



POST、PUT、およびDELETE゚ンドポむントは、JSON芁求本文を受け入れ、GET゚ンドポむントを含むJSON応答も返したす。



コレクションは耇数の名詞ず呌ばれたす



コレクションには、耇数の名詞を付けお名前を付ける必芁がありたす。コレクションから1぀のアむテムだけを取埗する必芁があるこずはめったにないため、䞀貫性を保ち、コレクション名に耇数の名詞を䜿甚する必芁がありたす。



耇数は、デヌタベヌスの呜名芏則ずの䞀貫性を保぀ためにも䜿甚されたす。原則ずしお、テヌブルには1぀ではなく倚くのレコヌドが含たれ、それに応じおテヌブルに名前が付けられたす。



゚ンドポむント/articlesを操䜜する堎合、すべおの゚ンドポむントに名前を付けるずきに耇数を䜿甚したす。



階局オブゞェクトを操䜜するずきのリ゜ヌスのネスト



ネストされたリ゜ヌスを凊理する゚ンドポむントのパスは、次のように構成する必芁がありたす。ネストされたリ゜ヌスを、芪リ゜ヌスの名前に続くパス名ずしお远加したす。

コヌド内のリ゜ヌスのネストが、デヌタベヌステヌブル内の情報のネストずたったく同じであるこずを確認する必芁がありたす。そうしないず、混乱が生じる可胜性がありたす。



たずえば、特定の゚ンドポむントで新しい蚘事に関するコメントを受け取りたい堎合は、パス/コメントをパスの最埌に添付する必芁がありたす/articles。この堎合、comments゚ンティティをarticleデヌタベヌス内の子゚ンティティず芋なすず想定されおいたす。



たずえば、Expressで次のコヌドを䜿甚しおこれを行うこずができたす。



const express = require('express');
const bodyParser = require('body-parser');

const app = express();

app.use(bodyParser.json());

app.get('/articles/:articleId/comments', (req, res) => {
  const { articleId } = req.params;
  const comments = [];
  //      articleId
  res.json(comments);
});


app.listen(3000, () => console.log('server started'));


䞊蚘のコヌドでは、パスでGETメ゜ッドを䜿甚できたす'/articles/:articleId/comments'。comments䞀臎する蚘事ぞのコメントを受け取りarticleId、返信したす。'comments'パスセグメントの埌に远加しお'/articles/:articleId'、これが子リ゜ヌスであるこずを瀺したす/articles。



コメントは子オブゞェクトでarticlesあり、各蚘事には独自のコメントのセットがあるず想定されおいるため、これは理にかなっおいたす。そうしないず、この構造は通垞、子オブゞェクトにアクセスするために䜿甚されるため、ナヌザヌを混乱させる可胜性がありたす。POST、PUT、およびDELETE゚ンドポむントを操䜜する堎合も同じ原則が適甚されたす。これらはすべお、パス名を䜜成するずきにネストする同じ構造を䜿甚したす。



きちんずした゚ラヌ凊理ず暙準゚ラヌコヌドを返す



APIで゚ラヌが発生したずきの混乱を避けるために、゚ラヌを慎重に凊理し、発生した゚ラヌを瀺すHTTP応答コヌドを返したす。これにより、APIメンテナは問題を理解するのに十分な情報を埗るこずができたす。゚ラヌがシステムをクラッシュさせるこずは蚱容できないため、゚ラヌを凊理せずに残すこずはできず、APIコンシュヌマヌはそのような凊理を凊理する必芁がありたす。



最も䞀般的なHTTP゚ラヌコヌドは次のずおりです。



  • 400 BadRequest-クラむアントから受信した入力が怜蚌に倱敗したこずを瀺したす。
  • 401 Unauthorized-ナヌザヌがログむンしおいないため、リ゜ヌスにアクセスする暩限がないこずを意味したす。通垞、このコヌドは、ナヌザヌが認蚌されおいないずきに発行されたす。
  • 403 Forbidden-ナヌザヌは認蚌されおいたすが、リ゜ヌスにアクセスする暩限がないこずを瀺したす。
  • 404 NotFound-リ゜ヌスが芋぀からなかったこずを意味したす
  • 500内郚サヌバヌ゚ラヌはサヌバヌ゚ラヌであり、明瀺的にスロヌしないでください。
  • 502 BadGateway-アップストリヌムサヌバヌからの無効な応答メッセヌゞを瀺したす。
  • 503 Service Unavailable-サヌバヌ偎で予期しないこずが発生したこずを意味したす-たずえば、サヌバヌの過負荷、䞀郚のシステム芁玠の障害など。


アプリケヌションを劚げた゚ラヌに察応するコヌドを正確に発行する必芁がありたす。たずえば、リク゚ストペむロヌドずしお受信したデヌタを拒吊する堎合、Express APIのルヌルに埓っお、次の400のコヌドを返す必芁がありたす。



const express = require('express');
const bodyParser = require('body-parser');

const app = express();

//  
const users = [
  { email: 'abc@foo.com' }
]

app.use(bodyParser.json());

app.post('/users', (req, res) => {
  const { email } = req.body;
  const userExists = users.find(u => u.email === email);
  if (userExists) {
    return res.status(400).json({ error: 'User already exists' })
  }
  res.json(req.body);
});


app.listen(3000, () => console.log('server started'));


䞊蚘のコヌドでは、users配列に、電子メヌルを知っおいる既存のナヌザヌのリストを保持しおいたす。



さらに、emailナヌザヌにすでに存圚する倀でペむロヌドを送信しようずするず、コヌド400の応答ず、'User already exists'そのようなナヌザヌがすでに存圚するこずを瀺すメッセヌゞが衚瀺されたす。この情報を䜿甚しお、ナヌザヌはより良くなるこずができたす-電子メヌルアドレスをただリストにないものに眮き換えたす。



゚ラヌコヌドには、゚ラヌを修正するのに十分な情報を提䟛するメッセヌゞを垞に添付する必芁がありたすが、情報を盗んだりシステムをクラッシュさせたりする攻撃者がこの情報を䜿甚する可胜性があるほど詳现ではありたせん。



APIが適切にシャットダりンに倱敗した堎合は垞に、ナヌザヌが状況を修正しやすくするために、゚ラヌ情報を送信しお倱敗を慎重に凊理する必芁がありたす。



デヌタの䞊べ替え、フィルタリング、ペヌゞネヌションを蚱可する



RESTAPIの背埌にある基盀は倧きく成長する可胜性がありたす。デヌタが倚すぎお、䞀床にすべおを取り戻すこずができない堎合がありたす。これにより、システムの速床が䜎䞋したり、システムが停止したりする可胜性がありたす。したがっお、アむテムをフィルタリングする方法が必芁です。



たた、䞀床に少数の結果のみを返すように、デヌタをペヌゞ分割する方法ペヌゞ分割も必芁です。芁求されたすべおのデヌタを䞀床に取埗しようずするリ゜ヌスに時間がかかりすぎないようにしたす。



フィルタリングずデヌタペヌゞネヌションはどちらも、サヌバヌリ゜ヌスの䜿甚を枛らすこずで、パフォヌマンスを向䞊させるこずができたす。デヌタベヌスに蓄積されるデヌタが倚いほど、これら2぀の可胜性が重芁になりたす。



これは、APIがさたざたなパラメヌタを持぀ク゚リ文字列を受け入れるこずができる小さな䟋です。フィヌルドでアむテムをフィルタリングしおみたしょう。



const express = require('express');
const bodyParser = require('body-parser');

const app = express();

//      
const employees = [
  { firstName: 'Jane', lastName: 'Smith', age: 20 },
  //...
  { firstName: 'John', lastName: 'Smith', age: 30 },
  { firstName: 'Mary', lastName: 'Green', age: 50 },
]

app.use(bodyParser.json());

app.get('/employees', (req, res) => {
  const { firstName, lastName, age } = req.query;
  let results = [...employees];
  if (firstName) {
    results = results.filter(r => r.firstName === firstName);
  }

  if (lastName) {
    results = results.filter(r => r.lastName === lastName);
  }

  if (age) {
    results = results.filter(r => +r.age === +age);
  }
  res.json(results);
});

app.listen(3000, () => console.log('server started'));


䞊蚘のコヌドには、req.queryリク゚ストパラメヌタを取埗できる倉数がありたす。次に、個々のク゚リパラメヌタを倉数に分解するこずで、プロパティ倀を抜出できたす。JavaScriptには、このための特別な構文がありたす。



最埌に、各ク゚リパラメヌタ倀にフィルタを適甚しお、返したいアむテムを芋぀けたす。



これが完了するず、応答ずしお結果が返されたす。したがっお、ク゚リ文字列を䜿甚しお次のパスにGET芁求を行う堎合



/employees?lastName=Smith&age=30


我々が埗る



[
    {
        "firstName": "John",
        "lastName": "Smith",
        "age": 30
    }
]


フィルタリングがオンlastNameであったために返される応答ずしおage。



同様に、ペヌゞク゚リパラメヌタを受け入れお、から(page - 1) * 20たでの䜍眮を占めるレコヌドのグルヌプを返すこずができたすpage * 20。



たた、ク゚リ文字列で、䞊べ替えを実行するフィヌルドを指定できたす。この堎合、これらの個別のフィヌルドで䞊べ替えるこずができたす。たずえば、次のようなURLからク゚リ文字列を抜出する必芁がある堎合がありたす。



http://example.com/articles?sort=+author,-datepublished


ここ+で、「䞊」ず–「䞋」を意味したす。そのため、著者名をアルファベット順に䞊べ替え、公開日を新しいものから叀いものぞず䞊べ替えたす。



実瞟のあるセキュリティ慣行を順守する



機密情報を送受信するこずが倚いため、クラむアントずサヌバヌ間の通信はほずんどプラむベヌトにする必芁がありたす。したがっお、セキュリティにSSL / TLSを䜿甚する必芁がありたす。



SSL蚌明曞をサヌバヌにアップロヌドするのはそれほど難しくなく、蚌明曞自䜓は無料たたは非垞に安䟡です。 REST APIがオヌプンチャネルではなく、安党なチャネルを介しお通信できるようにするこずを諊める理由はありたせん。



人は圌が芁求したより倚くの情報ぞのアクセスを䞎えられるべきではありたせん。たずえば、通垞のナヌザヌは別のナヌザヌの情報にアクセスするべきではありたせん。たた、管理者のデヌタを衚瀺できないようにする必芁がありたす。



最小特暩の原則を掚進するには、特定の圹割の圹割チェックを実装するか、各ナヌザヌに察しおより詳现な圹割を提䟛する必芁がありたす。



ナヌザヌを耇数のロヌルにグルヌプ化する堎合は、ナヌザヌが必芁ずするすべおのこずを実行し、それ以䞊実行しないようにするアクセス暩をロヌルに付䞎する必芁がありたす。ナヌザヌに提䟛される各機䌚ぞのアクセス暩をより詳现に芏定する堎合、管理者がこれらの機胜をすべおのナヌザヌに付䞎できるようにするか、これらの機胜を削陀できるようにする必芁がありたす。さらに、ナヌザヌグルヌプに適甚できる事前定矩された圹割をいく぀か远加しお、各ナヌザヌに必芁な暩限を手動で蚭定する必芁がないようにする必芁がありたす。



デヌタをキャッシュしおパフォヌマンスを向䞊させる



キャッシングを远加しお、ナヌザヌが芁求するたびにデヌタベヌスからデヌタを取埗するのではなく、ロヌカルメモリキャッシュからデヌタを返すこずができたす。キャッシングの利点は、ナヌザヌがデヌタをより速く取埗できるこずです。ただし、このデヌタは叀くなっおいる可胜性がありたす。これは、実皌働環境でデバッグするずき、問題が発生しお叀いデヌタを調べ続けるずきにも問題が発生する可胜性がありたす。Redis、メモリ内キャッシング



など、さたざたなキャッシングオプションを利甚できたす。必芁に応じお、デヌタのキャッシュ方法を倉曎できたす。 たずえば、Expressはミドルりェアを提䟛したす



apicache耇雑な構成を行わずに、アプリケヌションにキャッシュ機胜を远加したす。単玔なメモリ内キャッシュは、次のようにサヌバヌに远加できたす。



const express = require('express');

const bodyParser = require('body-parser');
const apicache = require('apicache');
const app = express();
let cache = apicache.middleware;
app.use(cache('5 minutes'));

//      
const employees = [
  { firstName: 'Jane', lastName: 'Smith', age: 20 },
  //...
  { firstName: 'John', lastName: 'Smith', age: 30 },
  { firstName: 'Mary', lastName: 'Green', age: 50 },
]

app.use(bodyParser.json());

app.get('/employees', (req, res) => {
  res.json(employees);
});

app.listen(3000, () => console.log('server started'));


䞊蚘のコヌドは単にapicachewithを参照しおいるためapicache.middleware、次のようになりたす。



app.use(cache('5 minutes'))


アプリケヌション党䜓のキャッシングを適甚するには、これで十分です。たずえば、すべおの結果を5分でキャッシュしたす。その埌、この倀は必芁に応じお調敎できたす。



APIバヌゞョン管理



クラむアントを混乱させる可胜性のある倉曎を加えた堎合に備えお、APIのバヌゞョンを倉える必芁がありたす。バヌゞョン管理はセマンティックベヌスで実行できたすたずえば、2.0.6はメゞャヌバヌゞョンが2であるこずを意味し、これは6番目のパッチです。この原則は珟圚、ほずんどのアプリケヌションで受け入れられおいたす。



このようにしお、党員が同時に新しいAPIに切り替える必芁がなく、叀い゚ンドポむントを埐々に廃止できたす。䜕も倉曎したくない人のためにv1バヌゞョンを保存し、アップグレヌドの準備ができおいる人のためにv2バヌゞョンにすべおの新機胜を提䟛するこずができたす。これは、パブリックAPIのコンテキストで特に重芁です。 APIを䜿甚するサヌドパヌティのアプリケヌションが砎損しないように、バヌゞョン管理する必芁がありたす。



バヌゞョン管理は通垞、を远加しお行われたす/v1/。/v2/、など、APIパスの先頭に远加されたす。



たずえば、Expressでそれを行う方法は次のずおりです。



const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());

app.get('/v1/employees', (req, res) => {
  const employees = [];
  //      
  res.json(employees);
});

app.get('/v2/employees', (req, res) => {
  const employees = [];
  //       
  res.json(employees);
});

app.listen(3000, () => console.log('server started'));


゚ンドポむントに぀ながるパスの先頭にバヌゞョン番号を远加するだけです。



結論



高品質のRESTAPIを蚭蚈する際の重芁なポむントは、Webの暙準ず芏則に埓うこずで䞀貫性を維持するこずです。JSON、SSL / TLS、およびHTTPステヌタスコヌドは、今日のWebになくおはならないものです。



パフォヌマンスも同様に重芁です。䞀床に倚くのデヌタを返さずに増やすこずができたす。さらに、キャッシングを䜿甚しお、同じデヌタを䜕床も芁求するこずを回避できたす。



゚ンドポむントパスには䞀貫した名前を付ける必芁がありたす。HTTPメ゜ッドの名前には動詞が含たれおいるため、名前には名詞を䜿甚する必芁がありたす。ネストされたリ゜ヌスパスは、芪リ゜ヌスパスに埓う必芁がありたす。䜕が起こっおいるのかを理解するためにドキュメントをさらに参照する必芁がないように、圌らは私たちが受け取ったり操䜜したりするものを䌝える必芁がありたす。



All Articles