Prisma + StackRender: 데이터베이스를 설계하고 백엔드 구축을 시작하세요
Source: Dev.to
번역할 텍스트가 제공되지 않았습니다. 번역이 필요한 전체 내용(코드 블록을 제외한 마크다운 텍스트)을 알려주시면 한국어로 번역해 드리겠습니다.
우리가 할 일
- StackRender를 사용하여 PostgreSQL 데이터베이스를 시각적으로 설계합니다
- 데이터베이스를 즉시 배포합니다
- 스키마를 Prisma로 자동 가져옵니다
- 백엔드 엔드포인트를 구축하고 테스트를 시작합니다
사전 요구 사항
- Node.js 설치
- PostgreSQL 데이터베이스 (로컬 또는 원격)
- Prisma 설치 및 Node.js 프로젝트에 설정
Note: Prisma가 처음이라면, 계속하기 전에 공식 설정 가이드를 따라 주세요.
Source: …
빈 프로젝트에서 시작하기
우리는 Prisma가 이미 설정된 새 Node.js 프로젝트로 시작합니다. schema.prisma 파일을 열면 모델도, 관계도, 정의된 것이 전혀 없는 완전한 빈 파일임을 알 수 있습니다. 동시에 연결된 PostgreSQL 데이터베이스도 비어 있습니다(테이블이나 제약조건이 없음). 바로 이 지점이 완벽한 시작점입니다.
generator client {
provider = "prisma-client-js"
output = "../src/generated/prisma"
}
datasource db {
provider = "postgresql"
}
StackRender를 사용해 스키마 설계하기
스키마를 직접 설계하는 대신 StackRender라는 무료 오픈소스 데이터베이스 스키마 생성기를 사용합니다.
-
StackRender에서 새 데이터베이스 생성
- 이름:
ecommerce_db - 데이터베이스 유형: PostgreSQL 선택
- 이름:
-
빈 다이어그램이 표시됩니다. 여기서 스키마를 직접 설계하거나 기존 데이터베이스를 가져올 수 있습니다. 이번 예시에서는 AI가 초기 설계를 담당하도록 합니다.
-
AI 프롬프트 – StackRender의 AI 프롬프트 기능에 다음을 입력합니다:
Design a multi‑vendor ecommerce database -
몇 초 안에 StackRender가 테이블, 필드, 관계를 포함한 전체 데이터베이스 다이어그램을 생성합니다. 모든 요소는 완전히 편집 가능하므로 테이블 이름을 바꾸고, 컬럼을 조정하고, 관계를 미세 조정한 뒤 진행할 수 있습니다.
-
스키마가 만족스러우면:
- StackRender에서 Code 섹션을 엽니다
- Export 버튼을 눌러 생성된 SQL 스크립트를 내보냅니다
-
내보낸 SQL 스크립트를 PostgreSQL 데이터베이스에 실행합니다(예: pgAdmin 사용). 실행이 완료되면 모든 테이블, 제약조건, 관계가 자동으로 생성됩니다. 이제 손으로 SQL을 작성하지 않아도 데이터베이스가 실시간으로 구축됩니다.
실시간 데이터베이스를 Prisma와 동기화하기
데이터베이스가 준비되었으니 스키마를 Prisma로 가져옵니다:
npx prisma db pull
Prisma가 데이터베이스를 introspect하여 기존 테이블과 관계를 기반으로 schema.prisma 파일을 자동으로 생성합니다. 아래는 결과 스키마의 일부(간략히 표시)입니다.
generator client {
provider = "prisma-client-js"
output = "../src/generated/prisma"
}
datasource db {
provider = "postgresql"
}
model cart_items {
id Int @id @default(autoincrement())
cart_id Int
product_id Int
quantity Int
added_at DateTime? @default(now()) @db.Timestamptz(6)
carts carts @relation(fields: [cart_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
products products @relation(fields: [product_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
}
model carts {
id Int @id @default(autoincrement())
user_id Int @unique
created_at DateTime? @default(now()) @db.Timestamptz(6)
updated_at DateTime? @default(now()) @db.Timestamptz(6)
cart_items cart_items[]
users users @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
}
/* ... additional models omitted for brevity ... */
팁: 생성된 모델에는 모든 관계, 열거형, 기본값이 포함되어 있어 바로 사용할 수 있는 Prisma 클라이언트를 제공합니다.
다음 단계
-
Prisma 클라이언트 생성
npx prisma generate -
API 라우트를 구축 시작 (예: Express, Fastify, NestJS 사용)하고
../src/generated/prisma에서 생성된 클라이언트를 가져옵니다. -
실제 데이터베이스에 대한 통합 테스트 작성하여 엔드포인트를 검증합니다.
-
반복 – 스키마를 조정해야 하면 StackRender로 돌아가 다이어그램을 수정하고, SQL을 다시 내보낸 뒤 실행하고,
npx prisma db pull을 다시 실행합니다.
요약
StackRender의 시각적 디자이너와 Prisma의 인스펙션을 결합하면 다음을 할 수 있습니다:
- 수동 SQL 작성을 건너뛰세요
- Prisma 모델을 데이터베이스와 완벽하게 동기화 상태로 유지하세요
- “백엔드‑우선” 개발 사이클을 가속화하세요
다음 프로젝트에서 이 워크플로를 시도해 보고 설정 시간을 몇 시간 단축해 보세요!
Prisma 스키마 (정리됨)
model cart_items {
id Int @id @default(autoincrement())
cart_id Int
product_id Int
quantity Int
cart carts @relation(fields: [cart_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
product products @relation(fields: [product_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
}
model categories {
id Int @id @default(autoincrement())
name String
description String?
products products[]
}
model order_items {
id Int @id @default(autoincrement())
order_id Int
product_id Int
quantity Int
price Decimal @db.Decimal(10, 2)
orders orders @relation(fields: [order_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
products products @relation(fields: [product_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
}
model orders {
id Int @id @default(autoincrement())
user_id Int
total_amount Decimal @db.Decimal(10, 2)
currency orders_currency_enum
status orders_order_status_enum @default(pending)
placed_at DateTime? @default(now()) @db.Timestamptz(6)
shipped_at DateTime? @db.Timestamptz(6)
delivered_at DateTime? @db.Timestamptz(6)
cancelled_at DateTime? @db.Timestamptz(6)
users users @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
order_items order_items[]
shipments shipments?
payments payments?
}
model payments {
id Int @id @default(autoincrement())
order_id Int @unique
amount Decimal @db.Decimal(10, 2)
method String?
status payments_payment_status_enum @default(unpaid)
transaction_id String? @unique
payment_date DateTime? @default(now()) @db.Timestamptz(6)
orders orders @relation(fields: [order_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
}
model product_images {
id Int @id @default(autoincrement())
product_id Int
url String
alt_text String?
products products @relation(fields: [product_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
}
model products {
id Int @id @default(autoincrement())
vendor_id Int
category_id Int?
name String
description String?
price Decimal @db.Decimal(10, 2)
stock_quantity Int
sku String? @unique
created_at DateTime? @default(now()) @db.Timestamptz(6)
updated_at DateTime? @default(now()) @db.Timestamptz(6)
cart_items cart_items[]
order_items order_items[]
product_images product_images[]
categories categories? @relation(fields: [category_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
vendors vendors @relation(fields: [vendor_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
reviews reviews[]
}
model reviews {
id Int @id @default(autoincrement())
product_id Int
user_id Int
rating Int
comment String?
review_date DateTime? @default(now()) @db.Timestamptz(6)
products products @relation(fields: [product_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
users users @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
}
model shipments {
id Int @id @default(autoincrement())
order_id Int @unique
shipment_date DateTime?
Source:
@db.Timestamptz(6)
tracking_number String? @unique
carrier String?
fulfillment_status shipments_fulfillment_status_enum @default(not_fulfilled)
created_at DateTime? @default(now()) @db.Timestamptz(6)
updated_at DateTime? @default(now()) @db.Timestamptz(6)
orders orders @relation(fields: [order_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
}
model users {
id Int @id @default(autoincrement())
username String @unique
email String @unique
password_hash String
first_name String?
last_name String?
shipping_address String?
billing_address String?
created_at DateTime? @default(now()) @db.Timestamptz(6)
updated_at DateTime? @default(now()) @db.Timestamptz(6)
carts carts?
orders orders[]
reviews reviews[]
}
model vendor_payouts {
id Int @id @default(autoincrement())
vendor_id Int
payout_date DateTime? @default(now()) @db.Timestamptz(6)
amount Decimal @db.Decimal(10, 2)
transaction_id String? @unique
vendors vendors @relation(fields: [vendor_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
}
model vendors {
id Int @id @default(autoincrement())
name String
email String @unique
phone_number String?
address String?
created_at DateTime? @default(now()) @db.Timestamptz(6)
updated_at DateTime? @default(now()) @db.Timestamptz(6)
order_items order_items[]
products products[]
vendor_payouts vendor_payouts[]
}
/* ---------- Enums ---------- */
enum orders_currency_enum {
USD
EUR
GBP
}
enum orders_order_status_enum {
pending
processing
shipped
delivered
cancelled
}
enum payments_payment_status_enum {
unpaid
paid
refunded
}
enum shipments_fulfillment_status_enum {
not_fulfilled
fulfilled
partially_fulfilled
}
Source: …
간단한 백엔드 기능 (POST /product)
다음 예시는 POST 엔드포인트를 만드는 방법을 보여줍니다. 이 엔드포인트는:
- 클라이언트로부터 제품 데이터를 받습니다.
- Zod 로 페이로드를 검증합니다.
- Prisma 를 사용해 제품(및 이미지)을 영속화합니다.
- 새로 생성된 제품을 벤더, 카테고리, 이미지와 함께 반환합니다.
라우트 구현 (TypeScript)
import { Request, Response } from "express";
import { prisma } from "./prismaClient"; // adjust the import to your setup
import { createProductSchema, CreateProductImageInput } from "./schemas";
router.post("/product", async (request: Request, response: Response) => {
try {
// 1️⃣ Validate request body
const result = createProductSchema.safeParse(request.body);
if (!result.success) {
response.status(400).json(result);
return;
}
const { data } = result;
// 2️⃣ Create product + nested images
const product = await prisma.products.create({
data: {
...data,
product_images: {
create: data.product_images.map(
(productImage: CreateProductImageInput) => productImage
),
},
},
include: {
vendors: true,
categories: true,
product_images: true,
},
});
// 3️⃣ Respond with the created entity
response.status(200).json({ success: true, data: product });
} catch (error) {
console.error(error);
response.status(500).json({ error, success: false });
}
});
엔드포인트가 수행하는 작업
| 단계 | 설명 |
|---|---|
| 수신 | 제품과 그 이미지 배열을 설명하는 JSON 페이로드를 받습니다. |
| 검증 | Zod (createProductSchema) 를 사용해 페이로드가 기대하는 형태인지 확인합니다. |
| 영속화 | product_images 에 대한 중첩 create 와 함께 prisma.products.create 를 호출합니다. |
| 반환 | 관련 vendor, category, product_images 를 포함한 전체 제품 레코드를 반환합니다. |
엔드‑투‑엔드 검증
- 초기 상태 –
products테이블이 비어 있습니다. - 테스트 요청 전송 (예: Postman 또는 curl 사용) – 유효한 페이로드와 함께 요청을 보냅니다.
- 결과 –
products에 새 행이 추가되고,product_images에도 해당 행이 생성됩니다. - 응답 – API가 생성된 제품과 연관된 데이터를 함께 반환합니다.
이를 통해 다음이 확인됩니다:
- Prisma 스키마가 데이터베이스에 올바르게 매핑되었습니다.
- 생성된 Prisma 클라이언트가 기대대로 동작합니다.
- 검증 레이어(Zod)가 DB에 도달하기 전에 잘못된 입력을 차단합니다.
Why This Workflow Is Valuable
- Visual DB design – 다이어그램이나 스키마 파일로 시작합니다; 손으로 작성한 SQL이 필요 없습니다.
- Automatic model generation – Prisma가 스키마를 읽어 타입‑안전 모델을 자동으로 생성합니다.
- Rapid prototyping – 스키마에서 작동하는 엔드포인트까지 몇 분 안에 구현할 수 있습니다.
- Consistency – 타입, 검증, 데이터베이스 제약 조건이 항상 동기화됩니다.
Prisma와 PostgreSQL으로 백엔드를 구축한다면, 시각적 설계 도구(예: StackRender)와 결합하여 설정 시간을 몇 시간, 심지어 며칠까지 단축할 수 있습니다.
리소스
- StackRender – 시각적 데이터베이스 설계 및 Prisma 스키마 생성.
- Prisma Docs – https://www.prisma.io/docs
- Zod – https://github.com/colinhacks/zod
행복한 코딩! 🚀
- 웹사이트
- Github