awesome-hacks
Docs

NestJSの基礎

NestJSを何も知らない状態から、全体像と最小構成を掴む

最終更新:2026/05/07

NestJSとは

TypeScript前提として設計された、Node.jsで 保守しやすいサーバーアプリ を作るためのフレームワーク。

  • Express/Fastify上で動く
    HTTPサーバー自体はExpressまたはFastifyが実行し、NestJSはその上に「アプリケーション構造」を提供する。 NestJSは内部HTTPエンジンとしてExpressまたはFastifyを選択可能で、デフォルトではExpressを利用する。 Controller / Service / Module / DI などによる構造化された設計を提供しつつ、選択したHTTP基盤(Express/Fastify)のエコシステムも活用できる。

  • Angularの設計思想(バックエンド版Angularという人もいる)

    • Module中心:Module、Controller、Serviceが最小単位でありそれぞれに役割分担が明確に決まっており、ModuleでController、Service、Provider、Repositoryを束ねる。
    • Dependency Injection(DI)が標準:NestJS側が自動でインスタンス注入(Expressだと自分でnewする必要がある)。依存関係をコンテナが管理して、テストや差し替えがやりやすい。
    • デコレータ文化:Angular系はデコレータ文化があり、NestJSもかなりそう。@Controller()@Get() のように、クラス/メソッドに役割を付与する。
  • “レイヤー分離”を強く促す
    NestJSは:

    • Module → 配線
    • Controller → HTTP入口
    • Service → 業務ロジック
    • Repository → DBアクセス(Serviceに書くと業務ロジックとSQL/ORMが混ざるため)

    のように責務分離をかなり強く推奨

  • クラスベース設計
    関数文化のReact/Next.jsとは違い、クラス重視

  • “エンタープライズ感”が強い

    • 型安全
    • DI
    • 層分離
    • テスト容易性
    • 保守性
    • 大人数開発

    を重視。
    「小さいAPIをサクッと作りたい」だけなら、Express、Hono、Fastifyの方が軽い

Express/Fastifyとは

ExpressはTypeScriptにおけるバックエンド開発のデファクトスタンダードとされるフレームワークであり、非常にシンプルな設計思想で自由に構築できる。
FastifyはExpressの後発で、Fastifyの方が速くTypeScriptへの相性がいいが、Expressより情報が少なかったり、古いmiddlewareに非対応の場合がある。
一方で、NestJSは最初からモジュール構造、依存性注入(DI)などの設計パターンが組み込まれている。

Angularとは

Angularはブラウザ上で動くフロントエンド(クライアントサイド)のフレームワーク
NestJSはサーバー上で動くバックエンド(サーバーサイド)のフレームワーク

Next.jsとの違い

Next.jsはReactベースのフロントエンドフレームワークであり、ウェブアプリの構築やSSR・SSGを実現する。API Routes機能で簡易的なバックエンド処理も実装可能。
一方で、Nest.jsはNode.js(TypeScript)ベースのバックエンドフレームワークであり、APIサーバー(REST API)や大規模な業務ロジックを構築するために使う。

まず覚える3つ(超重要)

1) Controller(入口)

HTTPリクエストを受け取って、レスポンスを返す場所。

  • 例: GET /users を受けて、ユーザー一覧JSONを返す
  • Controllerには「入力の受け取り・バリデーション・Service呼び出し」くらいを書き、重い処理はServiceに逃がすのが基本

2) Service(中身)

ビジネスロジックを書く場所。

  • 例: DBからユーザーを取る、整形する、権限をチェックする
  • Controllerから呼ばれる“部品”になりがちなので、単体テストしやすい

3) Module(配線)

ControllerやServiceを「このアプリはこれを使う」と登録して、つなぐ場所。

  • NestJSは基本的に「Moduleの世界」に閉じて動く
  • 何かが動かないとき、まず疑うのは「Moduleに登録されてない」「export/importの関係が間違ってる」

最小の形(Controller / Service / Module)

NestJSを早速試してみることを目的に、最小のREST APIを1本作る。

サンプル実装手順

  1. 作業フォルダ作成
mkdir nestjs-practice
cd nestjs-practice
  1. NestJSプロジェクト作成(公式CLI)
npm i -g @nestjs/cli
nest new api-sample

nest new 実行時に package manager を聞かれたら、普段使っているもの(npm/pnpm/yarn)を選べばOK。
ここでは npm 想定で進める。

  1. プロジェクトへ移動して起動確認
cd api-sample
npm run start:dev

ブラウザで http://localhost:3000 にアクセスして Hello World! が出れば初期起動は成功。

  1. ファイル構成(今回触る場所)
api-sample/
└─ src/
   ├─ app.module.ts
   └─ users/
      ├─ users.module.ts
      ├─ users.controller.ts
      └─ users.service.ts

各ファイルの役割は次のとおり。

  • src/app.module.ts
    アプリ全体のルートModule。機能Module(今回は UsersModule)を読み込む起点。
  • src/users/users.module.ts
    users機能の配線定義。ControllerとServiceを登録して関連付ける。
  • src/users/users.controller.ts
    HTTPの入口。GET /users のようなルーティングを定義し、Serviceを呼び出す。
  • src/users/users.service.ts
    処理の中身(ビジネスロジック)を置く。DBアクセスや計算など、Controllerから分離したい処理を書く。

どこを変えると何が変わるかをざっくり言うと、

  • レスポンス内容を変える: users.service.ts
  • エンドポイントURLやHTTPメソッドを変える: users.controller.ts
  • users機能の依存関係を変える: users.module.ts
  • アプリに機能を追加/読み込みする: app.module.ts
  1. users モジュール一式を作成
# 別ターミナルで実行(start:dev を止めてもOK)
cd api-sample/src/
nest g module users
nest g service users
nest g controller users
  1. src/users/users.service.ts
import { Injectable } from "@nestjs/common";

type User = { id: string; name: string };

@Injectable()
export class UsersService {
  list(): User[] {
    return [
      { id: "u_1", name: "Taro" },
      { id: "u_2", name: "Hanako" },
    ];
  }
}

このファイルは「返すデータの中身」を決める場所。
今は固定配列を返しているが、実務ではここがDBや外部API呼び出しに置き換わることが多い。

  1. src/users/users.controller.ts
import { Controller, Get } from "@nestjs/common";
import { UsersService } from "./users.service";

@Controller("users")
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get()
  list() {
    return this.usersService.list();
  }
}

このファイルはHTTPルーティングの定義場所。

  • @Controller("users") でベースパスを /users にする
  • @Get() で GET リクエストを受ける
  • メソッド内で usersService.list() を呼び、結果をJSONで返す
  1. src/users/users.module.ts(生成済みだが確認)
import { Module } from "@nestjs/common";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";

@Module({
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

このファイルはDIの登録表のイメージ。

  • controllers: HTTP入口として使うクラス
  • providers: 注入可能にするクラス(Serviceなど)
  1. src/app.module.tsUsersModule を読み込む
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { UsersModule } from "./users/users.module";

@Module({
  imports: [UsersModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

imports: [UsersModule] がないと、users機能はアプリに組み込まれない。
「ファイルはあるのにルートが効かない」ときはここを疑うとよい。

  1. 動作確認
npm run start:dev
curl http://localhost:3000/users

以下のようなJSONが返れば成功。

[
  { "id": "u_1", "name": "Taro" },
  { "id": "u_2", "name": "Hanako" }
]

DI(依存注入)が何をしているか

さっきのControllerでこれを見たはず。

constructor(private readonly usersService: UsersService) {}

これが「自分で new UsersService() しない」ポイント。

  • Nestが UsersService を作って管理してくれる(=コンテナ)
  • Controllerは「必要です」と宣言するだけ
  • 結果として、テストで UsersService を差し替えたり、実装を分割しやすい

関連

  • rest-api-basics(このリポジトリ内のREST基礎)
  • nodejs-basics(Node.jsの前提)

参考

NestJS公式サイト