0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【備忘録】NestJS + TypeORM 0.2系 でCRUDするまで 【古いです】

Last updated at Posted at 2024-06-12

はじめに

Nest.js + TypeORM についての 備忘録 です。

環境

Nest V8 なので古いと思いますが、modular architecture を学習する分には大丈夫かと思います。
TypeORM V2 なのでmigration周りが少し違うようです。
いつか書き直します・・・

NestJSのスケルトン作成

nest new ${プロジェクト名} で作成する。

・ex) flea-market というプロジェクト名で作成する。

nest new flea-market

Nestの3つのコア要素

Module -> ControllerService をまとめて Nestに登録する 役割を持つ
Controller -> ルーティングする 役割を持つ
Service -> 実装したいロジックを定義する 役割を持つ

3つのコア要素を使って エンドポイントを作る

Module概要

・ロジックとルーティングをまとめる 論理的な構造 というイメージ

1.classModuleデコレーター をつける。
2.Moduleデコレーターの引数オブジェクト を渡す。
3.オブジェクトのプロパティ
providers -> Injectableデコレータ がついたクラスを渡す。
controllers -> Controllerデコレータ がついたクラスを渡す。
imports -> モジュールの中で使う外部のモジュール を渡す。
exports -> 外部のモジュールに エクスポートしたいもの を渡す。

@Module({
  imports: [ItemsModule],
  controllers: [],
  providers: [],
})

Module作成コマンド

nest g module ${モジュール名} で作成する。

・ex) items というモジュール名で作成する。

nest g module items

Controller概要

ルーティングの機能 を担っている。

1.classControllerデコレーター をつける。
2.Controllerデコレーターの引数文字列 を渡す。
これが パス と コントローラー との紐づけになる。
3.Controllerデコレーター と紐づけた classのメソッド
HTTPメソッドデコレーター をつける。

@Controller('items')
export class ItemsController {
  @Get()
  findAll() {
    return 'これはfindAllメソッドです。';
  }
}

Controller作成コマンド

nest g controller ${コントローラー名} で作成する。

・ex) items というコントローラー名で作成する。

nest g controller items
nest g controller items --no-spec //テスト用のファイルを作成しない

Service概要

実装したいロジックを定義する 役割を持つ

コントローラー から呼び出して、ルーティングと機能 を紐づける。

1.classInjectableデコレーター をつける。
2.Injectableデコレーター と紐づけた classのメソッド
実装したいロジックをメソッドとして 定義する。

@Injectable()
export class ItemsService {
  findAll() {
    return 'これはItemsServiceです。';
  }
}

ControllerからServiceを利用する

1.ModuleprovidersService を登録する。
これにより Nestが勝手にDI してくれるようになる。

@Module({
  controllers: [ItemsController],
  providers: [ItemsService],
})
export class ItemsModule {}

2.Controllerの中にconstructorServiceを引数 に取る。
これにより Controller から Service を利用出来る。

@Controller('items')
export class ItemsController {
  constructor(private readonly itemsService: ItemsService) {}
  @Get()
  findAll() {
    return this.itemsService.findAll();
  }
}

Service作成コマンド

nest g service ${サービス名} で作成する。

・ex) items というコントローラー名で作成する。

nest g service items
nest g service items --no-spec // テスト用のファイルを作成しない

DI(Dependency Injection/依存性の注入)について

クラスAがクラスBに依存している 時に、
Aの中でBをインスタンス化せずに
Aの外Bをインスタンス化 し、Aに渡して実装します。

このように 外部から依存しているオブジェクト渡すパターンDI と呼ぶ。

ダイナミックルーティング + ルーティングパスの取得

HTTPデコレータ に対して
:${パス}の文字列 を渡すと ダイナミックルーティング が出来る。

・ハンドラの引数に
Paramデコレータの引数に渡したダイナミックルーティングのパス
パスの文字列 を引数とすることで取得出来る。

/items/${id} への GETリクエスト の例を下記に示します。

@Controller('items')
export class ItemsController {
  constructor(private readonly itemsService: ItemsService) {}

  // ダイナミックルーティング
    @Get(':id')
    findById(@Param('id') id: string): Item {
      return this.itemsService.findById(id);
    }
}

Post送信のBodyを取得する

・ハンドラの引数に Bodyデコレータ引数 を渡して取得する。

/items への POSTリクエスト の例を下記に示します。

@Controller('items')
export class ItemsController {
  constructor(private readonly itemsService: ItemsService) {}

  @Post()
  create(
    @Body('id') id: string,
    @Body('name') name: string,
    @Body('price') price: number,
    @Body('description') description: string,
  ): Item {
    const item: Item = {
      id,
      name,
      price,
      description,
      status: ItemStatus.ON_SALE,
    };

    return this.itemsService.create(item);
  }
}

DTO(Data Transfer Object)について

データの受け渡しに使われるオブジェクトのこと

・ex)DBとモデル間 でのデータやり取り・リクエストオブジェクト からのデータ受け取り

DTOの作り方

・ex) CreateItemDto というDTOの定義を下記に示します。

export class CreateItemDto {
  id: string;
  name: string;
  price: number;
  description: string;
}

DTOを使ってBodyを取得する

Post送信のBodyを取得する ではデコレータに引数を渡す必要がありますが、
DTOを使ってBodyを取得する時はデコレータに引数を渡す必要がありません。

下記に DTOを使ったBody取得 の例を示します。

@Controller('items')
export class ItemsController {
  constructor(private readonly itemsService: ItemsService) {}

  @Post()
  create(@Body() CreateItemDto: CreateItemDto): Item {
    return this.itemsService.create(CreateItemDto);
  }
}

Nestでのバリデーション実装

・Nestでのバリデーションには pipe を使います。
・pipeでは バリデーション・データの変換 が可能です。

バリデーション用の組み込み関数

ValidationPipe -> 入力のバリデーション
ParseIntPipe -> 入力を整数型に変換する
ParseBoolPipe -> 入力をBoolean型に変換する
ParseUUIDPipe -> 入力をUUID型に変換する
DefaultValuePipe -> 入力がnull・undefinedの時にデフォルト値を与える

バリデーションの適用箇所

ハンドラ に対して適用する
パラメータごと に対して適用する
グローバル に対して適用する

・ex) パラメータごと に対してバリデーションを適用する例を下記に示します。

@Controller('items')
export class ItemsController {
  constructor(private readonly itemsService: ItemsService) {}

  // ダイナミックルーティング
  @Get(':id')
  findById(@Param('id', ParseUUIDPipe) id: string): Item {
    return this.itemsService.findById(id);
  }
}

DTOにバリデーションを実装

バリデーションルール はDTOに実装します。

import { Type } from 'class-transformer';
import { IsInt, IsNotEmpty, IsString, MaxLength, Min } from 'class-validator';

export class CreateItemDto {
  @IsString()
  @IsNotEmpty()
  @MaxLength(40)
  name: string;

  @IsInt()
  @Min(1)
  @Type(() => Number)
  price: number;

  @IsString()
  @IsNotEmpty()
  description: string;
}

・バリデーションは グローバルに適用 します。

async function bootstrap() {
  // 引数に渡したものがルートモジュールになる
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();

レスポンスにビルトインされている例外処理を使う

ダイナミックルーティングされた値 をキーにDBからレコードを取得して、
レスポンスとして返すようなAPIを想定します。

NotFoundException はビルトインされた例外処理であり、
そのまま使用することが出来ます。
その実装例を下記に示します。

@Injectable()
export class ItemsService {
  private items: Item[] = [];

  findById(id: string): Item {
    const found = this.items.find((item) => item.id === id);
    if (!found) {
      throw new NotFoundException();
    }

    return found;
  }
}

・また、ビルトインされた例外処理は公式から見ることが出来ます。

TypeORM

Entity -> テーブルに対応するクラス
Repository -> DB操作に対応するクラス

TypeORMをNestにインストールする

TypeOrmModule をインポートして適用する。

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'postgres',
      password: 'postgres',
      database: 'postgres',
      autoLoadEntities: true,
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

Entityの作成方法

Entityデコレータ をつけたクラスを定義して、その中にカラム名を記述する。

import { ItemStatus } from 'src/items/item-status.enum';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Item {
  // 主キーかつ自動採番であることを示すデコレータ
  @PrimaryGeneratedColumn('uuid')
  id: string;

  // 通常のカラムであることを示すデコレータ
  @Column()
  name: string;

  @Column()
  price: number;

  @Column()
  description: string;

  @Column()
  status: ItemStatus;

  @Column()
  createdAt: string;

  @Column()
  updatedAt: string;
}

Entityを使ってmigrationする

  1. /ormconfig.js でexportする
  2. migrationファイル を作成する
  3. migration を実行する
ormconfig.js
module.exports = {
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  username: 'postgres',
  password: 'postgres',
  database: 'postgres',
  autoLoadEntities: true,
  entities: ["dist/entities/*.entity.js"],
  migrations: ["dist/migrations/*.js"],
  cli: {
    entitiesDir: "src/entities",
    migrationsDir: "src/migrations"
  }
}

・ex) 下記はCreateItemというマイグレーションファイル名で作成している例です。

npx typeorm migration:generate -n CreateItem

・ex) migrationを実行している例です。

npx typeorm migration:run

Repositoryの作成方法

EntityRepositoryデコレータ をつけたクラスを定義する
デコレータ の引数に Entity を渡す
Repository<Entity> を継承する

import { Item } from 'src/entities/item.entity';
import { EntityRepository, Repository } from 'typeorm';

@EntityRepository(Item)
export class ItemRepository extends Repository<Item> {
  async createItem(createitemDto: CreateItemDto): Promise<Item> {
    const { name, price, description } = createitemDto;

    // 親クラスの継承しているメソッドをthisでたどれる
    const item = this.create({
      name,
      price,
      description,
      status: ItemStatus.ON_SALE,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    });

    await this.save(item);

    return item;
  }
}

RepositoryをServiceから使えるようにする

importsTypeOrmmodule.forFeature([${Repository}]) を渡す

import { Module } from '@nestjs/common';
import { ItemsController } from './items.controller';
import { ItemsService } from './items.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ItemRepository } from './item.repository';

@Module({
  imports: [TypeOrmModule.forFeature([ItemRepository])],
  controllers: [ItemsController],
  providers: [ItemsService],
})
export class ItemsModule {}

ServiceRepository を呼び出す
ex) constructor(private readonly itemRepository: ItemRepository) {} の部分です

@Injectable()
export class ItemsService {
  constructor(private readonly itemRepository: ItemRepository) {}

  async create(createItemDto: CreateItemDto): Promise<Item> {
    return await this.itemRepository.createItem(createItemDto);
  }
}

さいごに

学習させていただいたUdemy講座です。

0
0
0

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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?