Closed18

nest+prismaめも

ながたいちこながたいちこ

nest.js

  • module単位で作成する。
  • moduleにはcontrollerとServiceを持っている。
  • controllerはルーティング、Serviceはビジネスロジックの実装を担当する。
ながたいちこながたいちこ

controller

  • ルーティングを担当する。

ルーティング

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

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

catsでGETリクエストをすると、findAllが呼び出される。
他にもPostなどが使用できる。
詳細はリファレンスを参照すること
https://docs.nestjs.com/controllers

リクエストオブジェクト

  • API呼び出しの際に受け取るパラメータ。
  • @Paramアノテーションなどを使用する。
import { Controller, Get, Param } from "@nestjs/common";

@Controller("api/author")
export class AuthorController {
  @Get(":id")
  findAuthor(@Param() params): string {
    return `author${params.id}`;
  }
}

他アノテーションはリファレンスを参照
https://docs.nestjs.com/controllers

ステータスコード

  • レスポンスのステータスコード
@Post()
@HttpCode(204)
create() {
  return 'This action adds a new cat';
}

リダイレクト

  • 別のところに飛ばす
@Get()
@Redirect('https://nestjs.com', 301)

動的なルーティング

  • リクエストとして動的なデータを受け入れる場合があるcats/id/1のような
  • ルート定義からパラメータをチュソクするには@Paramを使用する。
@Get(':id')
findOne(@Param() params): string {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}
ながたいちこながたいちこ

スコープ

  • 全体共有とリクエスト単位と常に新しいインスタンスを作成するものがある。
  • デフォルトは全体共有
  • スコープが参照範囲というよりも変数のライフサイクルみたいな説明になってる。
  • 基本的に気にしなくてよさそう。

非同期

  • asyncで非同期、かならずPromiseを返却する必要がある。
@Get()
async findAll(): Promise<any[]> {
  return [];
}
  • 他にもObservableを返却し、処理が完了し次第値を受け取る方式もある。
@Get()
findAll(): Observable<any[]> {
  return of([]);
}

ペイロード

  • リクエストで関数を呼び出す時に引数に指定するクラスみたいなやつ。
  • Dtoスキーマを作成する必要がある。
export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

@Post()
async create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}

ながたいちこながたいちこ

Nest.js providers

Nestの基本的な概念の一つ。
DI
オブジェクトやインスタンスの配線、アクセスの提供。
依存性の注入を行う。

ながたいちこながたいちこ

Service

  • @ InjectableをつけるとServiceクラスをProviderとして使用できる。
import { Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {
  constructor() {
    this.cats = [];
  }

  create(cat) {
    this.cats.push(cat);
  }

  findAll() {
    return this.cats;
  }
}
  • Interfaceを噛ませることもできる
export interface Cat {
  name: string;
  age: number;
  breed: string;
}
  • コンストラクタの引数に指定する
import { Controller, Get, Post, Body, Bind, Dependencies } from '@nestjs/common';
import { CatsService } from './cats.service';

@Controller('cats')
@Dependencies(CatsService)
export class CatsController {
  constructor(catsService) {
    this.catsService = catsService;
  }

  @Post()
  @Bind(Body())
  async create(createCatDto) {
    this.catsService.create(createCatDto);
  }

  @Get()
  async findAll() {
    return this.catsService.findAll();
  }
}
ながたいちこながたいちこ

オプションのProvider

  • Providerが必ず必要ではない場合。@Optionalを使用する。
import { Injectable, Optional, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}

プロパティベースの注入

  • プロパティに直接注入する方法
  • @Injectをつける
import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  @Inject('HTTP_OPTIONS')
  private readonly httpClient: T;
}
import { Injectable, Inject } from '@nestjs/common';
import { MyService } from './my.service';

@Injectable()
export class ExampleService {
  @Inject()
  private readonly myService: MyService;
}

Provider登録

  • Providerの定義後、Moduleにて追加する。
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class AppModule {}
ながたいちこながたいちこ

Nest.js Module

  • 必ず1つ以上作成する。
  • @Moduleをつける。
  • providers Nest インジェクターによってインスタンス化され、少なくともこのモジュール全体で共有できるプロバイダー
  • controllers このモジュールで定義され、インスタンス化する必要があるコントローラーのセット
  • imports このモジュールで必要なプロバイダーをエクスポートするインポートされたモジュールのリスト
  • exports そのサブセットはprovidersこのモジュールによって提供され、このモジュールをインポートする他のモジュールで利用できるはずです。プロバイダー自体またはそのトークン (provide値)のみを使用できます。
ながたいちこながたいちこ

機能Moduleと共有Module

NestJSでは、モジュールはアプリケーションの特定の機能をカプセル化するために使用されます。モジュールは、関連するコントローラー、プロバイダー、その他のコードを一緒にグループ化します。モジュールは大きく分けて2つの種類があります:機能モジュールと共有モジュール。

  1. 機能モジュール: これはアプリケーションの特定の機能をカプセル化するモジュールです。例えば、ユーザー管理、商品管理、注文管理などの機能を持つeコマースアプリケーションでは、それぞれの機能に対応するモジュール(UsersModuleProductsModuleOrdersModuleなど)を作成することが一般的です。

  2. 共有モジュール: これはアプリケーション全体で共有される機能をカプセル化するモジュールです。共有モジュールは、複数の機能モジュールから使用されるサービス、ヘルパー、ユーティリティなどを提供します。

これらのモジュールは、通常、フォルダ階層を使って整理されます。各モジュールは自身のディレクトリを持ち、そのディレクトリ内にはモジュールに関連するコントローラー、サービス、その他のファイルが含まれます。これにより、コードベースが大きくなっても、関連するコードが一緒にグループ化されているため、管理と理解が容易になります。

例えば、以下のようなディレクトリ構造を考えてみましょう:

src/
├── app.module.ts
├── app.controller.ts
├── app.service.ts
├── users/
│   ├── users.module.ts
│   ├── users.controller.ts
│   ├── users.service.ts
├── products/
│   ├── products.module.ts
│   ├── products.controller.ts
│   ├── products.service.ts
└── shared/
    ├── shared.module.ts
    ├── logger.service.ts

この例では、usersproductsディレクトリはそれぞれUsersModuleProductsModule(機能モジュール)を表し、sharedディレクトリはSharedModule(共有モジュール)を表しています。

ながたいちこながたいちこ

動的Module

  • アプリケーション起動時に動的に実行されるようなModule。
  • よくわからない。別の章でもっと教えてくれるらしい。

NestJSでは、モジュールは通常、静的に定義され、アプリケーションの起動時にロードされます。しかし、場合によっては、実行時にモジュールの設定を動的に変更する必要があります。これが動的モジュールの役割です。

動的モジュールは、forRoot()またはforFeature()といった静的メソッドを提供することで、実行時にモジュールの設定を提供できます。これらのメソッドは、通常、設定オブジェクトを引数として受け取り、モジュールを返します。

例えば、データベースモジュールがあるとします。このモジュールは、データベース接続の詳細(ホスト名、データベース名、ユーザー名、パスワードなど)を設定する必要があります。これらの設定は、実行環境(開発、テスト、本番など)によって異なる可能性があります。このような場合、動的モジュールを使用して、実行時に設定を提供できます。

以下に、動的モジュールの一例を示します:

@Module({
  imports: [
    DatabaseModule.forRoot({
      host: process.env.DB_HOST,
      database: process.env.DB_NAME,
      username: process.env.DB_USER,
      password: process.env.DB_PASS,
    }),
  ],
})
export class AppModule {}

この例では、DatabaseModuleは動的モジュールで、forRoot()メソッドを通じてデータベース接続の設定を受け取ります。この設定は、環境変数から取得され、アプリケーションの実行時に提供されます。

ながたいちこながたいちこ

middleWare

  • サイトとサーバー側の間に入る
ながたいちこながたいちこ

ミドルウェアは、ソフトウェアのアーキテクチャにおいて、特定の処理を行うための中間層を指します。特にWeb開発の文脈では、ミドルウェアはHTTPリクエストとレスポンスの間の処理を行うソフトウェアコンポーネントを指すことが多いです。

NestJSにおけるミドルウェアは、特定のルート(または一連のルート)に対するすべてのインバウンドリクエストを処理する関数です。ミドルウェアは、リクエストとレスポンスオブジェクトにアクセスでき、リクエスト/レスポンスサイクルの中で実行されるルートハンドラーの前後に自分自身のコードを実行できます。

ミドルウェアは、以下のような用途で使用されます:

  • リクエストのログ記録
  • ユーザー認証と認可
  • データのバリデーションとサニタイズ
  • エラーハンドリング
  • CORSの設定

NestJSでは、ミドルウェアは通常、@Injectable()デコレーターを使用して定義され、use()メソッドを実装します。このuse()メソッドは、リクエストとレスポンスオブジェクト、そして次のミドルウェアまたはルートハンドラーへの参照を引数として受け取ります。

以下に、NestJSのミドルウェアの一例を示します:

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...', req.method, req.originalUrl);
    next();
  }
}

このLoggerMiddlewareは、すべてのインバウンドリクエストのHTTPメソッドとURLをログに記録します。

ながたいちこながたいちこ

Nest 例外

  • 初めからいろいろな例外が用意されている。
// /src/users/users.controller.ts

import { Controller, Get, HttpException, HttpStatus } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}
  @Get()
  getUser(): string {
    return this.usersService.getUser();
  }
  @Get('throw')
  getException(): string {
    throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
  }
  @Get('override')
  getOverRideException(): string {
    throw new HttpException({ status: HttpStatus.FORBIDDEN, error: 'override custom' }, HttpStatus.FORBIDDEN,
  );
  }
}
  • 独自の例外を作成する場合は、HttpExceptionを継承したクラスを作成する必要がある。
// /src/exception/forbidden.exception.ts

import { HttpException, HttpStatus } from '@nestjs/common';
export class ForbiddenException extends HttpException {
  constructor() {
    super('custom exception', HttpStatus.FORBIDDEN);
  }
}
ながたいちこながたいちこ

Nest Pipe

  • パイプはデータの変換やバリデーションに使用する。
  • Providerと同じく@Injectableをつけるが、別物。
  • Injectableは依存性注入システムによってインスタンスができるという意味。
  • パイプとプロバイダーはインスタンス化したものの使用方法が異なる。
    • プロバイダー: プロバイダーは、サービス、リポジトリ、ファクトリなど、アプリケーションのさまざまな部分を提供します。これらはアプリケーション全体で再利用可能なコードをカプセル化し、モジュール間で共有します。プロバイダーは、アプリケーションのビジネスロジックを実装するために主に使用されます。
    • パイプ: パイプは、リクエストハンドラーが呼び出される前に実行され、リクエストハンドラーに渡される引数を変換、検証、または拡張することができます。パイプは、主にデータの変換やバリデーションに使用されます。

たとえば、以下のコードは、ParseIntPipeを使用して、ルートパラメータのidを文字列から数値に変換します:

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

@Controller('cats')
export class CatsController {
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    // idは数値として保証されます
  }
}

ながたいちこながたいちこ

Nest guard

  • middlewearでは行えない特定のルートへのアクセス制御を行う機能。
  • くらいあんと→middle→guard→ルートという流れでアクセスする。
  • ガードは主に認証と認可の目的で使用されます。たとえば、特定のルートが認証済みのユーザーにのみアクセス可能であるべき場合、ガードを使用してその制御を行うことができます。
  • ガードは、CanActivateインターフェースを実装するクラスとして定義されます。このインターフェースは、単一のメソッドcanActivate()を持ちます。このメソッドは、実行コンテキストを引数として受け取り、真偽値またはPromiseまたはObservableを返します。canActivate()がtrueまたはtrueを解決または発行するPromiseまたはObservableを返す場合、リクエストは処理を続行します。それ以外の場合、リクエストは拒否されます。
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    // ここで認証のロジックを実装します
    return request.user != null; // ユーザーが認証済みである場合にtrueを返します
  }
}

ながたいちこながたいちこ

Nest インターセプター

  • 特定のルートハンドラの実行前後に追加のロジックを実行するクラス。
    • レスポンスの変換
    • 非同期操作の管理
    • パフォーマンスの計測
    • ログの記録
    • observerble
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...');
    const now = Date.now();
    return next
      .handle()
      .pipe(
        tap(() => console.log(`After... ${Date.now() - now}ms`)),
      );
  }
}

適応する

import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { LoggingInterceptor } from './logging.interceptor';

@Controller('cats')
export class CatsController {
  @Get()
  @UseInterceptors(LoggingInterceptor)
  findAll() {
    // ...
  }
}

ながたいちこながたいちこ

Prisma

Prismaってなに?

  • Node.jsとTypeScriptで使用できるORM。
    • ORM(Object-Relational Mapping):データベースとの関係を抽象化し、プログラム上で扱いやすくするもの
  • Prisma Client:タイプセーフなクエリビルダー
  • PrismaMigrate:移行システム
  • PrismaStudio:データベースの内容を表示するGUI

DB定義方法

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

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

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  author    User?   @relation(fields: [authorId], references: [id])
  authorId  Int?
}

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}

Clientを使用したDBアクセス

  1. Clientの生成
npm install @prisma/client
prisma generate
  1. インスタンス作成
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
  1. アクセス
// Run inside `async` function
const allUsers = await prisma.user.findMany({
  include: { posts: true },
})
ながたいちこながたいちこ

環境構築

  1. データベースを作る/アクセス設定を行う
  2. Prismaをインストールする
    npm install prisma --save-dev
  3. Schema.prismaファイルにモデルデータを追加する。
 id    Int     @id @default(autoincrement())
 email String  @unique
 name  String?
 posts Post[]
}

model Post {
 id        Int     @id @default(autoincrement())
 title     String
 content   String?
 published Boolean @default(false)
 author    User    @relation(fields: [authorId], references: [id])
 authorId  Int
}```

4. Migrateを実行

npx prisma migrate dev --name init

ながたいちこながたいちこ

既存のDBからスキーマを抽出

以下のコマンドを実行することで既存のDBからテーブル定義などを抽出し、schema.prismaに反映できる。

npx prisma db pull
このスクラップは1ヶ月前にクローズされました