Nx Nestjs Mongodb Mongoose統合のセットアップ - 第1部

スケーラビリティと効率性を高めるための、Nx、Nest.js、MongoDB、Mongooseの統合ガイドです。手順に従ってスムーズに開発を進められます。

技術公開日 読了目安 2 分

はじめに

この記事では、Nx、Nest.js、MongoDB、Mongooseを統合するためのセットアップ手順を解説します。この統合は、スケーラブルで効率的なアプリケーションを構築するために欠かせないものです。必要なパッケージのインストール、メインアプリケーションファイル(main.ts)の更新、app.module.tsファイルの修正、プロジェクトの生成、設定ファイルの作成、MongoDB接続のセットアップについて説明します。

パッケージのインストール

まず、プロジェクトに必要なパッケージをインストールしましょう。ターミナルを開き、以下のコマンドを実行します。

GraphQLパッケージ

npm i @nestjs/graphql @nestjs/apollo graphql apollo-server-express

Class Validatorパッケージ

npm i class-validator class-transformer

Bcrypt

npm i bcrypt

PassportとJWTパッケージ

npm install --save @nestjs/passport passport passport-local
npm install --save-dev @types/passport-local
npm install --save @nestjs/jwt passport-jwt
npm install --save-dev @types/passport-jwt
npm install cookie-parser
npm install --save-dev @types/cookie-parser

main.tsの更新

次に、apps/api/src/main.tsにあるmain.tsファイルを更新します。クッキーとバリデーションパイプに必要な設定を追加します。

ここでは、apps/apiフォルダはnextjsプロジェクトです。

import { Logger, ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import cookieParser from 'cookie-parser';

import { AppModule } from './app/app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const globalPrefix = 'api';
  app.setGlobalPrefix(globalPrefix);

  app.use(cookieParser());
  app.useGlobalPipes(new ValidationPipe());

  const port = process.env.PORT || 3000;
  await app.listen(port);
  Logger.log(`🚀 Application playground is running on: http://localhost:${port}/api/graphql`);
}

bootstrap();

app.module.tsの更新

次に、apps/api/src/app/app.module.tsにあるapp.module.tsファイルを更新します。この変更にはApolloとGraphQLの統合が含まれます。

import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      path: '/api/graphql',
      autoSchemaFile: true
    }),
  ],
  controllers: [AppController],
  providers: [AppService]
})
export class AppModule {}

リゾルバーを持つモジュールが存在しない場合、エラーが発生します!

@nestjs/configパッケージのインストール

設定を扱うために、@nestjs/configパッケージをインストールします。

npm i @nestjs/config

プロジェクトの生成

次に、共有設定用のプロジェクトを生成します。

npx nx generate @nx/js:library shared-config --directory=libs/shared/config --importPath=@libs/shared/config --tags=scope:shared --bundler=swc

✔ Which unit test runner would you like to use? · jest

env.tsの作成

libs/shared/config/src/lib/内に、以下の内容でenv.tsファイルを作成します。

import * as dotenv from 'dotenv';

dotenv.config({ path: '.env.local' });

export const env = {
  PORT: process.env['PORT'],
  API_PORT: process.env['API_PORT'],
  NEXT_PUBLIC_API_GQL_URL: process.env['NEXT_PUBLIC_API_GQL_URL'],
  DB_URL: process.env['DB_URL'],
};

index.tsファイルを更新します。

export * from './lib/env';

@nestjs/mongooseパッケージのインストール

Nest.jsでMongoDBを扱うために、@nestjs/mongooseとmongooseパッケージをインストールします。

npm i @nestjs/mongoose mongoose

Mongooseプロジェクトの生成

Mongoose共有設定用のプロジェクトを生成します。

npx nx generate @nx/js:library api-mongoose-shared --directory=libs/api/mongoose/shared --importPath=@libs/api/mongoose/shared --tags=scope:api --bundler=swc

✔ Which unit test runner would you like to use? · none

Mongooseファイルの作成

libs/api/mongoose/shared/src/lib/内に以下のファイルを作成します。

model/abstract.model.ts

import { Field, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class AbstractModel {
  @Field()
  readonly _id!: string;
}

database/abstract.schema.ts

import { Prop, Schema } from '@nestjs/mongoose';
import { SchemaTypes, Types } from 'mongoose';

@Schema()
export class AbstractDocument {
  @Prop({ type: SchemaTypes.ObjectId })
  _id!: Types.ObjectId;
}

database/abstract.repository.ts

import { Logger, NotFoundException } from '@nestjs/common';
import { FilterQuery, Model, Types, UpdateQuery } from 'mongoose';
import { AbstractDocument } from './abstract.schema';

export abstract class AbstractRepository<TDocument extends AbstractDocument> {
  protected abstract readonly logger: Logger;

  constructor(protected readonly model: Model<TDocument>) {}

  async create(document: Omit<TDocument, '_id'>): Promise<TDocument> {
    const createdDocument = new this.model({
      ...document,
      _id: new Types.ObjectId()
    });

    return (await createdDocument.save()).toJSON() as unknown as TDocument;
  }

  async findOne(filterQuery: FilterQuery<TDocument>): Promise<TDocument> {
    const document = await this.model.findOne(filterQuery, {}, { lean: true });

    if (!document) {
      this.logger.warn('Document not found with filterQuery', filterQuery);
      throw new NotFoundException('Document not found.');
    }

    return document as TDocument;
  }

  async findOneAndUpdate(filterQuery: FilterQuery<TDocument>, update: UpdateQuery<TDocument>) {
    const document = await this.model.findOneAndUpdate(filterQuery, update, {
      lean: true,
      new: true
    });

    if (!document) {
      this.logger.warn(`Document not found with filterQuery:`, filterQuery);
      throw new NotFoundException('Document not found.');
    }

    return document;
  }

  async find(filterQuery: FilterQuery<TDocument>) {
    return this.model.find(filterQuery, {}, { lean: true });
  }

  async deleteOne(filterQuery: FilterQuery<TDocument>): Promise<void> {
    const result = await this.model.deleteOne(filterQuery);

    if (result.deletedCount === 0) {
      this.logger.warn('No document found for deletion with filterQuery', filterQuery);
      // throw new NotFoundException('No document found for deletion.');
    }
  }

  async deleteMany(filterQuery: FilterQuery<TDocument>): Promise<void> {
    const result = await this.model.deleteMany(filterQuery);

    if (result.deletedCount === 0) {
      this.logger.warn('No documents found for deletion with filterQuery', filterQuery);
      // throw new NotFoundException('No documents found for deletion.');
    }
  }

 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 async aggregate(pipeline: any[]): Promise<any[]> {
    return this.model.aggregate(pipeline);
  }
}

database/database.module.ts

import { env } from '@libs/shared/config';
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [
    MongooseModule.forRootAsync({
      useFactory: () => ({
        uri: env.DB_URL
      })
    })
  ]
})
export class DatabaseModule {}

index.tsファイルを更新します。

export { AbstractRepository } from './lib/database/abstract.repository';
export { AbstractDocument } from './lib/database/abstract.schema';
export { DatabaseModule } from './lib/database/database.module';
export { AbstractModel } from './lib/model/abstract.model';

これで、Nx、Nest.js、MongoDB、Mongoose統合の第1部のセットアップが完了しました。続く第2部では、設定と統合のプロセスをさらに進めていきます。お楽しみに。

Nx Nestjs Mongodb Mongoose統合のセットアップ - 第2部