- その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
- 公式は、mysql 使ってます
- 今回は、公式とは異なるPostgreSQL使ってみます
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
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を作成する
-
npx prisma generate
または、 https://docs.nestjs.com/recipes/prisma のuse-prisma-client-in-your-nestjs-services
のソースを作成する
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のアプリケーションにリクエストを送ってみる
-
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自動生成はあるのか?