Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
Tags
- factory
- Volatile
- spring security
- Google OAuth
- builder
- Spring
- java
- 일급 컬렉션
- lombok
- Dependency Injection
- synchronized
- OAuth 2.0
- 일급 객체
Archives
- Today
- Total
HJW's IT Blog
[CodeIt] 07월 3주차 본문
Prisma 설치 / 초기화
npm install prisma --save-dev
npm install @prisma/client
npx prisma init --datasource-provider db-type // qostgresql, mysql, sqlite 등
- Prisma Schema 생성
// 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 String @id @default(uuid())
email String @unique
firstName String
lastName String
address String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Product {
id String @id @default(uuid())
name String
description String?
category Category
price Float
stock Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Category {
FASHION
BEAUTY
SPORTS
ELECTRONICS
HOME_INTERIOR
HOUSEHOLD_SUPPLIES
KITCHENWARE
}
npx prisma init --datasource-provider postgresql
- 초기화 및 시작
// .env
DATABASE_URL="postgresql://postgres:gjwldnd!1@localhost:5432/comazon_dev?schema=public"
PORT=3000
- schema.prisma 에서 model 은 table 이라 보면 된다
- Shift + Alt + F 로 자동 줄맞춤
model User {
id String @id @default(uuid())
email String @unique
firstName String
lastName String
address String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
- @id 는 unique 한 식별자임을 나타낸다
- @unique 는 유일한 값임을 나타낸다
- ? 는 optional 한 필드임을 나타낸다
- @default(uuid()) : 36자리 id
- 다음과 같이 enum type 을 사용할 수도 있다
model User {
// ...
membership Membership @default(BASIC)
}
enum Membership {
BASIC
PREMIUM
}
- multivalued + unique 필드를 사용할 경우,
model User {
id String @id @default(uuid())
email String @unique
firstName String
lastName String
address String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([firstName, lastName])
}
// fname, lname 의 조합이 unique 해야 한다
- 마이그레이션 : 모델코드를 DB 에 반영하는 과정으로, 스키마 파일에 변동사항이 있다면 항상 해주어야 한다
npx prisma migrate dev
npx prisma studio
- 위 명령어로 db gui 에 접근할 수 있다
- 테이블에 이미 데이터가 있어 migration 을 못할 경우, 우선 nullable 필드로 생성한 후, 데이터를 넣고 추가로 migration 을 해주면 된다
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
- 위 코드를 통해 PrismaClient import
// get users
app.get('/users', async (req, res) => {
const users = await prisma.user.findMany();
res.send(users);
});
//get user by id
app.get('/users/:id', async (req, res) => {
const { id } = req.params;
const user = await prisma.user.findUnique({
where: { id },
});
res.send(user);
});
app.post('/users', async (req, res) => {
const uer = await prisma.user.create({
data: req.body,
});
res.status(201).send(user);
});
app.patch('/users/:id', async (req, res) => {
const { id } = req.params;
const user = await prisma.user.update({
where: {
id
},
data : req.body,
});
res.send(user);
});
app.delete('/users/:id', async (req, res) => {
const { id } = req.params;
await prisma.user.delete({
where:{id},
});
res.sendStatus(204);
});
// seed.js
import { PrismaClient } from '@prisma/client';
import {USERS} from './mock.js'
const prisma = new PrismaClient();
async function main() {
// 기존 데이터 삭제
await prisma.user.deleteMany();
// 목 데이터 삽입
await prisma.user.createMany({
data: USERS,
skipDuplicates: true,
});
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
// package.json 에 추가
"prisma":{
"seed": "node prisma/seed.js"
}
npx prisma db seed
쿼리 파라미터
app.get('/users', async (req, res) => {
const { offset = 0, limit = 10, order = 'newest'} = req.query;
const users = await prisma.user.findMany({
orderBy: {createdAt: 'asc'},
skip : parseInt(offset),
take : parseInt(limit),
});
res.send(users);
});
offset : 몇개의 데이터를 건너뛸지
limit :
orderBy: { 정렬 필드, 순서}
추가 CRUD Method
.findFirst()
- unique 하지 않은 필드 사용시
.upsert()
- where 를 만족하는 객체를 update, 없다면 create
.count()
- 객체의 개수만 반환
유효성 검사
//structs.js
import * as s from 'superstruct'
import isEmail from 'is-email'
export const CreateUser = s.object({
email: s.define('Email', isEmail),
firstName: s.size(s.string(),1, 30),
lastName: s.size(s.string(),1, 30),
address: s.string(),
});
export const PatchUser = s.partial(CreateUser);
//app.js 에 추가
import { assert } from 'superstruct'
import { CreateUser } from './structs.js'
app.post('/users', async (req, res) => {
// 리퀘스트 바디 내용으로 유저 생성
assert(req.body, CreateUser); // 유효성 검사
const user = await prisma.user.create({
data: req.body,
});
res.status(201).send(user);
});
- express 에서 오류가 발생하면 서버가 죽기 때문에 try catch 로 감싸주어야 한다
- 유효성 검사 asyncHander 에서 error.name 은 StructError 이다
RDB 기본기
- Primary Key : row 를 식별하는 unique value column. 다른 row 로도 식별은 가능하다.
- Foreign Key : Relationship 이 있는 다른 테이블의 PK
- 데이터 모델링 : 서비스에 정확히 어떤 데이터가 필요하고, 데이터간 어떤 관꼐가 있는지를 정교하게 표현하는 것
- ER (Entity Relationship) Model
- Entity : 현실세계의 사물 혹은 객체. 하나의 개체 = 하나의 테이블
- Attribute : Entity 의 세부 정보
- Relationship : Entity 간의 관계'
- Cardinality
- 1 : 1 → 하나의 entity 가 정확하 하나의 다른 entity instance와 관계
- 1 : N → 하나의 entity 가 최대 N개의 다른 entity instance와 관계
- N : M → N개의 entity instance 가 최대 M개의 다른 entity instance 와 관계
Prisma에서 Relationship 정의
1:N 관계
model User {
id String @id @default(uuid())
email String @unique
firstName String
lastName String
address String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
orders Order[]
}
model Order {
id String @id @default(uuid())
status OrderStatus @default(PENDING)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
userId String
}
- Order model 에 user User 과 같이 선언한 뒤 alt shift f 로 자동완성
1:1 관계
model User {
id String @id @default(uuid())
email String @unique
firstName String
lastName String
address String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
orders Order[]
UserPreference UserPreference?
}
model UserPreference {
id String @id @default(uuid())
receiveEmail Boolean
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
userId String @unique
}
N:M 관계
model User {
id String @id @default(uuid())
email String @unique
firstName String
lastName String
address String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
orders Order[]
UserPreference UserPreference?
savedProducts Product[]
}
model Product {
id String @id @default(uuid())
name String
description String?
category Category
price Float
stock Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
savedUsers User[]
}
- onDelete option
- 연결된 데이터가 삭제되었을때 기존 데이터를 어떻게 처리할 것인지
- Cascade : FK 가 가르키는 데이터 삭제시, 기존 데이터도 삭제
- Restrict : 특정 데이터를 참조한느 데이터들이 있으면 데이터를 삭제하지 못함
- SetNull : FK 가 가르키는 데이터 삭제시 NULL 로 설정
- SetDefault : 삭제시 FK 를 기본 default 값으로 설정
model UserPreference {
id String @id @default(uuid())
receiveEmail Boolean
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String @unique
}
- user Preference 도 get route 에서 가져오기
app.get('/users/:id', asyncHandler(async (req, res) => {
const { id } = req.params;
const user = await prisma.user.findUniqueOrThrow({
where: { id },
include: {
userPreference: true,
},
});
res.send(user);
}));
- 특정 필드만 가져오려면
include: {
userPreference: {
select: {
receiveEmail: true,
},
},
},
- 관련 객체 생성
//user.http
POST <http://localhost:3000/users>
Content-Type: application/json
{
"email": "yjkim@gmail.com",
"firstName": "유진",
"lastName": "김a",
"address": "충청북도 청주시 북문로 210번길 5",
"userPreference":{
"receiveEmail": false
}
}
//struct.js
app.post(
'/users',
asyncHandler(async (req, res) => {
assert(req.body, CreateUser);
const { userPreference, ...userFields} = req.body;
const user = await prisma.user.create({
data: req.body,
});
res.status(201).send(user);
})
);
//app.js
app.post(
'/users',
asyncHandler(async (req, res) => {
assert(req.body, CreateUser);
const { userPreference, ...userFields} = req.body;
const user = await prisma.user.create({
data: {
...userFields,
userPreference: {
create: userPreference,
},
},
include: {
userPreference: true,
},
});
res.status(201).send(user);
})
);
//app.js / patch
app.patch(
'/users/:id',
asyncHandler(async (req, res) => {
assert(req.body, PatchUser);
const { id } = req.params;
const { userPreference, ...userFields} = req.body;
const user = await prisma.user.update({
where: {id},
data: {
...userFields,
userPreference: {
update: userPreference,
},
},
include: {
userPreference: true,
},
});
res.send(user);
})
);
- 관련된 객체는 꼭 create property 를 이용해야 한다.
- 객체 연결
//app.js
app.post(
'/users/:id/saved-products',
asyncHandler(async (req, res) => {
assert(req.body, PostSavedProduct);
const { id: userId } = req.params;
const { productId } = req.body;
const { savedProducts } = await prisma.user.update({
where: { id: userId },
data: {
savedProducts: {
connect: {
id: productId,
},
},
},
include: {
savedProducts: true,
},
});
res.send(savedProducts);
})
);
// struct.js
export const PostSavedProduct = s.object({
productId: Uuid,
})
- user 를 해당 id 를 가진 상품과 연결하라는 뜻이다
- 기존에 존재하는 객체와 연결하기 위해서는 → struct.js 에 정의 → 유효성 검사 → update → data 내에 조건에 따른 id 선언
- 제고 관리
app.post(
'/orders',
asyncHandler(async (req, res) => {
assert(req.body, CreateOrder);
const { userId, orderItems } = req.body;
const productIds = orderItems.map((orderItem) => orderItem.productId);
const products = await prisma.product.findMany({
where: { id: { in: productIds } },
});
function getQuantity(productId) {
const orderItem = orderItems.find(
(orderItem) => orderItem.productId === productId
);
return orderItem.quantity;
}
// 재고 확인
const isSufficientStock = products.every((product) => {
const { id, stock } = product;
return stock >= getQuantity(id);
});
if (!isSufficientStock) {
throw new Error('Insufficient Stock');
}
// Order 생성하고 재고 감소
const order = await prisma.order.create({
data: {
userId,
orderItems: {
create: orderItems,
},
},
include: {
orderItems: true,
},
});
const queries = productIds.map((productId) =>
prisma.product.update({
where: { id: productId },
data: { stock: { decrement: getQuantity(productId) } },
})
);
await Promise.all(queries);
res.status(201).send(order);
})
);
- transaction
- await prisma.$transaction();
- 여러 쿼리를 묶어서 실행한다
- 모든 작업이 성공하거나 모든 작업이 실패
const [order] = await prisma.$transaction(
[
prisma.order.create({
data: {
userId,
orderItems: {
create: orderItems,
},
},
include: {
orderItems: true,
},
}),
...queries,
]
);
- 안에 실행할 쿼리들을 배열 형태로 넘겨주면 된다
- 즉, 일부만 성공하고 일부는 실패하는 일이 없다