1
0

More than 1 year has passed since last update.

NestJSをAPI Gateway + AWS Lambdaで構成する(その7)ORM(RDBデータベースを扱う)

Last updated at Posted at 2023-08-13

ドキュメント

TypeORMとPrisma

1. Prismaセットアップ、既存 nestjsのプロジェクトに追加する

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

ここまでは新規の場合
その6までで使ってきた「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のセットアップ(postgresql dockerコンテナ利用)

  • postgresqlはコンテナを使い、docker composeを使う
  • postgresql接続用のIDE・ツールはpgadmin4を使う
  • postgresqlとpgadminは以下の定義相当
compose.yml
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
  • pgadmin4へアクセス

    • http://localhost:8000/
    • compose.ymlのpgadmin4のenvironmentで指定する
    • ログインroot@myapp.com と パスワードroot で ログインする
  • compose.ymlのenvironmentで指定している情報で接続します

    • hostname :postgresql
    • port : 5432
    • Maintenace databasename:mydb POSTGRES_DBとして指定した値
    • Username:POSTGRES_USERとして指定した値
    • Password:POSTGRES_PASSWORDとして指定した値
  • コンテナ停止する場合は以下コマンドで可能

docker compose down

3. nestjs側の設定 postgresql接続設定

aws-node-typescript-nest/.env

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

4. Prismaのschema.prismaに2つのテーブル定義を記載して、migrateをしてみる

aws-node-typescript-nest/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?
}

migrate実行

 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
  • postgres側のpublicスキーマを参照するとテーブルが作成されている

5. Prsimaのデータベース操作用のモジュールを追加する、Install and generate Prisma Client

  • 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 一部抜粋
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>

...

6. PrismaServiceを作成する

aws-node-typescript-nest/src/prisma.service.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();
  }
}

7. PrismaServiceをモジュール等で使えるようにする、そしてDIする

  • aws-node-typescript-nest/src/users/users.service.tsにconstructor追加、findOne() は asyncにしていなれば asyncにする。DBアクセスをawaitにできないとDBアクセス待たずに処理が次にいってしまう。
users.service.ts
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を追加
users.module.ts
import { PrismaService } from '../prisma.service';

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

8. PostgreSQLデータベースにデータを入れておく

  • 簡単なINSERT文の例
insert.sql
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');

9. NestJSのアプリケーションにリクエストを送ってみる

{ 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自動生成はあるのか?
1
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
1
0