vi docker-compose.yml
version: "3.8"
services:
db:
container_name: myapp-api-db
image: mysql:8.0
restart: always
ports:
- 13306:3306
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: myapp_api_db
MYSQL_USER: myapp_api_db
MYSQL_PASSWORD: db_password
volumes:
- ./db/data:/var/lib/mysql
npm i -g @nestjs/cli
nest new myapp-api
cd myapp-api
npm i @prisma/client
npm i -D prisma
npx prisma init
vi prisma/schema.prisma
prisma/schema.prisma を以下の内容で保存
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
src/prisma.service.ts を以下の内容で保存
import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close();
});
}
}
vi .env
.env を以下の内容で保存
DATABASE_URL="mysql://myapp_api_db:db_password@localhost:13306/myapp_api_db?schema=public"
次のような簡単なリレーションを持つテーブルを作成します
vi prisma/schema.prisma
model User {
id Int @id @default(autoincrement())
email String @db.Text
name String @db.Text
createdAt DateTime @default(now()) @db.Timestamp(0)
updatedAt DateTime @default(now()) @updatedAt @db.Timestamp(0)
source Source[] @relation("source_fk_1")
// translation Translation[] @relation("translation_fk_2")
}
model Source {
id Int @id @default(autoincrement())
userId Int
user User @relation(name: "source_fk_1", fields: [userId], references: [id])
text String @db.Text
translation Translation[] @relation("translation_fk_1")
}
model Translation {
id Int @id @default(autoincrement())
sourceId Int
source Source @relation(name: "translation_fk_1", fields: [sourceId], references: [id])
// userId Int
// user User @relation(name: "translation_fk_2", fields: [userId], references: [id])
locale String @db.VarChar(8)
text String @db.Text
}
docker ps
docker exec -it <コンテナ名> bash
mysql -uroot -p mysql
create user translate_api_db@localhost identified by 'db_password';
grant create, alter, drop, references on *.* to translate_api_db;
以下のようにするとdockerコンテナの初回起動時に ./db/initdb.d/initdb.sql を実行してマイグレーションに必要なユーザーを作成することができます
mkdir db
cd db
mkdir initdb.d
cd initdb.d
vi initdb.sql
initdb.sql を以下の内容で保存します
grant select, insert, update, delete, create, alter, drop, references on *.* to 'myapp_api_db'@'%' with grant option;
docker 起動
docker compose up
うまくいかない場合は次のようなエラーがでているはずです
[Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/init.sql
myapp-api-db | ERROR 1410 (42000) at line 1: You are not allowed to create a user with GRANT
myapp-api-db exited with code 1
npx prisma migrate dev
マイグレーションファイル名入力を促されるので次のように 行った操作を簡単に入力します。(実行日時は自動的にセットされます)
add_tables
実行すると「DBへテーブルが作成され」「実行されたsql文が次のファイル名にて保存され」ます
MySQLを操作するアプリを起動するか 次のコマンドから確認することもできます
npx prisma studio
マイグレーションファイルを作成せずに、同期する
マイグレーションファイルを生成せず、スキーマを同期する
npx prisma migrate devを実行すると、毎回スキーマファイルが作成されるので、
開発時など頻繁に変更するときには少しめんどくさい。
開発用のコマンドもあり、npx prisma db pushを使うと、
マイグレーションファイルを生成せず同期できる。
いろいろ試して、変更点が整理できたら、
マイグレーションファイルを作成するのがよい感じ。
引用 : https://www.memory-lovers.blog/entry/2021/10/13/113000
ファイル名 /prisma/seed.ts を作成します。
vi prisma/seed.ts
prisma/seed.ts を 以下の内容で保存します
import { PrismaClient, Prisma } from '@prisma/client';
const prisma = new PrismaClient();
const userData: Prisma.UserCreateInput[] = [
{
email: 'test@user.com',
name: 'テスト太郎',
createdAt: new Date(),
updatedAt: new Date(),
source: {
create: [
{
text: 'おはようございます。今日は晴れていますね。',
translation: {
create: [
{
locale: 'en',
text: "Good morning. It's a sunny day today.",
},
{
locale: 'pt_BR',
text: 'Bom dia. Hoje está ensolarado.',
},
],
},
},
{
text: '昨日の晩御飯は誰と何を食べましたか?',
translation: {
create: [
{
locale: 'en',
text: 'Who did you have dinner with and what did you eat yesterday?',
},
{
locale: 'pt_BR',
text: 'Com quem você jantou e o que comeu ontem à noite?',
},
],
},
},
],
},
},
];
async function main() {
console.log(`Start seeding ...`);
for (const u of userData) {
const user = await prisma.user.create({
data: u,
});
console.log(`Created user with id: ${user.id}`);
}
console.log(`Seeding finished.`);
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
create以外にも次のような便利なメソッドがあります
引用 : https://zenn.dev/takky94/scraps/448d1ad68d969a
・データが存在すれば update、存在しなければ create がしたい
→ Upsert
・あるモデルのcreateやupdate時、そのモデルの関係モデルにデータが存在すればupdate、しなければcreateしたい
→ connectOrCreate
package.json へ次のコードを追記します。 scripts の一番最後の行に入れておくといいでしょう。
"scripts": {
...................
"seed": "ts-node prisma/seed.ts"
},
npm run seed
または 直接ファイルを実行してもokです
npx ts-node prisma/seed.ts
graphql のコードも自動生成したい場合は、こちらのパッケージをインストールしておきます
node.js|プログラムメモ
npx prisma db pull
npx prisma generate
(実行すると 型のサポートを受けれるようになります)
.env の代わりに .env.development を 使用している場合は、こちらのコマンドを実行します
npx dotenv -e .env.development -- npx prisma db pull
npx dotenv -e .env.development -- npx prisma generate
(ユーザ一覧とその先のリレーション、すべてを取得するスクリプトを記述します)
vi prisma/sample-script.ts
prisma/sample-script.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const users = await prisma.user.findMany({
include: {
source: {
include: {
translation: true,
},
},
},
});
console.log(JSON.stringify(users, null, 4));
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
include で リレーション先テーブルの すべてのカラムを取得しています
npx ts-node prisma/sample-script.ts
結果
[ { "id": 1, "email": "test@user.com", "name": "テスト太郎", "createdAt": "2023-03-27T04:20:47.000Z", "updatedAt": "2023-03-27T04:20:47.000Z", "source": [ { "id": 1, "userId": 1, "text": "おはようございます。今日は晴れていますね。", "createdAt": "2023-03-27T04:20:47.000Z", "updatedAt": "2023-03-27T04:20:47.000Z", "translation": [ { "id": 1, "sourceId": 1, "locale": "en", "text": "Good morning. It's a sunny day today.", "createdAt": "2023-03-27T04:20:47.000Z", "updatedAt": "2023-03-27T04:20:47.000Z" }, { "id": 2, "sourceId": 1, "locale": "pt_BR", "text": "Bom dia. Hoje está ensolarado.", "createdAt": "2023-03-27T04:20:47.000Z", "updatedAt": "2023-03-27T04:20:47.000Z" } ] }, { "id": 2, "userId": 1, "text": "昨日の晩御飯は誰と何を食べましたか?", "createdAt": "2023-03-27T04:20:47.000Z", "updatedAt": "2023-03-27T04:20:47.000Z", "translation": [ { "id": 3, "sourceId": 2, "locale": "en", "text": "Who did you have dinner with and what did you eat yesterday?", "createdAt": "2023-03-27T04:20:47.000Z", "updatedAt": "2023-03-27T04:20:47.000Z" }, { "id": 4, "sourceId": 2, "locale": "pt_BR", "text": "Com quem você jantou e o que comeu ontem à noite?", "createdAt": "2023-03-27T04:20:47.000Z", "updatedAt": "2023-03-27T04:20:47.000Z" } ] } ] } ]
https://medium.com/yavar/prisma-relations-2ea20c42f616
テストを一通り実行して確認しておきます
npm run test
npm run test:e2e
npm run test:cov
npx nest generate resource users --dry-run
(実際に実行するときは、 --dry-run を外します)
npx nest generate resource users
( REST API を選択してエンター )
npm run start:dev
http://localhost:3000/ へ アクセスして Hello,world! が表示されることを確認します
http://localhost:3000/users へ アクセスして This action returns all users が表示されることを確認します
以下のように書き換えて保存します
import { Injectable } from '@nestjs/common';
import { CreateGuserInput } from './dto/create-guser.input';
import { PrismaService } from '../prisma.service';
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
async findAll() {
return this.prisma.user.findMany({
include: {
source: true,
},
});
}
async findOne(id: number) {
return this.prisma.user.findFirst({ where: { id: id } });
}
create(createGuserInput: CreateGuserInput) {
return this.prisma.user.create({
data: {
email: createGuserInput.email,
name: createGuserInput.name,
},
});
}
}
http://localhost:3000/users へ アクセスしてユーザ一覧が返ってくることを確認します。
npm i @nestjs/graphql @nestjs/apollo graphql
schema.gql を自動生成させるために、あらかじめサーバを起動しておきます
npm run start:dev
起動後に必ずエラーが出ていないことを確認しましょう。
npx nest generate resource gusers
(GraphQL (code first) を選択してエンターを押します)
以下のファイルが新規作成または更新されます
CREATE src/gusers/gusers.module.ts (238 bytes)
CREATE src/gusers/gusers.resolver.spec.ts (545 bytes)
CREATE src/gusers/gusers.resolver.ts (1181 bytes)
CREATE src/gusers/gusers.service.spec.ts (467 bytes)
CREATE src/gusers/gusers.service.ts (653 bytes)
CREATE src/gusers/dto/create-gguser.input.ts (198 bytes)
CREATE src/gusers/dto/update-gguser.input.ts (251 bytes)
CREATE src/gusers/entities/gguser.entity.ts (189 bytes)
UPDATE src/app.module.ts (454 bytes)
また schema.gql も生成されます。
src/gusers/entities/guser.entity.ts を修正します
import { ObjectType, Field, Int } from '@nestjs/graphql';
@ObjectType()
export class Guser {
@Field(() => Int, { description: 'Example field (placeholder)' })
exampleField: number;
}
↓ 以下のように修正します
import { ObjectType, Field, ID } from '@nestjs/graphql';
import { Source } from '../../sources/entities/source.entity';
@ObjectType()
export class Guser {
@Field(() => ID)
id: number;
@Field()
name: string;
@Field()
email: string;
@Field()
createdAt: Date;
@Field()
updatedAt: Date;
@Field(() => [Source], { nullable: true })
source?: Array<Source>;
}
リレーション source も返すように設定します
npx nest generate resource sources
src/sources/entities/source.entity.ts
import { ObjectType, Field, ID } from '@nestjs/graphql';
@ObjectType()
export class Source {
@Field(() => ID)
id: number;
@Field()
useId: number;
@Field()
text: string;
@Field()
createdAt: Date;
@Field()
updatedAt: Date;
@Field(() => [Source], { nullable: true })
source?: Array<Source>;
}
クエリの操作を行うリゾルバ / サービスを修正します。
リゾルバ:一旦そのままです。変更しません。
サービス: 以下のように修正します
src/gusers/gusers.service.ts
findAll() {
return `This action returns all gusers`;
}
findOne(id: number) {
return `This action returns a #${id} guser`;
}
↓ このように findAll() , findOne()メソッドを入れ替えます
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async findAll() {
return prisma.user.findMany({
include: {
source: true,
},
});
}
async findOne(id: number) {
return prisma.user.findFirst({ where: { id: id } });
}
http://localhost:3000/graphql へアクセスして
{
gusers{
id,name,email,createdAt,updatedAt
}
}
クエリを投げて、正しくデータが返ってくることを確認します。
getSources → getMySources に名前変更するには以下のようにします
@Query(() => [Source])
getSources(@CurrentUser() firebaseUser: CurrentFirebaseUserData) {
........
}
↓
方法1. { name: 'getMySources' } で変える
@Query(() => [Source], { name: 'getMySources' })
getSources(@CurrentUser() firebaseUser: CurrentFirebaseUserData) {
........
}
↓
方法2. メソッド名を変える
@Query(() => [Source])
getMySources(@CurrentUser() firebaseUser: CurrentFirebaseUserData) {
........
}
その他参考:
omar-dulaimi/prisma-class-validator-generator: Prisma 2+ generator to emit typescript models of your database with class validator
Prisma でメソッドはやせない問題どうしたらいいんだ | きむそん.dev