こんにちは、あっきー(IwswAkht)です。
フロントエンドでTypeScriptが有名になってきてる近年、ついにTypeScriptで動くバックエンドのフレームワーク「NestJS」が登場しました。
同時にNestJSでの開発プロジェクトに携わる機会があり、インプットする機会にも恵まれました。せっかくインプットしたので合わせてアウトプットもしていこうということで記事を書いていきます。
今回は環境構築をしながら、NestJSの基本的な構造などを中心に話ていきます。
NestJSをさわってみようかなと思ってる方はぜひこの記事に雰囲気を掴んでいただけたらと思います。
それでは、よろしくお願いします。
NestJSとは
まずは、NestJSの特徴です。以下にまとめました。
- Node.js上で動作するオープンソースのバックエンド開発フレームワーク
- TypeScriptで作られているので基本TypeScriptで開発するもの
- Expressをコアに作られている
- Express + NestJS特有の機能も使うことができる
- AngularにインスパイアされているのでAngluarに似ているところが多い
NestJSのメリット
NestJSを使うメリットを以下にまとめました。
- TypeScriptで開発するので型つけることができる
- Expressの機能やライブラリも使用できる
- Nest CLIを使ってプロジェクトやファイルのテンプレートを作成できる
- テストフレームワークが標準で用意されている
- 拡張性が高く、RDB、NoSQL、セキュリティ、GraphQL、WebSocketなどに対応
- Angularにインスパイアされてるので一緒に使うと学習コストが下がる
- AngularとNestJSのmonorepo構成にすることでフロントエンド、バックエンド両開発の学習コストを下げる
NestJSのデメリット
もちろんデメリットもあります。以下にまとめました。
- 他のフレームワークと比較してまだ情報が少ない
- 公式ドキュメントも日本語対応していない
Nest CLIとは
Nest CLIとはNestJSのコマンドラインインターフェースです
例えばLaravelのlaravel newであったりRuby on Railsのrails newであったり。
フレームワーク特有のコマンドでプロジェクトフォルダを作成できたりするのですが、それのNestJSバージョンです。
Nest CLIでプロジェクトフォルダの作成
Nest CLIでまずは空っぽのプロジェクトフォルダを作成してみましょう。
前提として、以下の環境は既に導入済みとします。
今回はnpmでの導入方法になります。
テキストエディタはお好みのもので構いませんが、特段理由がないようでしたらVSCodeを使うことをお勧めします。
ターミナルよりNest CLIのパッケージをインストールします。
ターミナル
1 |
$ npm i -g @nestjs/cli |
お好みの名前でプロジェクトフォルダを作成し移動します。
ターミナル
1 |
$ mkdir project && cd project |
移動したフォルダ先でNestJSのプロジェクトフォルダを作成します。project-nameはお好みの名前を設定ください。
ターミナル
1 |
$ nest new project-name |
これで完了です。プロジェクトフォルダ配下に空のNestJSプロジェクトが作成されたことが確認できます。
各種ファイルの概要
それぞれのファイルがどんな役割があるのかざっと確認します。
フォルダ全体の構成は以下になります。(node_modulesは省略してます)
ターミナル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$ tree -I node_modules . ├── node_modules ├── .eslintrc.js ├── .prettierrc ├── README.md ├── nest-cli.json ├── package-lock.json ├── package.json ├── src │ ├── app.controller.spec.ts │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ └── main.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json └── tsconfig.json |
それぞれのファイルの概要を以下にまとめます。
ディレクトリ、ファイル名 | 概要 | |
---|---|---|
node_modules | プロジェクトに依存しているライブラリ群 | |
.eslintrc.js | ES Lintの設定ファイル | |
.prettierrc | コード整形を行うprettirの設定ファイル | |
nest-cli.json | Nestの設定を上書きしたり、追加するためのファイル | |
package-lock.json | ライブラリの依存情報が記載されたファイル | |
package.json | ライブラリの管理ファイル | |
src | プロダクションコードを格納するディレクトリ | |
app.controller.spec.ts | appモジュールのコントローラのテストファイル | |
app.controller.ts | appモジュールのコントローラファイル | |
app.module.ts | 全モジュールの親にあたるファイル | |
app.service.ts | appモジュールのサービスファイル | |
main.ts | 最初に読み込まれるファイル | |
test | テストコードを格納するディレクトリ | |
app.e2e-spec.ts | e2eテストコードファイル | |
jest-e2e.json | jestの設定ファイル | |
tsconfig.build.json | TypeScriptの設定ファイル | |
tsconfig.json | TypeScriptの設定ファイル |
NestJSの基本構造
NestJSは基本、機能単位でモジュールを作成します。
1つのモジュールは原則以下の3ファイルによって構成されます。
- Controller
- Service
- Repository
それぞれの役割を説明していきます。
Controllerファイルの作成
Controllerの役割はクライアントからのリクエストを受け付け、クライアントにレスポンスを返すことです。
要はルーティングの役割を担うファイルです。
ルーティングをするファイルになりますので、ロジックにあたる処理は書かないでそれらのプログラムはServiceに実装するのが一般的です。
なのでControllerには原則、ルーティングとServiceを呼び出す記述以外は書くことはないです。
Controllerの記述例は以下になります。
usersController
1 2 3 4 5 6 7 8 9 |
import { Controller, Get } from '@nestjs/common'; @Controller('users') export class UsersController { @Get() index() { // user一覧を取得する } } |
基本は@Controller()デコレータを付与しController全体のルーティングを行います。
先の例だとUsersControllerは/usersのパスにアクセスされた時に起動するControllerという意味になります。
Controllerでのルーティングをしたらクラス内の各メソッドに対してHTTPメソッドを定義します。
先の例だとindex()メソッドに@Get()デコレータを付与することでHTTPリクエストGETで/usersのパスにアクセスした時はindex()を実行するという意味合いになります。
また、Nest CLIでControllerの雛形ファイルを作成することができます。コマンドは以下です。
ターミナル
1 |
$ nest g controller users |
上記コマンドの実行結果としてusers.controller.tsとusers.controller.spec.tsのファイルが作成されます。
テストファイルを作成しない場合は--no-specオプションを末尾に付与してあげてください。
Serviceファイルの作成
Serviceはビジネスロジックを定義するファイルです。
先ほどControllerはルーティング以外の記述は書かないと言いましたので、細かい処理はこのServiceに書いていくイメージですね。
Serviceの書き方などを見る前に、まずServiceを使う上で理解しておきたい大事な概念がありますのでそちらを先に説明します。
それがDI(Dependency Injection)です。
直訳すると依存性の注入です。よくわからないですよね。
もう少しプログラムの言葉で表現すると依存関係のあるオブジェクトを外部から渡すことをDIと言います。
まだ、わかりづらいですよね。例を出しつつもう少し詳しくいきます。
まず、依存関係のあるオブジェクトとはなんでしょうか。
例えば、UsersControllerのビジネスロジックを書いているUsersServiceが存在するとします。
このUsersControllerはUsersServiceが存在しないと正しく動作クラスです。
このようなある特定のプログラムがないと対象のプログラムを動かすことができないような状況を依存関係があるオブジェクトと言います。
次に外部から渡すとはどういうことでしょうか。
外部から渡さない例を見てみます。
UsersServiceはクラスなのでどこかでインスタンス化しなければいけません。
インスタンス化を以下のようにController内でインスタンス化したとします。
Controller
1 2 3 4 5 6 7 8 |
@Controller('users') export class UsersController { @Get() index() { const service = new UsersService(); return service.method(); } } |
クラス内で別のクラスをインスタンス化する。このような書き方を外部から渡さないと言います。
これでもプログラム的には全然問題はないです。
ただ、例えばDBの向き先であったり、ログの出力先の定義などをテスト環境と本番環境で別々にするという環境によって違う実装が必要になるケースが存在したとします。
この場合、テスト環境用のServiceクラスで実装し、本番環境にアップする時に本番用のServiceクラスの実装に書き換える必要がでたりしてプログラム間の依存度を高めてしまいます。
このような事象を回避するためにServiceは外部でインスタン化し、Controllerに渡すように設計された考え方がDIと言います。
DIすることで依存元のプログラムを書き換えることなく依存先のプログラムを切り替えられるようになり依存性を低くすることができるというメリットがあります。
DIの概要はこの辺にして実際にNestJSでDIする流れを確認します。
まずServiceの書き方の例は以下です。
Service
1 2 3 4 |
import { Injectable } from '@nest/common'; @Injectable() export class UsersService {} |
クラスに@injectable()デコレータを付与します。
これでDI可能なクラスになります。
このクラスをモジュールに登録します。
モジュールの例は以下です。
Module
1 2 3 4 5 |
@Module({ controllers: [UsersController], providers: [UsersService], }) export class UsersModule {} |
DIするにはprovidersにServiceクラスを登録します。この記述だけでNestJSが自動でDIしてくれるようになります。
あとは使いたいクラスのコンストラクタで受け取るようにしてあげれば完了です。
Controller
1 2 3 4 5 6 7 8 |
@controller export class UsersController { constructor(private readonly userService: UsersService) {} method() { //this.userService.method()で使用 } } |
また、Nest CLIでServiceの雛形ファイルを作成することができます。コマンドは以下です。
ターミナル
1 |
$ nest g service users |
上記コマンドの実行結果としてusers.service.tsとusers.service.spec.tsのファイルが作成されます。
テストファイルを作成しない場合は--no-specオプションを末尾に付与してあげてください。
Repositoryの作成
Repositoryはデータベースへの参照や作成、更新、削除などのいわゆるCRUD処理を記述するためのファイルになります。
データベース処理なので別途接続設定が必要になります。
今回、データベースについては詳しくふれないので簡単に概要だけ確認します。
まず、Repositoryの記述例は以下です。
Repository
1 2 3 4 5 |
import { EntityRepository, Repository } from 'typeorm'; import { User } from '../entities/user.entity'; @EntityRepository(User) export class UserRepository extends Repository<User> {} |
Repositoryには@EntityRepository()デコレータを付与します。
引数に渡してるUserは簡単に言えばデータベースのテーブル内容を定義したファイルです。
デコレータにテーブルの情報を渡してデータベース処理を記述するクラスがRepositoryということだけを今回は理解しておいてもらえばオッケーです。
Module化する
ここまでの流れで確認してきたController、Service、Repositoryを1つのモジュールとしてまとめます。
これで1つの機能を備えたモジュールの完成です。
記述例としては以下になります。※Repositoryの記述は除外してます。
Module
1 2 3 4 5 6 7 8 9 |
import { Module } from '@nestjs/common'; import { UserController } from './users.controller'; import { UserService } from './users.service'; @Module({ controllers: [UserController], providers: [UserService], }) export class UserModule {} |
その他の設定のプロパティ含め以下が概要になります。
- providers:Serviceなど@injectableデコレータがついたクラスを指定e
- controllers:@controllerデコレータがついたクラスを記述
- imports:Repositoryなどモジュール内部で使いたい外部モジュールを記述
- exports:外部モジュールとしてエクポートしたいものを記述
また、Nest CLIでModuleの雛形ファイルを作成することができます。コマンドは以下です。
ターミナル
1 |
$ nest g module users |
上記コマンドの実行結果としてusers.module.tsのファイルが作成されます。
appモジュールに登録
1つの機能を持ったモジュールが作成されたら最後にappモジュールにそのモジュールを登録します。作ったモジュールは最終的にこのappモジュールに登録する必要があります。
以下、記述例です。
app.module.ts
1 2 3 4 5 6 7 8 9 10 11 |
import { Module } from '@nestjs/common'; import { UsersModule } from './users/users.module'; @Module({ imports: [ UsersModule, ], controllers: [], providers: [], }) export class AppModule {} |
全体構造を確認
appモジュールに各機能ごとにモジュールが登録されたら最後はmain.tsで読み込まれます。
これによりアプリケーション全体にモジュールの機能が適応されます。
main.tsの内容を確認してみます。
main.ts
1 2 3 4 5 6 7 8 |
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); |
appモジュールがインポートされてポート3000でリスンされていることが確認できますね。
こんな感じでmain.tsをプログラムのスタート地点として→appモジュール→各機能モジュール→機能モジュールの実装
という設計で開発していくのがNestJSの基本構造になります。
図解すると以下のようなイメージです。
ここまでの内容を踏まえて最後にHello world して終了しましょう。
Hello Worldしてみる
以下のコマンドを順番に実行して必要なファイルを作成します。
ターミナル
1 2 3 |
$ nest g module hello $ nest g controller hello --no-spec $ nest g service hello --no-spec |
./src/helloフォルダは以下にhello.modules.tsとhello.controller.tsとhello.service.tsが作成されたことを確認します。
確認できましたら各ファイルに以下の編集を加えます。
hello.controller.ts
1 2 3 4 5 6 7 8 |
import { Injectable } from '@nestjs/common'; @Injectable() export class HelloService { hello(): string { return 'Hello World!!'; } } |
hello.controller.ts
1 2 3 4 5 6 7 8 9 10 |
import { Controller, Get } from '@nestjs/common'; import { HelloService } from './hello.service'; @Controller('hello') export class HelloController { constructor(private helloService: HelloService) {} index(): string { return this.helloService.hello(); } } |
修正したらnpm startでローカルサーバを起動し、http://localhost:3000/helloにアクセスしてHello World!が出力されればOKです。
さいごに
さいごまで読んでいただきありがとうございます。
NestJSの全体像が少しは見えましたでしょうか。
NestJSが今後バックエンドでベターなフレームワークとして浸透するのかなどはわかりませんが、個人的に使ってみた所感としてはシンプルな設計でさくっとAPIサーバ立てるならNestJSが最適だなという感じでした。
まだまだ日本語情報が少なくこれからふれる人は苦労する場面も多いだろうなと思うので引き続きNestJSの記事は多めに書いてみなさんの役に立ったらいいなと思ってますので引き続きよろしくお願いします。
この記事を気に入っていただけましたらTwitterでもプログラミングに関してのツイートをリアルタイムでしていますので
ご一緒にフォローもお願いします。
僕は未経験からSESの企業にエンジニアとして転職し、その後はフリーラン、現在は受託開発企業に転職しました。
エンジニアとしていろいろな働き方を経験し、いろいろな転職サイトや転職エージェントの方にお話を伺いました。
その経験の中でだいじだと感じたことは、ITに特化した転職エージェントのサービスを利用することです。
幅広い業界に対応した転職エージェントはIT転職に特化したエージェントと比べると紹介先の数が少ないです。
そのため希望してない紹介先に転職し、思ってたのと違うとなることもあります。
そうならないためにエンジニアを目指すなら必ずIT特化の転職サービスを登録し、たくさんの選択肢の中から自分が行きたいと思う企業を探してください。
僕はフリーランスでの案件探し、受託開発企業の転職の時もレバテックのかたに紹介していただきました。
とても満足できましたので皆さんもぜひ利用してみてください!
\無料のIT系転職サービス/
/優良案件がたくさん\
最初にModuleを作成した場合、後続のServiceやControllerをNest CLIで作成した場合、自動でModuleの方が更新されます。