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などが使用できる。
詳細はリファレンスを参照すること
リクエストオブジェクト
- 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}`;
}
}
他アノテーションはリファレンスを参照
ステータスコード
- レスポンスのステータスコード
@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つの種類があります:機能モジュールと共有モジュール。
-
機能モジュール: これはアプリケーションの特定の機能をカプセル化するモジュールです。例えば、ユーザー管理、商品管理、注文管理などの機能を持つeコマースアプリケーションでは、それぞれの機能に対応するモジュール(
UsersModule
、ProductsModule
、OrdersModule
など)を作成することが一般的です。 -
共有モジュール: これはアプリケーション全体で共有される機能をカプセル化するモジュールです。共有モジュールは、複数の機能モジュールから使用されるサービス、ヘルパー、ユーティリティなどを提供します。
これらのモジュールは、通常、フォルダ階層を使って整理されます。各モジュールは自身のディレクトリを持ち、そのディレクトリ内にはモジュールに関連するコントローラー、サービス、その他のファイルが含まれます。これにより、コードベースが大きくなっても、関連するコードが一緒にグループ化されているため、管理と理解が容易になります。
例えば、以下のようなディレクトリ構造を考えてみましょう:
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
この例では、users
とproducts
ディレクトリはそれぞれUsersModule
とProductsModule
(機能モジュール)を表し、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アクセス
- Clientの生成
npm install @prisma/client
prisma generate
- インスタンス作成
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
- アクセス
// Run inside `async` function
const allUsers = await prisma.user.findMany({
include: { posts: true },
})
環境構築
- データベースを作る/アクセス設定を行う
- Prismaをインストールする
npm install prisma --save-dev
- 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