NestJSの基礎
NestJSを何も知らない状態から、全体像と最小構成を掴む
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本作る。
サンプル実装手順
- 作業フォルダ作成
mkdir nestjs-practice
cd nestjs-practice- NestJSプロジェクト作成(公式CLI)
npm i -g @nestjs/cli
nest new api-samplenest new 実行時に package manager を聞かれたら、普段使っているもの(npm/pnpm/yarn)を選べばOK。
ここでは npm 想定で進める。
- プロジェクトへ移動して起動確認
cd api-sample
npm run start:devブラウザで http://localhost:3000 にアクセスして Hello World! が出れば初期起動は成功。
- ファイル構成(今回触る場所)
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
usersモジュール一式を作成
# 別ターミナルで実行(start:dev を止めてもOK)
cd api-sample/src/
nest g module users
nest g service users
nest g controller userssrc/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呼び出しに置き換わることが多い。
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で返す
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など)
src/app.module.tsでUsersModuleを読み込む
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機能はアプリに組み込まれない。
「ファイルはあるのにルートが効かない」ときはここを疑うとよい。
- 動作確認
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の前提)