【入門】Nestjs PrismaでCRUD機能をハンズオン

nestjs prismaでcrud機能を作成

nestjs 最小構成でのハンズオン

今回はnestjsのハンズオンをします。 今回はセットアップから簡単なapiの作成 swaggerでの確認の仕方まで解説します。

目次

nestjsとは

  • node.jsのサーバサイドアプリケーションフレームワーク
  • TypeScript対応
  • 内部的にはExpressかFastifyを使用できる。

セットアップ

nestjs cliをインストールして、プロジェクトを作成します。

$ npm i -g @nestjs/cli
$ nest new project-name

ディレクトリ構造は以下のような感じです。

src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
└── main.ts
  • app.controller.ts
    • ルートが 1つの基本的なコントローラー
  • app.controller.spec.ts
    • コントローラーの単体テスト
  • app.module.ts
    • アプリケーションのルートモジュール
  • app.service.ts
    • 単一メソッドの基本サービス
  • main.ts
    • コア関数NestFactoryを使用して、Nestアプリケーションインスタンスを作成するアプリケーションのエントリファイル

アプリケーションの起動

アプリケーションを起動します。

npm run start

ブラウザを開いてhttp://localhost:3000にアクセスすることで、Hello World!の文字が表示されます。

ホットリロードモード

アプロケーションのコードファイルを編集すると、そのコードの変更を実行中のアプリケーションに直ちに適応できる機能をホットリロードと呼ばれます。

nestjsはホットリロード機能をデフォルトで搭載しています。

npm run start:dev

自分は開発中は基本この機能を使用して開発します。

起動

nestjsでは起動にbootstrapという単語を使うそうです、cssフレームワークで有名なbootstrapとは全然関係ありません。 main.tsをいじることで起動しています。 NestFactoryでアプリを起動しています。

コントローラー

コントローラーはリクエストを処理して、クライアントに返答します。

cliを利用してコントローラーを作成することができます。

nest g controller cats

サンプルコード

[cat.controller.ts]

import { Controller, Get } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return '猫が沢山いる';
  }
}

このようにしてapiを作成していきます。

swagger

apiの開発でswaggerが推奨されていたので導入しました。

swaggerのインストール

npm install --save @nestjs/swagger

起動

インストールプロセスが完了したら、ファイルを開き、次のクラスSwaggerModuleをmain.tsに使用してSwaggerを初期化します。 このswaggerのインストールを

[main.ts]

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('猫たちのAPI例')
    .setDescription('猫のAPIドキュメント')
    .setVersion('1.0')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

  await app.listen(3000);
}
bootstrap();

DocumentBuilderはOpenAPI仕様に準拠したswaggerドキュメントを構築するのに役立ちます。 個人的にはapiを叩く速度が格段に速くなるのが最大のメリットだと感じました。

nestでservice作成

[src/cats/cats.service.ts]

import { Injectable } from '@nestjs/common';

@Injectable()
export class CatService {
  async getCats() {
    return '猫がとても多くいる';
  }
}

http://localhost:3000/api

[src/cats/cats.module.ts]

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

[app.module.ts]

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsModule } from './cats/cats.module';

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

こうすることで、app.module.tsの可読性が向上しますね。

next cliコマンドについて

nest g controller cats
nest g service cats
nest g module cats

ファイルを作成するコマンドですが最低限のソースの記載と、テストコードのジェネレートをする感じですね。 app.module.tsを強制的にいじるので微妙な感じはします 普通にファイルを作る感じでもいいかもです

nest g resource cats

このコマンドはcontrollerとserviceとmoduleの三つの作成がされるので、便利です。

prisma

Prismaは、Node.js,TypeScript用のオープンソースORMです。 nestjsに対応しているormは豊富ですが

prismaのインストール

npm install prisma --save

https://www.prisma.io/docs/concepts/components/prisma-client/crud#read

npx prisma init

このコマンドは以下のことを行います。

  • prisma/schema.prisma を作成します
  • .envを作成します。

デフォルトではpostgresqlになっていますが、今回は簡単にsqliteで実行します。

[prisma/schema.prisma]

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

[.env]

DATABASE_URL="file:./dev.db"

実際にcrud処理を作ってみます。 schema.prismaにモデルを追加します

model Cat {
  id    Int     @default(autoincrement()) @id
  name  String?
}

migrateを走らせます。

npx prisma migrate dev --name init
npx prisma generate

prismaclientのインストール

npm install @prisma/client --save

nestja-prismaを追加する

nest add nestjs-prisma

spliteを選択したら使えるようになります。

PrismaServiceを動かすためにmoduleを修正します

[src/cats/cats.module.ts]

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { PrismaService } from 'nestjs-prisma';

@Module({
  controllers: [CatsController],
  providers: [CatsService, PrismaService],
})
export class CatsModule {}

[src/cats/cats.service.ts]

import { Injectable } from '@nestjs/common';
import { PrismaService } from 'nestjs-prisma';

@Injectable()
export class CatsService {
  constructor(private prisma: PrismaService) {}
  async getCats() {
    return this.prisma.cat.findFirst();
  }
}

データを確認したいですが、初期値がないので簡単にデータを追加できるprisma studioを起動します。

npx prisma studio

確認で来たらprismaが使えるようになりました

crudについて

アプリケーションの基本機能である、

  • create 新規作成処理
  • read 読み込み処理
  • update 更新処理
  • delete 削除処理

頭文字4つをとって「CRUD」と呼ばれています。

この4つの機能をprismaを使用してハンズオンしようと思います。

新規作成処理(create)

新規作成処理を作成します createする際に、Request型定義をdtoというものを使って定義します。

DTOについて

  • NestJS の DTO は Request Payload(body) の型定義を行うためのもの
  • 型定義と同時にバリデーションまで含むことができる

[src/cats/dto/create-cat.dto.ts]

import { ApiProperty } from '@nestjs/swagger';

export class CreateCatDto {
  @ApiProperty()
  name: string;
}

リクエストのbodyにname: stringを設定しています。 swaggerのドキュメントに追加するために@ApiProperty()を追加しました

createのcontroller

import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    return this.catsService.create(createCatDto);
  }
}
  • @Post()でポストリクエストを受け場合に起動します。

  • constructor(private readonly catsService: CatsService) {}ここの記述でcatsServiceを注入しています。

  • リクエストにdtoを設定して

  • 設定した値をそのままserviceに送っています。

  • 処理自体はサービスで行います。

createのservice

import { Injectable } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { PrismaService } from 'nestjs-prisma';

@Injectable()
export class CatsService {
  constructor(private prisma: PrismaService) {}
  async create(createCatDto: CreateCatDto) {
    return this.prisma.cat.create({
      data: createCatDto,
    });
  }
 }

asyncをしないとprismaの処理を待たずに終了するので、つけました。 今回はリクエストの中身をそのまま送信できるのでdataの中身がdtoになりましたが、変換処理などをおこなう場合はserviceで行えばいいと思います。

読み込み処理(read)

readには一覧を表示する機能と詳細を表示する機能が必要なシーンが多いので基本的なサンプルコードを作成しました。

読み込み処理のcontroller

 @Get()
  async findAll() {
    return this.catsService.findAll();
  }

  @Get(':id')
  async findOne(@Param('id') id: string) {
    return this.catsService.findOne(+id);
  }

上のメソッドが一覧で下が一件を取り出すメソッドです。

読み込み処理のservice

  async findAll() {
    return this.prisma.cat.findMany();
  }

  async findOne(id: number) {
    return this.prisma.cat.findUnique({
      where: {
        id: id,
      },
    });
  }

読み込み処理はprisma.cat.findManyprisma.cat.findUniqueですね。

### 更新処理

猫ちゃんの名前を変更したい場合の処理ですね。

更新処理のdto

[src/cats/dto/update-cat.dto.ts]

import { PartialType } from '@nestjs/swagger';
import { CreateCatDto } from './create-cat.dto';

export class UpdateCatDto extends PartialType(CreateCatDto) {}

更新するにはidとnameが必要ですがcreatedtoの内容を継承してるのでupdateもこのまま更新しようかと思います。

更新処理のcontroller

  @Patch(':id')
  async update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
    return this.catsService.update(+id, updateCatDto);
  }

更新処理のservice

  async update(id: number, updateCatDto: UpdateCatDto) {
    return this.prisma.cat.update({
      where: {
        id: id,
      },
      data: updateCatDto,
    });
  }

更新処理はprisma.cat.updateですね

このように、idとupdateCatDtoを分けていることで、更新処理もすんなり通せました。

削除処理

削除処理のcontroller

  @Delete(':id')
  async remove(@Param('id') id: string) {
    return this.catsService.remove(+id);
  }

削除処理のservice

  async remove(id: number) {
    return this.prisma.cat.delete({
      where: {
        id: id,
      },
    });
  }

削除処理は prisma.cat.deleteです。

全体

[ディレクトリ構造]

src/cats
├── cats.controller.ts
├── cats.module.ts
├── cats.service.ts
└── dto
    ├── create-cat.dto.ts
    └── update-cat.dto.ts

[src/cats/cats.controller.ts]

import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
import { UpdateCatDto } from './dto/update-cat.dto';

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    return this.catsService.create(createCatDto);
  }

  @Get()
  async findAll() {
    return this.catsService.findAll();
  }

  @Get(':id')
  async findOne(@Param('id') id: string) {
    return this.catsService.findOne(+id);
  }

  @Patch(':id')
  async update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
    return this.catsService.update(+id, updateCatDto);
  }

  @Delete(':id')
  async remove(@Param('id') id: string) {
    return this.catsService.remove(+id);
  }
}

[src/cats/cats.service.ts]

import { Injectable } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { UpdateCatDto } from './dto/update-cat.dto';
import { PrismaService } from 'nestjs-prisma';

@Injectable()
export class CatsService {
  constructor(private prisma: PrismaService) {}
  async create(createCatDto: CreateCatDto) {
    return this.prisma.cat.create({
      data: createCatDto,
    });
  }

  async findAll() {
    return this.prisma.cat.findMany();
  }

  async findOne(id: number) {
    return this.prisma.cat.findUnique({
      where: {
        id: id,
      },
    });
  }

  async update(id: number, updateCatDto: UpdateCatDto) {
    return this.prisma.cat.update({
      where: {
        id: id,
      },
      data: updateCatDto,
    });
  }

  async remove(id: number) {
    return this.prisma.cat.delete({
      where: {
        id: id,
      },
    });
  }
}

まとめ

nestjsのインストールからprismaを使用して、crud機能の作成までしました

この先にやることは

  • ログイン機能
  • swaggerのドキュメントをもう少し書く
  • バリデーションの実装

などですね。 nodejsでバックエンドの作成ができるようになったのは素晴らしいことだと感じています。

Nakano
Nakano
Back-end engineer

AWS,Rails,UE4,vue.js,hugo,その他なんでもやりたい