TypeScriptでRouterを分割する方法と型の共通化!Node.jsとExpressによるバックエンドWEB開発初心者向け完全解説
生徒
「TypeScriptとNode.js、Expressを使ってバックエンドのWEBアプリ開発の勉強を始めました。機能が増えてコードが長くなってきたので、プログラムのファイルを別々に分けたいのですが、どのように分割すれば良いでしょうか?」
先生
「TypeScriptやExpressのWEB開発では、Routerという仕組みを使ってURLの経路ごとの処理を別のファイルに分割するのが一般的な設計方法です。ファイルを分けることで、プログラムの整理整頓ができて読みやすくなりますよ。」
生徒
「なるほど。ファイルを分けたとき、TypeScriptの特徴である型定義を複数のファイルで一緒に使い回す共通化の方法も知りたいです。」
先生
「それは素晴らしい視点ですね。Routerの分割方法とデータの型定義を共通化して管理するテクニックは、実際のWEBシステム開発現場でも必須の基本スキルです。パソコンをあまり触ったことがない初心者の方でも仕組みがハッキリと理解できるように、具体的なプログラムの例を出しながら基本から丁寧に分かりやすく解説していきますね!」
1. バックエンド開発におけるRouterの役割とは
まずはバックエンド開発という言葉について説明します。バックエンドとは、インターネットのWEBサイトやスマートフォンアプリの裏側で動いている、データの処理や保存を行う仕組みのことです。画面に見えない部分のプログラム全般を指します。このバックエンドをJavaScriptの仲間であるTypeScriptと、サーバーを動かす仕組みであるNode.js、そしてWEBアプリの土台となるフレームワークのExpressを組み合わせて開発するのが、現在のWEB業界で非常に人気のある開発手法です。フレームワークとは、WEBアプリを簡単に作るための便利な道具箱のようなものです。
このExpressの中でとても重要な部品がRouter(ルーター)です。ルーターとは、日本語にすると「道案内」という意味になります。WEBブラウザから「このページを表示してほしい」「このデータを保存してほしい」という要求が届いたときに、その要求に対応した正しい処理の部屋へ案内する役割を持っています。この案内作業のことをルーティングと呼びます。例えば、ユーザー登録のページへの案内、商品の詳細画面への案内など、それぞれのURLに応じて適切なプログラムを動かすためにルーターが活躍します。
2. なぜファイルを分割して整理する必要があるのか
プログラミングを始めたばかりの段階では、すべてのプログラムを一箇所にまとめて一つのファイルに書いてしまいがちです。しかし、WEBアプリケーションの規模が大きくなり、機能が充実してくると、案内するべきURLの数が数十個、数百個と増えていきます。それを一つのファイルに全部書いてしまうと、何千行もの巨大なプログラムになってしまいます。そうなると、どこに何の処理が書いてあるのかを探すだけでも一苦労ですし、一箇所を書き直したときに別の場所が壊れてしまう原因にもなります。パソコンのデスクトップ画面がファイルで埋め尽くされていると、必要なファイルが探せなくなるのと同じ状態です。
そこで、機能ごとにファイルを綺麗に小分けにする作業が必要になります。ユーザーに関する道案内はユーザー用のファイルに、商品に関する道案内は商品用のファイルに、というように別々のファイルに役割を分担させます。これをモジュール化やコンポーネント化と呼ぶこともあります。ファイルを適切に分割しておくことで、不具合が発生したときも原因の場所を特定しやすくなりますし、他のプログラマーと一緒に共同で開発を進める際にも作業の衝突を防ぐことができるようになります。
3. 実際のプログラムで見るRouterの分割手順
それでは、実際にExpressを使ったプログラムでルーターを分割する具体的な手順を見ていきましょう。ここでは、WEBアプリの土台となる中心のファイルと、ユーザー情報を扱うための専用の案内ファイルの二つに分ける例を紹介します。まずは、ユーザー専用の案内を行う子どものルーターファイルを作成します。記述する際は、Expressが提供する専用のルーター機能を呼び出して、そこに案内処理を登録していきます。そして、そのファイルを外部から読み込めるように書き出します。
import express from 'express';
// Expressからルーターの機能を新しく作って呼び出します
const userRouter = express.Router();
// ユーザーの一覧を取得するための道案内を設定します
userRouter.get('/list', (req, res) => {
res.send('ユーザーの一覧を表示する処理です');
});
// 外部のファイルでもこのユーザー用ルーターを使えるように書き出します
export default userRouter;
次に、WEBアプリ全体を管理する中心のファイルで、今作ったユーザー専用のルーターファイルを読み込んで合体させます。これにより、全体のプログラムがすっきりと整理されます。中心のファイルでは、全体のアプリを立ち上げて、特定のURLが来たら先ほどの子どもルーターに案内を引き継ぐという設定を行います。以下のコードが、その全体の土台となる親ファイルの記述例です。
import express from 'express';
// 先ほど作成したユーザー用のルーターファイルを読み込みます
import userRouter from './userRouter';
const app = express();
// /user というURLから始まるアクセスが来たら、ユーザー用のルーターに案内を任せます
app.use('/user', userRouter);
// サーバーを起動して、リクエストが来るのを待ち受けます
app.listen(3000, () => {
console.log('サーバーがポート3000で起動しました');
});
このように設定することで、インターネットのブラウザから「/user/list」というURLにアクセスがあった場合、自動的にユーザー用ルーターの処理が呼び出されるようになります。プログラムが役割ごとに分かれていて、非常に見やすくなりました。
4. TypeScriptにおける型の基本と共通化のメリット
ここで、TypeScriptという言語の最大の強みである型(かた)について解説します。型とは、プログラムの中で扱うデータが「数字なのか」「文字なのか」「YESかNOの二択なのか」という情報の種類をあらかじめ厳しく指定しておくルールのことです。JavaScriptという言語ではこのルールが緩いため、文字を入れるべき場所に間違えて数字を入れてしまっても、プログラムを実行するまでエラーに気付けないという問題がありました。しかしTypeScriptを使えば、コードを書いている最中にパソコンが「ここのデータの種類が間違っていますよ」と即座に教えてくれます。これは、スペルミスや勘違いによるプログラムのバグを未然に防ぐための大変強力な味方になります。
そして、複数のファイルにプログラムを分割したときに重要になるのが型の共通化です。例えば、「ユーザーの名前と年齢とID番号」をセットにしたデータの形を定義したとします。このデータの形を、ルーターのファイルでも、データを保存するファイルでも、それぞれ別々に毎回手書きで定義していると、後から「やっぱり電話番号のデータも追加したい」となったときに、すべてのファイルを一つずつ手作業で修正しなければならなくなります。修正を忘れたファイルがあると、そこからプログラムが動かなくなる原因になります。そこで、データの型定義を一箇所にまとめて保管し、必要なファイルがそこから型をダウンロードして使い回す仕組みを作ります。これが型の共通化であり、開発の効率を劇的に向上させるテクニックです。
5. 型定義ファイルを別で作成して共有する方法
実際に型を共通化するためには、型定義専用のファイルを新しく作成します。TypeScriptでは、型を定義するためにinterface(インターフェース)やtype(タイプ)という構文を使用します。これは、データの設計図や名簿のフォーマットのようなものです。今回はユーザー情報の設計図を一枚作成してみましょう。ファイル名は分かりやすいように「types.ts」などにしておきます。外部から読み込めるように、型の名前の前に「export」と記述するのがポイントです。
// ユーザーデータの標準的な形(設計図)を定義して外部に公開します
export interface UserProfile {
id: number; // 識別するための番号(数字)
name: string; // ユーザーのお名前(文字)
age: number; // ユーザーの年齢(数字)
isActive: boolean; // 現在利用中かどうか(真偽値:trueかfalse)
}
この設計図があることで、プログラム内で扱うユーザーデータが必ずこの4つの項目を正しい種類で持っていることを保証できます。項目名が間違っていたり、年齢に文字を入力しようとしたりすると、TypeScriptがすぐにエラーを出して教えてくれるようになります。この専用ファイルを一つ用意しておくことが、大規模なバックエンド開発を安全に進めるための第一歩となります。
6. 共通化した型を複数のRouterで再利用する実例
それでは、先ほど別ファイルに作成した共通の型定義「UserProfile」を、分割したルーターのファイルで実際に読み込んで使ってみましょう。型を読み込むときは、プログラムの先頭で「import」を使用します。これにより、別ファイルで定義した設計図をそのまま現在のファイルに持ってきて適用することができます。以下のプログラムでは、共通の型を使って複数のユーザーデータを安全に作成し、それをWEBブラウザに返却する処理を書いています。
import express from 'express';
// 共通の型定義ファイルからUserProfileという設計図を読み込みます
import { UserProfile } from './types';
const productRouter = express.Router();
// 共通の型を適用したデータを作成します
const sampleUsers: UserProfile[] = [
{ id: 1, name: '山田太郎', age: 25, isActive: true },
{ id: 2, name: '佐藤花子', age: 30, isActive: false }
];
// データを安全に返却するルーティングを設定します
productRouter.get('/premium', (req, res) => {
// 共通の型が適用されているので、データの構造が常に保証されます
res.json(sampleUsers);
});
export default productRouter;
このように、分割されたそれぞれのルーターファイルが同じ「UserProfile」という型を参照することで、システム全体でデータの扱い方にズレがなくなります。もし将来的にユーザー情報に新しい項目が追加されたとしても、修正するのは「types.ts」という型定義ファイルの一箇所だけで済むようになり、変更に強い綺麗なプログラムが完成します。
7. 安全なバックエンド開発を行うための動作確認
プログラムが完成したら、実際に意図した通りに動くかどうかの動作確認を行います。Node.js環境でTypeScriptを実行するためには、プログラムを一度通常のJavaScriptに変換するコンパイルという作業を行うか、または直接TypeScriptを実行できる特別なツールを使用します。無事にサーバーが起動したら、パソコン上のWEBブラウザを起動して、アドレスバーと呼ばれるURLを入力する場所に「http://localhost:3000/user/list」などのURLを入力してアクセスしてみます。画面に自分が設定した文字やデータが正しく表示されれば、ルーターの分割と接続が大成功したことになります。
もし画面にエラーが表示されたり、何も表示されなかったりした場合は、コマンドプロンプトやターミナルと呼ばれる、パソコンに命令を出す黒い画面に出力されているログ(履歴メッセージ)を確認します。TypeScriptの開発では、型に違反していると実行する前の段階で親切なエラーメッセージを出して場所を教えてくれます。そのメッセージを読み解きながら、スペルミスがないか、ファイルの読み込み経路が間違っていないかを一つずつ確認していくことが、プログラミングの上達においてとても大切なプロセスになります。