LoginSignup
0

NestJSに入門する

Last updated at Posted at 2023-12-25

NestJSに入門します。

プロジェクトを新しく作成する

> npm i -g @nestjs/cli
> nest new nestjs-trial
⚡  We will scaffold your app in a few seconds..

? Which package manager would you ❤️  to use? npm
CREATE nestjs-trial/.eslintrc.js (663 bytes)
CREATE nestjs-trial/.prettierrc (51 bytes)
CREATE nestjs-trial/README.md (3340 bytes)
CREATE nestjs-trial/nest-cli.json (171 bytes)
CREATE nestjs-trial/package.json (1945 bytes)
CREATE nestjs-trial/tsconfig.build.json (97 bytes)
CREATE nestjs-trial/tsconfig.json (546 bytes)
CREATE nestjs-trial/src/app.controller.ts (274 bytes)
CREATE nestjs-trial/src/app.module.ts (249 bytes)
CREATE nestjs-trial/src/app.service.ts (142 bytes)
CREATE nestjs-trial/src/main.ts (208 bytes)
CREATE nestjs-trial/src/app.controller.spec.ts (617 bytes)
CREATE nestjs-trial/test/jest-e2e.json (183 bytes)
CREATE nestjs-trial/test/app.e2e-spec.ts (630 bytes)

✔ Installation in progress... ☕

🚀  Successfully created project test
👉  Get started with the following commands:

$ cd test
$ npm run start


                          Thanks for installing Nest 🙏
                 Please consider donating to our open collective
                        to help us maintain this package.


               🍷  Donate: https://opencollective.com/nest
ディレクトリの状態
> cd nestjs-trial
> tree -I node_modules
.
├── 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

これで、/へアクセスするとHello World!が表示されます。

まずはControllerを作ってみる

> nest g controller cats
CREATE src/cats/cats.controller.spec.ts (478 bytes)
CREATE src/cats/cats.controller.ts (97 bytes)
UPDATE src/app.module.ts (322 bytes)
cats.controller.ts
import { Controller, Get } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats' 
  }
}

これで、/catsへアクセスするとThis action returns all catsが表示されます。

新しいリソースを作ってみる

> nest g resource dogs
? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? Yes
CREATE src/dogs/dogs.controller.spec.ts (556 bytes)
CREATE src/dogs/dogs.controller.ts (873 bytes)
CREATE src/dogs/dogs.module.ts (241 bytes)
CREATE src/dogs/dogs.service.spec.ts (446 bytes)
CREATE src/dogs/dogs.service.ts (595 bytes)
CREATE src/dogs/dto/create-dog.dto.ts (29 bytes)
CREATE src/dogs/dto/update-dog.dto.ts (165 bytes)
CREATE src/dogs/entities/dog.entity.ts (20 bytes)
UPDATE package.json (1978 bytes)
UPDATE src/app.module.ts (381 bytes)
✔ Packages installed successfully.
ディレクトリの状態
> tree -I node_modules
.
├── README.md
├── nest-cli.json
├── package-lock.json
├── package.json
├── src
│   ├── app.controller.spec.ts
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   ├── cats
│   │   ├── cats.controller.spec.ts
│   │   └── cats.controller.ts
│   ├── dogs
│   │   ├── dogs.controller.spec.ts
│   │   ├── dogs.controller.ts
│   │   ├── dogs.module.ts
│   │   ├── dogs.service.spec.ts
│   │   ├── dogs.service.ts
│   │   ├── dto
│   │   │   ├── create-dog.dto.ts
│   │   │   └── update-dog.dto.ts
│   │   └── entities
│   │       └── dog.entity.ts
│   └── main.ts
├── test
│   ├── app.e2e-spec.ts
│   └── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json

DBへ接続してみる

prismaを使ってみます。

> npm i prisma --save-dev
> npx prisma init
✔ Your Prisma schema was created at prisma/schema.prisma
  You can now open it in your favorite editor.

warn You already have a .gitignore file. Don't forget to add `.env` in it to not commit any private information.

Next steps:
1. Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started
2. Set the provider of the datasource block in schema.prisma to match your database: postgresql, mysql, sqlite, sqlserver, mongodb or cockroachdb.
3. Run prisma db pull to turn your database schema into a Prisma schema.
4. Run prisma generate to generate the Prisma Client. You can then start querying your database.

More information in our documentation:
https://pris.ly/d/getting-started

dockerでpostgresqlを立ち上げてそこに接続してみます。

compose.yml
services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: postgres
      TZ: 'Asia/Tokyo'
    ports:
      - 5432:5432
    volumes:
      - ./postgres/data:/var/lib/postgresql/data

下記を実行します。

docker compose up

DBが立ち上がります。

prismaにDogモデルを定義してみます。
環境変数にDATABASE_URLも定義します。

schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

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

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

model Dog {
  id        String   @id @default(uuid())
  name      String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
.env
DATABASE_URL="postgresql://postgres:password@localhost:5432/postgres"

下記を実行しマイグレーションファイルの作成、DBに対して実行します。

> npx prisma migrate dev --name init
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "postgres", schema "public" at "localhost:5432"

Applying migration `20231220071137_init`

The following migration(s) have been created and applied from new schema changes:

migrations/
  └─ 20231220071137_init/
    └─ migration.sql

Your database is now in sync with your schema.

Running generate... (Use --skip-generate to skip the generators)

added 1 package, and audited 730 packages in 3s

130 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

✔ Generated Prisma Client (v5.7.1) to ./node_modules/@prisma/client in 45ms
prismaディレクトリの状態
> tree prisma
prisma
├── migrations
│   ├── 20231220071137_init
│   │   └── migration.sql
│   └── migration_lock.toml
└── schema.prisma

PrismaClientを作成しDBへ接続します。

npm i @prisma/client
prisma.servise.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }
}

dogsをDBから取得します。

dogs.service.ts
import { Injectable } from '@nestjs/common';
import { CreateDogDto } from './dto/create-dog.dto';
import { UpdateDogDto } from './dto/update-dog.dto';
import { PrismaService } from './../prisma.service'
import { Dog, Prisma } from '@prisma/client';

@Injectable()
export class DogsService {
  constructor(private prisma: PrismaService) {}

  createDog(createDogDto: CreateDogDto) {
   return 'This action adds a new dog';
  }

  async dogs(params: {
    skip?: number;                                                            
    take?: number;
    cursor?: Prisma.DogWhereUniqueInput;
    where?: Prisma.DogWhereInput;
    orderBy?: Prisma.DogOrderByWithRelationInput;
  }): Promise<Dog[]> {
    const { skip, take, cursor, where, orderBy } = params;
    return this.prisma.dog.findMany({
      skip,
      take,
      cursor,
      where,
      orderBy,
    });
  }

  dog(id: number) {
    return `This action returns a #${id} dog`;
  }

  updateDog(id: number, updateDogDto: UpdateDogDto) {
    return `This action updates a #${id} dog`;
  }

  deleteDog(id: number) {
    return `This action removes a #${id} dog`;
  }
}

OpenAPI使ってみる

npm i --save @nestjs/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('nestjs-trial')
    .setDescription('This is NestJS trial.')
    .setVersion('1.0.0')
    .addTag('trial')
    .build()
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document)

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

localhost:3000/apiで下記が見れるようになりました。

FireShot Capture 058 - Swagger UI - localhost.png

http://localhost:3000/api-jsonにアクセスするとjson形式、http://localhost:3000/api-yamlにアクセスするとyaml形式で取得できました。

下記のようにすると

cats.controller.ts
import { Controller, Get } from '@nestjs/common';
+ import { ApiTags } from '@nestjs/swagger';

@Controller('cats')
+ @ApiTags('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats' 
  }
}

こんな感じでタグが表示されます。

他にもいろいろデコレータがあります。
https://docs.nestjs.com/openapi/decorators

バリデーションをしてみる

npm i --save class-validator class-transformer
main.ts
import { NestFactory } from '@nestjs/core';
+ import { ValidationPipe } from '@nestjs/common' 
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('nestjs-trial')
    .setDescription('This is NestJS trial.')
    .setVersion('1.0.0')
    .addTag('trial')
    .build()
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document)

+ app.useGlobalPipes(new ValidationPipe());

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

dogの作成のエンドポイントも作ります。

dogs.service.ts
createDog(data: Prisma.DogCreateInput): Promise<Dog>
  return this.prisma.dog.create({
    data
  });
}
create-dog.dto.ts
import { IsString } from 'class-validator';

export class CreateDogDto {
  @IsString
  name: string;
}

例えば、下記のデータをリクエストすると、

{
    "name": 1
}

下記のエラーが返ってきました。

{
    "message": [
        "name must be a string"
    ],
    "error": "Bad Request",
    "statusCode": 400
}

Render.comにデプロイしてみる

環境変数にNODE_ENV=productionを追加します。

ビルド時にnest: not foundというエラーになったので、

@nestjs/cliをdependenciesへ移しました。

// build command
> npm ci && npm run build
// start command
> npm run start:prod

DBも作成してそこに繋ぎます。

今回はSupabaseを使ってみました。
Settings>Database>Connection stringNode.jsタブにある文字列を環境変数のDATABASE_URLに設定します。

GET /dogsすると、データが取れました。

[
  {
    id: "8cb9e5f3-2444-456e-9998-b8dcef88d739",
    name: "shiba inu",
    createdAt: "2023-12-22T23:38:14.594Z",
    updatedAt: "2023-12-22T23:38:14.594Z"
  }
]

※リポジトリは下記です。

最後に

私の勤めている株式会社mofmofでNestJSとOpenAIを使ったプロダクト開発を行ったので、あらためてNestJSに入門してみました。

ちなみに上記の企画では観光地をおすすめしてくれるサービスをチームで作りました。

あと、今回の話とは関係ないのですが、このNestJSの入門ではいつも使っているVSCodeとSourcetreeを使わずにNeovimとLazygitを使いました。

慣れてないのでとても辛かったです。

思考のスピードでコーディングできるようになりたい。

株式会社mofmofは受託開発会社ですが、メンバー自らがオーナーとなってプロダクト開発にチャレンジしています。
この記事は、NestJS, 生成AIにチャレンジする「金曜日のチーム開発」の成果です。

https://indie-dev.mof-mof.co.jp/

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0