カテゴリ: TypeScript 更新日: 2026/06/30

TypeScriptでDIコンテナを導入する方法!Expressバックエンド開発の依存性注入入門

TypeScriptでDI(依存性注入)コンテナを導入する方法
TypeScriptでDI(依存性注入)コンテナを導入する方法

先生と生徒の会話形式で理解しよう

生徒

「TypeScriptを使ってNode.jsとExpressでWebサーバーの開発を勉強中なのですが、DIとか依存性注入とか、DIコンテナという言葉が出てきて難しくて困っています。」

先生

「確かに、バックエンド開発の初心者にとって、依存性注入やDIコンテナは最初の大きな壁に感じられますよね。でも、基本的な仕組みを日常生活の例えで考えると、実はとてもシンプルな仕組みなんですよ。」

生徒

「パソコンの操作もプログラミングもまだ慣れていない私でも、本当に理解できるようになりますか?」

先生

「大丈夫です。今回は専門用語を一つずつ分解して、実際にExpressのプログラムにDIコンテナを取り入れる手順まで、丁寧に順番を確認しながら進めていきましょう!」

1. 依存性注入(DI)とは何かを初心者向けに解説

1. 依存性注入(DI)とは何かを初心者向けに解説
1. 依存性注入(DI)とは何かを初心者向けに解説

プログラミングを始めたばかりの方にとって、依存性注入(いぞんせいちゅうにゅう)という言葉は難しく聞こえるかもしれません。英語ではDependency Injectionと呼び、その頭文字を取ってDIと表現されます。まずは、この言葉の意味をパソコンの操作や日常生活の身近な例えを使って分かりやすく説明します。

プログラムの世界でいう「依存」とは、「ある部品が別の部品がないと動かない状態」のことを指します。例えば、あなたがパソコンで文字を入力したいとき、キーボードという部品が必要になります。このとき、「パソコンはキーボードに依存している」と言えます。もしキーボードがパソコン本体と完全に一体化していて取り外せない場合、キーボードが壊れたらパソコンごと買い替えなければいけなくなります。これはプログラミングにおいて、とても不便で管理が大変な状態です。

そこで登場するのが「注入」です。これは、キーボードをパソコン本体に最初から固定するのではなく、USBケーブルなどで「外から差し込む(注入する)」ようにする仕組みのことです。外から差し込む形にすれば、別のキーボードに交換することが簡単にできます。このように、プログラムの部品を中に固定せず、外部から組み立てるように設計する手法を依存性注入(DI)と呼びます。これにより、プログラムの部品をいつでも自由に交換できるようになり、不具合の修正やテストが劇的に楽になります。

2. DIコンテナが必要とされる理由とメリット

2. DIコンテナが必要とされる理由とメリット
2. DIコンテナが必要とされる理由とメリット

依存性注入の考え方は理解できても、プログラムの規模が大きくなって部品の数が数十個、数百個と増えていくと、別の問題が発生します。それは、「外から部品を差し込む作業自体がとても面倒になる」ということです。例えば、ゲーム機を動かすために、コントローラー、テレビ、電源ケーブル、ゲームソフトなど、大量の部品を毎回手作業で順番通りに正しく組み立てて接続しなければならない状態を想像してください。組み立てるだけで疲れてしまいますよね。

この面倒な組み立て作業を、あなたの代わりに自動で行ってくれる魔法の箱のような道具がDIコンテナです。DIコンテナにあらかじめ「この部品とこの部品を組み合わせて使います」という登録の手続きをしておくだけで、プログラムを動かすときに自動で全ての部品を正しく接続して、完成品を準備してくれます。

TypeScriptによるバックエンド開発、特にNode.jsやExpressを使用したサーバー開発では、データベースと通信する部品や、ユーザーの入力を処理する部品など、多くの役割を持った部品が登場します。DIコンテナを導入することで、開発者は部品の組み立て作業から解放され、本来作りたい機能の開発に集中できるようになります。また、プログラムの構造が整理されるため、複数人のチームで開発を行う際にも全体のコードが読みやすくなるという大きなメリットがあります。

3. バックエンド開発におけるNode.jsとExpressの役割

3. バックエンド開発におけるNode.jsとExpressの役割
3. バックエンド開発におけるNode.jsとExpressの役割

ここで、今回使用する環境であるNode.js(ノードジェイエス)Express(エクスプレス)について簡単に整理しておきましょう。プログラミング未経験の方にとって、これらの役割の違いは分かりにくい部分です。

通常、ウェブブラウザでホームページを閲覧する際、裏側ではサーバーと呼ばれるコンピューターが動いています。Node.jsは、本来ブラウザの中でしか動かなかったJavaScriptやTypeScriptというプログラミング言語を、パソコンのシステム上やサーバー上で直接動かせるようにするための土台となる仕組みです。

そして、そのNode.jsという土台の上で、ウェブサーバーの機能を簡単に作れるように用意された道具箱(フレームワーク)がExpressです。Expressを使うことで、ユーザーから「このページを見たい」という要求が届いたときに、どの処理を呼び出すかというルールを短いプログラムで記述できるようになります。今回は、このExpressで作るウェブサーバーの仕組みの中に、DIコンテナを組み込んで、より頑丈で綺麗なプログラムを構築する方法を学んでいきます。

4. 今回の構築に必要なライブラリのインストール手順

4. 今回の構築に必要なライブラリのインストール手順
4. 今回の構築に必要なライブラリのインストール手順

それでは、実際にTypeScriptでDIコンテナを使うための準備を始めましょう。今回は、TypeScriptの世界で非常によく使われているtsyringe(ティーエスインジ)というDIコンテナのライブラリを使用します。パソコンの画面でコマンドを入力する作業が必要になりますが、ゆっくり進めれば大丈夫です。

まず、プロジェクトのフォルダを作成し、必要な部品をパソコンにダウンロードするために、コマンドプロンプトやターミナルと呼ばれる黒い画面に下記の命令を入力して実行します。この作業をインストールと呼びます。


// 必要なライブラリをインストールするコマンドの例です
// tsyringeと一緒に、型定義やリフレクション用のライブラリを入れます
npm install tsyringe reflect-metadata

上記のコマンドを実行することで、DIコンテナの機能本体であるtsyringeと、TypeScriptが部品の情報を正しく読み取るために必要となるreflect-metadataという補助パーツがプロジェクトに追加されます。プログラミングでは、このように世界中の開発者が作った便利な道具を組み合わせて開発を進めていきます。

5. シンプルなクラスを使った基本的なDIの記述方法

5. シンプルなクラスを使った基本的なDIの記述方法
5. シンプルなクラスを使った基本的なDIの記述方法

準備が整いましたので、まずはDIコンテナを動かすための最もシンプルなプログラムを書いてみましょう。ここでは、画面に挨拶のメッセージを表示する「メッセージ送信の部品」を作ります。プログラミングでは、部品の設計図のことをクラス(Class)と呼びます。

まずは、挨拶の文字を作る専用の設計図を用意し、それを別の部品にDIコンテナを使って注入するコードを見てみましょう。ファイルの先頭で、必ず補助パーツの読み込みを行うことがポイントです。


import "reflect-metadata";
import { injectable, container } from "tsyringe";

// 挨拶メッセージを作る部品の設計図
@injectable()
class GreetingService {
    public getHello(): string {
        return "TypeScriptのDIコンテナの世界へようこそ!";
    }
}

// 上記の部品を利用するメインの制御部品
@injectable()
class WelcomeController {
    constructor(private greetingService: GreetingService) {}

    public run(): void {
        const message = this.greetingService.getHello();
        console.log(message);
    }
}

// DIコンテナに自動組み立てを依頼して実行する
const controller = container.resolve(WelcomeController);
controller.run();

コードの中にある@injectable()という見慣れない記号は、デコレーターと呼ばれるものです。これはDIコンテナに対して、「この設計図は、魔法の箱で組み立てができる対象ですよ」と教えてあげるための目印です。そして、最後のほうにあるcontainer.resolveという命令を実行すると、DIコンテナが自動的にGreetingServiceを探し出し、WelcomeControllerの組み立てを完了させて手渡してくれます。手動で接続する作業は一切必要ありません。

このプログラムを実行すると、パソコンの画面に下記の通りの結果が出力されます。


TypeScriptのDIコンテナの世界へようこそ!

6. ExpressにDIコンテナを組み込む実践プログラム

6. ExpressにDIコンテナを組み込む実践プログラム
6. ExpressにDIコンテナを組み込む実践プログラム

基礎が理解できたら、いよいよ実際のバックエンド開発に近づけていきましょう。ウェブサーバーの枠組みであるExpressの中で、DIコンテナを利用するプログラムを記述します。ここでは、インターネット経由でアクセスが来たら、ユーザーの一覧データを返すという仕組みを想定したコードを作ります。

データを管理する部品と、画面の応答を担当する部品を明確に分けて、それらをDIコンテナで結合します。初心者の方でも流れが追えるように、分かりやすい構造にしています。


import "reflect-metadata";
import express from "express";
import { injectable, container } from "tsyringe";

// データベースの代わりにユーザーデータを管理する部品
@injectable()
class UserRepository {
    public getUsers(): string[] {
        return ["太郎", "次郎", "花子"];
    }
}

// ユーザーからの要求を受け取って応答する制御部品
@injectable()
class UserController {
    constructor(private userRepository: UserRepository) {}

    public handleRequest(req: express.Request, res: express.Response): void {
        const users = this.userRepository.getUsers();
        res.json({ success: true, data: users });
    }
}

// Expressのサーバーを設定する
const app = express();
const port = 3000;

// サーバーが動いたときに実行される通り道を準備
app.get("/users", (req, res) => {
    // DIコンテナから部品を取り出して処理を実行
    const userController = container.resolve(UserController);
    userController.handleRequest(req, res);
});

// サーバーを起動して待機状態にする
app.listen(port, () => {
    console.log("サーバーがポート3000で無事に起動しました");
});

このプログラムでは、インターネットのブラウザからhttp://localhost:3000/usersというアドレスにアクセスがあった場合、Expressがその要求を検知します。その後、DIコンテナがUserRepositoryを中に含んだ状態のUserControllerをその場で組み立てて呼び出し、最終的にユーザーの名前が入った一覧データを画面に返します。役割ごとに部品が綺麗に独立しているため、後からデータの取得先を本物のデータベースに変更したい場合でも、他の場所を汚さずに修正が可能です。

このプログラムを起動した際に、パソコンの画面に表示されるログは以下の通りになります。


サーバーがポート3000で無事に起動しました

7. インターフェースを活用して部品の交換をより簡単にする方法

7. インターフェースを活用して部品の交換をより簡単にする方法
7. インターフェースを活用して部品の交換をより簡単にする方法

DIコンテナの本当の凄さを実感するために、もう少し発展的なテクニックを学びましょう。プログラミングにはインターフェース(Interface)という機能があります。これは、部品の具体的な中身ではなく、「こういう機能を持っていなければならない」という外枠のルール、約束事だけを決める仕組みです。

例えば、スマートフォンの充電器の接続口の形(規格)が決まっていれば、どこの会社が作ったケーブルでも差し込んで充電ができますよね。この接続口の規格にあたるものがインターフェースです。これを利用すると、テスト用のダミー部品と、本番用の部品を状況に応じて一瞬で切り替えることができるようになります。

今回は、通知を送るというルールを定義したインターフェースを作り、メールで送る部品と、スマートフォンのアプリ画面に通知を出す部品の二つを切り替えるコードを作成してみましょう。特定の名前(トークン)を使ってDIコンテナに登録するのが特徴です。


import "reflect-metadata";
import { injectable, container, inject } from "tsyringe";

// 通知機能が必ず持っていなければならないルールの定義
interface NotificationSender {
    send(message: string): void;
}

// ルールに従って作られた、メール送信用の部品
@injectable()
class EmailNotificationSender implements NotificationSender {
    public send(message: string): void {
        console.log("メールを送信しました: " + message);
    }
}

// ルールに従って作られた、アプリ画面通知用の部品
@injectable()
class PushNotificationSender implements NotificationSender {
    public send(message: string): void {
        console.log("アプリ画面にプッシュ通知を表示しました: " + message);
    }
}

// メインの処理を行う部品。インターフェースを注入してもらう
@injectable()
class OrderService {
    constructor(
        @inject("NotificationSender") private sender: NotificationSender
    ) {}

    public checkout(): void {
        // 商品の購入処理が行われたと仮定します
        this.sender.send("ご注文ありがとうございました!");
    }
}

// DIコンテナに対して、どちらの部品を接続するか名前をつけて登録する
// ここを切り替えるだけで、プログラム全体の挙動を簡単に変えられます
container.register("NotificationSender", { useClass: PushNotificationSender });

// 組み立てて実行
const orderService = container.resolve(OrderService);
orderService.checkout();

コードの解説をします。OrderServiceという注文処理の部品は、メール送信の仕組みの具体的な中身を知りません。ただ「通知機能というルールを満たした何かが届く」ということだけを信じて動いています。そして、プログラムの一番下にあるcontainer.registerという設定部分で、今回はPushNotificationSenderを使うように指示を出しています。もし、やっぱりメール送信に変えたいとなった場合は、この設定の文字を一つ書き換えるだけで、全体の動きをガラリと変えることができます。

上記のプログラムを実行すると、設定した通りにアプリ画面への通知処理が働き、下記の出力結果が画面に表示されます。


アプリ画面にプッシュ通知を表示しました: ご注文ありがとうございました!

8. 初心者が陥りやすいエラーの原因と解決のコツ

8. 初心者が陥りやすいエラーの原因と解決のコツ
8. 初心者が陥りやすいエラーの原因と解決のコツ

最後に、初めてTypeScriptでDIコンテナを触る人が陥りがちなエラーの罠とその解決策について詳しく解説します。慣れないうちは、エラー画面に出てくる英語のメッセージを見るだけでパニックになってしまうかもしれませんが、原因が分かれば怖くありません。

最も多いトラブルの一つが、ファイルの冒頭に書くべきimport "reflect-metadata";を忘れてしまうことです。これを忘れると、DIコンテナは部品の形を認識できなくなり、「組み立てのための情報が見つかりません」といった内容の複雑なエラーを表示して動かなくなってしまいます。新しくファイルを作った際は、一番上の行にお守りのように必ずこの記述を入れる癖をつけましょう。

もう一つの原因は、デコレーターである@injectable()の付け忘れです。クラスの設計図の真上にこの文字を書き忘れると、その部品はDIコンテナの魔法の箱に入ることができなくなります。もしプログラムを実行したときに「正しく組み立てができませんでした」というエラーが出た場合は、まず全ての部品にこの2つの記述が正しく揃っているか、深呼吸をして落ち着いて確認してみてください。一つずつ確認していけば、必ず原因を見つけることができますよ。

関連記事:
カテゴリの一覧へ
新着記事
New1
JavaScript
JavaScriptのスコープとは?ローカル変数・グローバル変数の違いと使い分け
New2
TypeScript
TypeScriptで型の合成時に発生する型エラーの対処法を徹底解説!初心者にもわかりやすく解説
New3
TypeScript
TypeScriptでAPI呼び出しをラップするユーティリティ関数の設計完全ガイド!初心者向けにAxiosやFetchの使い方を解説
New4
TypeScript
TypeScriptのMapped Typesとインターフェースの連携方法を徹底解説!初心者でも理解できる型変換の基本
人気記事
No.1
Java&Spring記事人気No1
JavaScript
JavaScriptのループで無限ループが起きる原因と対策を初心者向けに徹底解説!
No.2
Java&Spring記事人気No2
JavaScript
JavaScriptのインストール方法まとめ!Windows・Mac・Linux別にステップ解説
No.3
Java&Spring記事人気No3
JavaScript
JavaScriptの文字列の長さを取得する(lengthプロパティ)
No.4
Java&Spring記事人気No4
TypeScript
TypeScriptのファイル分割とimport/exportの基本を徹底解説!初心者でも安心のやさしい入門
No.5
Java&Spring記事人気No5
TypeScript
TypeScriptでコメントを書く正しい書き方と使い分け【初心者向けにやさしく解説】
No.6
Java&Spring記事人気No6
JavaScript
JavaScriptのプリミティブ型と参照型の違いを初心者向けに解説
No.7
Java&Spring記事人気No7
TypeScript
TypeScriptの始め方:開発環境の構築手順【初心者向け】
No.8
Java&Spring記事人気No8
TypeScript
TypeScriptでパスエイリアスを設定する方法!baseUrlとpathsでコードをスッキリ整理