はじめに
最近、実務でNestJSを書き始めました!
NestJSに関する日本語の記事がまだまだ少ないので、自分用メモも兼ねてどんどん記事を書いていきたいです。
使用技術
- NestJS(TypeScript)
- GraphQL
- TypeORM
クラス図
HogeService
にFugaService
を注入して、HogeModule
を外部から利用したいというユースケースです。HogeResolver
にFugaService
を利用します。
Serviceの実装
fuga.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import FugaEntity from 'src/entities/fuga';
@Injectable()
export class FugaService {
constructor(
@InjectRepository(FugaEntity)
private fugaRepository: Repository<FugaEntity>, // Fugaエンティティを注入
) {}
async find(id: number): Promise<FugaEntity> {
return this.fugaRepository.findOne(id);
}
}
hoge.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import HogeEntity from 'src/entities/hoge';
import { FugaService } from './fuga.service';
@Injectable()
export class HogeService {
constructor(
@InjectRepository(HogeEntity)
private hogeRepository: Repository<HogeEntity>, // Hogeエンティティを注入
private fugaService: FugaService, // Serviceを注入
) {}
async find(id: number): Promise<HogeEntity> {
const fuga = await this.fugaService.find(1); // 注入したインスタンスを利用可能に!
return this.hogeRepository.findOne(id);
}
}
動かしてみると...
[Nest] 877 - 11/28/2021, 2:45:15 AM ERROR [ExceptionHandler] Nest can't resolve dependencies of the FugaService (?). Please make sure that the argument FugaRepository at index [0] is available in the HogeModule context.
Potential solutions:
- If FugaRepository is a provider, is it part of the current HogeModule?
- If FugaRepository is exported from a separate @Module, is that module imported within HogeModule?
@Module({
imports: [ /* the Module containing FugaRepository */ ]
})
一見、これだけで動きそうなものなんですが、NestJSはmoduleで依存関係を定義してあげないとエラーが出ます。
moduleの実装
fuga.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FugaService } from 'src/services/fuga.service';
import FugaEntity from 'src/entities/fuga';
@Module({
imports: [TypeOrmModule.forFeature([FugaEntity])], // エンティティを明記
exports: [FugaModule, TypeOrmModule],
})
export class FugaModule {}
hoge.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { HogeService } from 'src/services/hoge.service';
import HogeEntity from 'src/entities/hoge';
import { FugaModule } from './fuga.module';
import { FugaService } from 'src/services/fuga.service';
import { HogeResolver } from 'src/resolvers/hoge.resolver';
@Module({
imports: [
TypeOrmModule.forFeature([HogeEntity]), // エンティティを明記
FugaModule, // !!! 利用するモジュールを追記 !!!
],
providers: [
HogeService,
HogeResolver,
FugaService, // !!! 利用するサービスを追記 !!!
],
exports: [HogeModule, TypeOrmModule],
})
export class HogeModule {}
あとは、使う側にmoduleを読み込んであればOK!
app.module.ts
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApiModule } from './api.module';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
GraphQLModule.forRoot({
autoSchemaFile: true,
}),
TypeOrmModule.forRoot(),
HogeModule, // !!! モジュールを読み込む !!!
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Resolverの実装
hoge.resolver.ts
import { Args, Int, Query, Resolver } from '@nestjs/graphql';
import Hoge from 'src/entities/hoge';
import { HogeService } from 'src/services/hoge.service';
@Resolver(() => Hoge)
export class HogeResolver {
constructor(private hogeService: HogeService) {}
@Query(() => Hoge)
async getHoge(@Args({ type: () => Int }) id: number) {
return this.hogeService.find(id);
}
}
コンストラクタでHogeService
を注入して、Resolverを実装することができます。ここには、Fugaservice
は出てきません!
終わりに
コードをテスタブルにするために、ちゃんとService層を依存性注入(DI)するようにしたいですよね。NestJSはこれが簡単にできる仕組みを備えているので、ドキュメントをちゃんと読んで使いこなせるようになりたいです!
moduleに書かないといけないことによるメリットは、正直よくわかっていないです笑。
appendix
package.json
"dependencies": {
"@nestjs/common": "^8.0.0",
"@nestjs/core": "^8.0.0",
"@nestjs/graphql": "^9.0.4",
"@nestjs/platform-express": "^8.0.0",
"@nestjs/typeorm": "^8.0.2",
"apollo-server-express": "^3.3.0",
"class-transformer": "^0.4.0",
"class-validator": "^0.13.1",
"graphql": "^15.5.3",
"graphql-tools": "^8.2.0",
"pg": "^8.7.1",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"typeorm": "^0.2.37"
},
"devDependencies": {
"@nestjs/cli": "^8.0.0",
"@nestjs/schematics": "^8.0.0",
"@nestjs/testing": "^8.0.0",
"@types/express": "^4.17.13",
"@types/jest": "^27.0.1",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^4.28.2",
"@typescript-eslint/parser": "^4.28.2",
"eslint": "^7.30.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"jest": "^27.0.6",
"prettier": "^2.3.2",
"supertest": "^6.1.3",
"ts-jest": "^27.0.3",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "^3.10.1",
"typescript": "^4.3.5"
},