使用API Gateway + AWS Lambda构建NestJS(第7部分)ORM(用于处理RDB数据库)

    • その1 NestJSやLambdaでのnode.jsの話し

 

    • その2 NestJSのサンプルを動かす、Hello World.

 

    • その3 コントローラーを追加する

 

    • その4 DTOを使う

 

    • その5 CRUD generatorを使い modules,controller, service, dto を生成する

 

    • その6 インターセプタ

 

    • その7 ORMでRDBデータベースを扱う

 

    その8 NestJSをAPI Gateway+AWS Lambdaとしてデプロイ

文件

    • 公式 https://docs.nestjs.com/techniques/database

TypeORM
Prisma

公式は、mysql 使ってます
今回は、公式とは異なるPostgreSQL使ってみます

可以为上面的内容提供一种中文表述,也就是:”TypeORM和Prisma”

    • https://www.prisma.io/docs/concepts/more/comparisons/prisma-and-typeorm

 

    Prismaの方が新しい

在现有的nestjs项目中添加Prisma设置的步骤。

$ npm install -g @nestjs/cli
$ nest new hello-prisma

到目前为止是一个新的情况
我们将继续使用到第六步为止一直使用的“aws-node-typescript-nest”

$ cd aws-node-typescript-nest
$ npm install prisma --save-dev

已安装以下版本

"prisma": "^5.0.0",

安装继续

$ npx prisma init
    以下、initの実行ログ
$  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
    

以下会创建一个文件。

aws-node-typescript-nest/.env
aws-node-typescript-nest/prisma/schema.prisma
    postgresqlは選んでないけど、デフォルトはpostgresqlでファイルが生成される

2. 使用 PostgreSQL Docker 容器进行 PostgreSQL 的设置

    • postgresqlはコンテナを使い、docker composeを使う

 

    • postgresql接続用のIDE・ツールはpgadmin4を使う

 

    postgresqlとpgadminは以下の定義相当
services:
  postgresql:
    image: postgres:15.3
    container_name: postgresql
    ports:
      - 5432:5432
    volumes:
      #- ./contrib/setup.sh:/docker-entrypoint-initdb.d/initdb.sh
      - ./postgres/pgdata:/var/lib/postgresql/data
      - ./postgres/init:/docker-entrypoint-initdb.d
      - ./postgres/config/postgresql.conf:/etc/postgresql/postgresql.conf
    environment:
      POSTGRES_USER: pguser
      POSTGRES_PASSWORD: pgpassword
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8"
      POSTGRES_DB: mydb
      POSTGRES_HOST_AUTH_METHOD: trust
    command: postgres -c log_destination=stderr -c log_statement=all -c log_connections=on -c log_disconnections=on -c 'config_file=/etc/postgresql/postgresql.conf'
    hostname: postgresql
    restart: always

  pgadmin4:
    image: dpage/pgadmin4:latest
    container_name: pgadmin4
    ports:
      - 8000:80
    volumes:
      - ./pgadmin:/var/lib/pgadmin/storage
    environment:
      PGADMIN_DEFAULT_EMAIL: root@myapp.com
      PGADMIN_DEFAULT_PASSWORD: root
    hostname: pgadmin4
    depends_on:
      - postgresql
    restart: always
    コンテナの起動(docker compose利用)
# compose.yml ファイルのある場所に移動
cd xxx 
docker compose up -d
undefined
docker compose down

3. 使用NestJS进行设置,进行PostgreSQL连接配置。


DATABASE_URL="postgresql://pguser:pgpassword@localhost:5432/mydb?schema=public"
    記載する各項目の値
DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public"

在 Prisma 的 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 User {
  id    Int     @default(autoincrement()) @id
  email String  @unique
  name  String?
  posts Post[]
}

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

迁移执行

 npx prisma migrate dev --name init
    postgresqlへの通信ができないと以下のようなエラーになる
$ npx prisma migrate dev --name init
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "mydb", schema "public" at "localhost:5432"

Error: P1001: Can't reach database server at `localhost`:`5432`

Please make sure your database server is running at `localhost`:`5432`.
    アプリ側を別のdev-containerを使っていて、docker composeで管理されていないので アプリのコンテナ -> ホスト -> docker compose管理のコンテナ への接続ができない。回避は https://docs.docker.com/desktop/networking/ を例に
DATABASE_URL="postgresql://pguser:pgpassword@host.docker.internal:5432/mydb?schema=public"
    実行ログ
root ➜ /workspaces/serverless-nest/sls-examples/aws-node-typescript-nest (feature/v3-aws-nest-prisma ✗) $ npx prisma migrate dev --name init
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "mydb", schema "public" at "host.docker.internal:5432"

Applying migration `20230727044313_init`

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

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

Your database is now in sync with your schema.

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

added 2 packages, and audited 1817 packages in 5s

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

47 vulnerabilities (11 low, 17 moderate, 19 high)

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

✔ Generated Prisma Client (5.0.0 | library) to ./node_modules/@prisma/client in 53ms
undefined

5. 安装并生成 Prisma Client,以便添加用于操作 Prisma 数据库的模块。

    • Prisma Clientは、Prismaモデル定義から生成されるタイプセーフのデータベースクライアント

 

    clinetモジュールを追加
npm install @prisma/client
    DB接続用クライアントを生成する
npx prisma generate 
    実行ログ例
$ npx prisma generate
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma

✔ Generated Prisma Client (5.0.0 | library) to ./node_modules/@prisma/client in 53ms
You can now start using Prisma Client in your code. Reference: https://pris.ly/d/client
\```
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
\```

./node_modules/@prisma/client を見ると aws-node-typescript-nest/node_modules/.prisma/client/index.d.ts 一部抜粋


/**
 * Client
**/

import * as runtime from '@prisma/client/runtime/library';
import $Types = runtime.Types // general types
import $Public = runtime.Types.Public
import $Utils = runtime.Types.Utils
import $Extensions = runtime.Types.Extensions

export type PrismaPromise<T> = $Public.PrismaPromise<T>


export type UserPayload<ExtArgs extends $Extensions.Args = $Extensions.DefaultArgs> = {
  name: "User"
  objects: {
    posts: PostPayload<ExtArgs>[]
  }
  scalars: $Extensions.GetResult<{
    id: number
    email: string
    name: string | null
  }, ExtArgs["result"]["user"]>
  composites: {}
}

/**
 * Model User
 * 
 */
export type User = runtime.Types.DefaultSelection<UserPayload>
export type PostPayload<ExtArgs extends $Extensions.Args = $Extensions.DefaultArgs> = {
  name: "Post"
  objects: {
    author: UserPayload<ExtArgs> | null
  }
  scalars: $Extensions.GetResult<{
    id: number
    title: string
    content: string | null
    published: boolean | null
    authorId: number | null
  }, ExtArgs["result"]["post"]>
  composites: {}
}

/**
 * Model Post
 * 
 */
export type Post = runtime.Types.DefaultSelection<PostPayload>

...

创建PrismaService

npx prisma generate または、 https://docs.nestjs.com/recipes/prisma の use-prisma-client-in-your-nestjs-services のソースを作成する

import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

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

7. 使PrismaService可在模块等中使用,并进行依赖注入

aws-node-typescript-nest/src/users/users.service.tsにconstructor追加、findOne() は asyncにしていなれば asyncにする。DBアクセスをawaitにできないとDBアクセス待たずに処理が次にいってしまう。

import { PrismaService } from '../prisma.service';
...

export class UsersService {
  constructor(private prisma: PrismaService) { }


  async findOne(id: number) {
    const user = await this.prisma.user.findUnique({
      where: {
        id,
      },
    });
    console.log({ user })
    return `This action returns a #${id} user`;
  }

aws-node-typescript-nest/src/users/users.module.tsにprovidersでPrismaServiceを追加

import { PrismaService } from '../prisma.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService, PrismaService]
})
export class UsersModule { }
    app.module.ts は UsersModuleをimportsするため変更なし
@Module({
  imports: [UsersModule],
  controllers: [AppController, CatsController],
  providers: [AppService, { provide: APP_INTERCEPTOR, useClass: LoggingInterceptor, }],
})
export class AppModule { }

将数据存入PostgreSQL数据库中。

    簡単なINSERT文の例
INSERT INTO public."User"(
	id, email, name)
	VALUES (101, 'root-101@example.com', '101-name');

INSERT INTO public."User"(
	id, email, name)
	VALUES (102, 'root-102@example.com', '102-name');

尝试向NestJS应用程序发送请求。

npm startで起動しておき

curlやpostmanで /users/{:id} にGETリクエストを送る

URL 例、http://localhost:3000/dev/users/101, http://localhost:3000/dev/users/102

コンソールにデータベースから取得した情報が表示される

{ user: { id: 101, email: 'root-101@example.com', name: '101-name' } }
{ user: { id: 102, email: 'root-102@example.com', name: '102-name' } }

    power shell からの curl実行した例
> curl  http://localhost:3000/dev/users/101

                                                                                                                        StatusCode        : 200                                                                                                 StatusDescription : OK                                                                                                  Content           : This action returns a #101 user
RawContent        : HTTP/1.1 200 OK
                    Connection: keep-alive
                    Keep-Alive: timeout=5
                    Accept-Ranges: bytes
                    Content-Length: 31
                    Cache-Control: no-cache
                    Content-Type: text/html; charset=utf-8
                    Date: Sat, 29 Jul 2023 05:33:...
Forms             : {}
Headers           : {[Connection, keep-alive], [Keep-Alive, timeout=5], [Accept-Ranges, bytes], [Content-Length, 31]...
                    }
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 31



> curl  http://localhost:3000/dev/users/102


StatusCode        : 200
StatusDescription : OK
Content           : This action returns a #102 user
RawContent        : HTTP/1.1 200 OK
                    Connection: keep-alive
                    Keep-Alive: timeout=5
                    Accept-Ranges: bytes
                    Content-Length: 31
                    Cache-Control: no-cache
                    Content-Type: text/html; charset=utf-8
                    Date: Sat, 29 Jul 2023 05:33:...
Forms             : {}
Headers           : {[Connection, keep-alive], [Keep-Alive, timeout=5], [Accept-Ranges, bytes], [Content-Length, 31]...
                    }
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 31

个人的想法

    • schema定義してマイグレーションを実行すると、Rails や Django に似てると思う。LL言語・スクリプト言語はおおよそ似てくるのか?

 

    • 久しぶりにORMを使った

日頃は、ORMに関連しなかったり、DynamoDBだったりしていたのでORMをゼロから触ったのは2016年頃のDBFlute、2019年頃のDjango 依頼かもしれない

Amplify での schema.graphql もAppSyncのGraphQLでいくつかgenerateされる
NestJSでPrismaの場合にフロントエンド用のts自動生成はあるのか?

bannerAds