Node.jsでルーティング処理を作成する方法

Node.jsのネイティブHTTPモジュールを用いてルーティング処理を作成する方法を紹介します。コードもGit Hubに掲載するので参考にしてください。

概要

Node.jsのネイティブHTTPモジュールを使用して、簡単にルーティングを使用する方法を紹介します。
ルーティングは、HTTPリクエストを処理するためのエンドポイントを指定することで定義されます。

以下では、ルーティングの概念、ルートの定義、ルートハンドラの作成、ルーティングパラメータの処理、およびルートのオプションについて詳しく説明します。

ここでは、以下バージョンを使用した、ルーティング方法を説明します。

nodejs v19.7.0

また、今回作成するコードは全て、GitHubに掲載しています。

ルーティングを作成する方法

ルーティングは、クライアントからのリクエストが特定のエンドポイントに到達した際に、Node.jsアプリケーションがどのように処理するかを定義します。

ルーティングは、HTTPメソッド(GET、POST、PUT、DELETEなど)とパス(URL)の組み合わせで定義されます。 route.js

const http = require('http');
const url = require('url');

const server = http.createServer((req, res) => {
  const parsedUrl = url.parse(req.url, true);
  const path = parsedUrl.pathname;
  const trimmedPath = path.replace(/^\/+|\/+$/g, '');
  
  const chosenHandler = typeof(router[trimmedPath]) !== 'undefined' ? router[trimmedPath] : handlers.notFound;

  chosenHandler((statusCode, payload) => {
    res.writeHead(statusCode);
    res.end(JSON.stringify(payload));
  });
});

server.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました');
});

const handlers = {
  sample: (callback) => {
    callback(200, {name: 'sample handler'});
  },
  notFound: (callback) => {
    callback(404);
  },
};

const router = {
  'sample': handlers.sample,
  "notFound": handlers.notFound
};

解説

  • HTTPとURLモジュールのインポート
const http = require('http');
const url = require('url');

このコードでは、HTTPサーバー機能を提供する’http’モジュールと、URLの解析機能を提供する’url’モジュールをインポートしています。

  • HTTPサーバーの作成と起動
const server = http.createServer((req, res) => {...});

server.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました');
});

ここでは、‘http’モジュールのcreateServerメソッドを用いてHTTPサーバーを作成し、3000番ポートで起動しています。

  • リクエストURLの解析
const parsedUrl = url.parse(req.url, true);
const path = parsedUrl.pathname;
const trimmedPath = path.replace(/^\/+|\/+$/g, '');

サーバーがリクエストを受信すると、リクエストURLを解析し、先頭と末尾のスラッシュを削除してパスを取得します。

  • ハンドラーの選択
const chosenHandler = typeof(router[trimmedPath]) !== 'undefined' ? router[trimmedPath] : handlers.notFound;

解析したパスに基づいて、適切なハンドラー関数を選択します。パスに対応するハンドラーがrouterオブジェクト内に存在しない場合、デフォルトの’notFound’ハンドラーを選択します。

  • ハンドラーの実行とレスポンスの送信
chosenHandler((statusCode, payload) => {
  res.writeHead(statusCode);
  res.end(JSON.stringify(payload));
});

選択したハンドラーを実行し、その結果をHTTPレスポンスとしてクライアントに返します。ハンドラー関数はステータスコードとペイロードを引数とするコールバック関数を呼び出します。

  • ハンドラー関数とルーターオブジェクトの定義
const handlers = {
  sample: (callback) => {
    callback(200, {name: 'sample handler'});
  },
  notFound: (callback) => {
    callback(404);
  },
};

const router = {
  'sample': handlers.sample,
  "notFound": handlers.notFound
};

ここでは、各リクエストパスに対応するハンドラー関数を定義しています。また、それらのハンドラー関数をパスと関連付けるルーターオブジェクトも定義しています。

テスト

# GETリクエストのテスト
curl -X GET http://localhost:3000/sample
curl -X GET http://localhost:3000/notfound

各種APIメソッドを使用する方法

api.js

const http = require('http');
const url = require('url');

const server = http.createServer((req, res) => {
  const parsedUrl = url.parse(req.url, true);
  const path = parsedUrl.pathname;
  const trimmedPath = path.replace(/^\/+|\/+$/g, '');

  const chosenHandler = typeof (router[req.method][trimmedPath]) !== 'undefined' ? router[req.method][trimmedPath] : handlers.notFound;

  collectRequestData(req, result => {
    chosenHandler(result, (statusCode, payload) => {
      res.writeHead(statusCode);
      res.end(JSON.stringify(payload));
    });
  });

});

function collectRequestData(request, callback) {
  const FORM_URLENCODED = 'application/json';
  if (request.headers['content-type'] === FORM_URLENCODED) {
    let body = '';
    request.on('data', chunk => {
      body += chunk.toString();
    });
    request.on('end', () => {
      callback(JSON.parse(body));
    });
  }
  else {
    callback(null);
  }
}

server.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました');
});

const handlers = {
  getUsers: (data, callback) => {
    callback(200, { name: 'get users handler' });
  },
  postUser: (data, callback) => {
    callback(200, { name: 'post user handler', data: data });
  },
  putUser: (data, callback) => {
    callback(200, { name: 'put user handler', data: data });
  },
  deleteUser: (data, callback) => {
    callback(200, { name: 'delete user handler' });
  },
  notFound: (data, callback) => {
    callback(404);
  },
};

const router = {
  'GET': {
    'users': handlers.getUsers,
  },
  'POST': {
    'users': handlers.postUser,
  },
  'PUT': {
    'users': handlers.putUser,
  },
  'DELETE': {
    'users': handlers.deleteUser,
  }
};

解説

このコードは、Node.jsを用いてHTTPサーバーを立て、特定のエンドポイントとHTTPメソッドに対してのリクエストを処理する簡易的なWeb APIを実装しています。具体的には、GET/POST/PUT/DELETEメソッドを’users’パスに対して受け取り、適切なハンドラーで処理します。コードの各部分について以下に詳しく説明します。

  • 必要なモジュールのインポート
const http = require('http');
const url = require('url');

httpurlという二つの内蔵モジュールをインポートしています。httpはHTTPサーバーを立てるために、urlはリクエストのURLを解析するために使用します。

  • HTTPサーバーの作成
const server = http.createServer((req, res) => {
  // 以下に続く...
});
server.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました');
});

http.createServerメソッドを用いてHTTPサーバーを作成し、3000番ポートでリクエストを待ち受けます。クライアントからの各リクエストは、createServerに渡されたコールバック関数で処理されます。

  • リクエストパスの解析
const parsedUrl = url.parse(req.url, true);
const path = parsedUrl.pathname;
const trimmedPath = path.replace(/^\/+|\/+$/g, '');

受け取ったリクエストのURLを解析し、それが指し示すパスを取得しています。また、パスの前後のスラッシュを削除して、ルーティング処理を容易にしています。

  • ルーティング
const chosenHandler = typeof (router[req.method][trimmedPath]) !== 'undefined' ? router[req.method][trimmedPath] : handlers.notFound;

リクエストのHTTPメソッドとパスに基づいて適切なハンドラーを選択します。存在しないパスまたはメソッドに対するリクエストは、404エラーを返すnotFoundハンドラーが選択されます。

  • リクエストデータの収集
collectRequestData(req, result => {
  chosenHandler(result, (statusCode, payload) => {
    res.writeHead(statusCode);
    res.end(JSON.stringify(payload));
  });
});

collectRequestData関数は、リクエストボディからデータを読み取ります。読み取ったデータはchosenHandlerに渡され、リクエストの処理結果をクライアントに返します。

  • ハンドラーとルーターの定義
const handlers = {
  getUsers: (data, callback) => {
    callback(200, { name: 'get users handler' });
  },
  // 以下に続く...
};

const router = {
  'GET': {
    'users': handlers.getUsers,
  },
  // 以下に続く...
};

‘users’パスに対するGET/POST/PUT/DELETEメソッドの各ハンドラーが定義されています。各ハンドラーは、リクエストデータと一緒にコールバック関数を受け取り、そのコールバックを呼び出すことでレスポンスを返します。また、各ハンドラーはrouterオブジェクトにHTTPメソッドとパスとして登録されています。

テスト

## ユーザーの取得
curl -X GET http://localhost:3000/users
# ユーザーの作成
curl -X POST -H "Content-Type: application/json" -d '{"id":"1","name":"John Doe"}' http://localhost:3000/users
# ユーザーの更新
curl -X PUT -H "Content-Type: application/json" -d '{"id":"1","name":"John Smith"}' http://localhost:3000/users
# ユーザーの削除
curl -X DELETE -H "Content-Type: application/json" -d '{"id":"1"}' http://localhost:3000/users

まとめ

今回の記事では、ルーティングについて学びました。ルーティングは、リクエストのパスとHTTPメソッドに基づいて、適切なハンドラーを選択する処理です。ルーティングを実装することで、Web APIの実装が容易になります。次回は、ルーティングを用いて簡易的なWeb APIを実装します。