0529 풀스택 실무 기초 (3/N): 데이터베이스와 ORM 기본 구조
✅ 1. 데이터베이스란 무엇인가?
- 데이터베이스(Database, DB)는 서비스에서 사용하는 데이터를 체계적으로 저장하고 관리하는 공간입니다.
- 웹서비스는 단순히 화면과 API만으로 동작하지 않습니다.
- 회원 정보, 상품 정보, 주문 정보, 상담 신청 내역, 관리자 설정, 이벤트 데이터 등 대부분의 중요한 데이터는 DB에 저장됩니다.
➕ 1-1. 풀스택 개발에서 DB가 중요한 이유
- 프론트엔드는 사용자가 데이터를 입력하고 확인하는 화면을 담당합니다.
- 백엔드는 사용자의 요청을 받아 비즈니스 로직을 처리합니다.
- DB는 그 결과를 저장하고, 다시 조회할 수 있게 해줍니다.
React 화면
↓
API 요청
↓
NestJS 서버
↓
Prisma / ORM
↓
Database
- 예를 들어 사용자가 상담 신청을 하면 다음 흐름이 발생합니다.
1. 사용자가 상담 신청 폼 입력
2. React에서 POST /api/consults 요청
3. NestJS에서 요청 데이터 검증
4. Prisma를 통해 DB에 상담 신청 데이터 저장
5. 저장된 결과를 API 응답으로 반환
6. React에서 완료 메시지 표시
✅ 2. 관계형 데이터베이스
- 관계형 데이터베이스(Relational Database)는 데이터를 표 형태의 테이블로 저장하는 DB입니다.
- 실무에서 많이 사용하는 MySQL, PostgreSQL, MariaDB, Oracle 등이 관계형 DB에 해당합니다.
➕ 2-1. 테이블
- 테이블(Table)은 같은 종류의 데이터를 저장하는 구조입니다.
- 엑셀 시트처럼 행과 열로 구성되어 있습니다.
users 테이블
| id | name | email | createdAt |
|---|--------|-------------------|---------------------|
| 1 | 홍길동 | test@example.com | 2026-05-29 10:00:00 |
| 2 | 김철수 | kim@example.com | 2026-05-29 11:00:00 |
- 예를 들어 쇼핑몰이나 휴대폰 판매 사이트에서는 다음과 같은 테이블이 있을 수 있습니다.
| 테이블 | 저장하는 데이터 |
|---|
users | 사용자 정보 |
admins | 관리자 계정 |
products | 상품 정보 |
orders | 주문 정보 |
consults | 상담 신청 내역 |
events | 이벤트 정보 |
banners | 배너 정보 |
logs | 운영 로그 |
✅ 3. 컬럼과 레코드
➕ 3-1. 컬럼
- 컬럼(Column)은 테이블에서 데이터의 항목을 의미합니다.
- 예를 들어 사용자 테이블에는 이름, 이메일, 전화번호 같은 컬럼이 있을 수 있습니다.
users 테이블의 컬럼 예시
id
name
email
phone
createdAt
updatedAt
➕ 3-2. 레코드
- 레코드(Record)는 테이블에 저장된 실제 데이터 한 줄을 의미합니다.
- Row, 행이라고도 부릅니다.
| id | name | email | phone |
|---|--------|------------------|--------------|
| 1 | 홍길동 | test@example.com | 01012345678 |
✅ 4. 기본키와 외래키
➕ 4-1. 기본키
- 기본키(Primary Key)는 테이블의 각 레코드를 고유하게 식별하는 값입니다.
- 보통
id 컬럼을 기본키로 사용합니다.
products 테이블
| id | name | price |
|---|------------|---------|
| 1 | Galaxy S25 | 1200000 |
| 2 | iPhone 16 | 1300000 |
- 여기서
id는 각 상품을 구분하는 고유한 값입니다.
- 기본키는 중복되면 안 되고, 비어 있어도 안 됩니다.
➕ 4-2. 외래키
- 외래키(Foreign Key)는 다른 테이블의 기본키를 참조하는 컬럼입니다.
- 테이블 간 관계를 연결할 때 사용합니다.
users 테이블
| id | name |
|---|--------|
| 1 | 홍길동 |
orders 테이블
| id | userId | productName |
|---|--------|-------------|
| 1 | 1 | Galaxy S25 |
orders.userId는 users.id를 참조합니다.
- 즉, 주문 데이터가 어떤 사용자와 연결되어 있는지 알 수 있습니다.
✅ 5. 테이블 관계
- 관계형 DB에서는 테이블끼리 관계를 맺을 수 있습니다.
- 실무에서 자주 사용하는 관계는 1:1, 1:N, N:M입니다.
➕ 5-1. 1:1 관계
- 하나의 데이터가 다른 하나의 데이터와만 연결되는 관계입니다.
User 1명 ↔ Profile 1개
-
예시:
- 사용자와 사용자 상세 프로필
- 관리자 계정과 관리자 설정
➕ 5-2. 1:N 관계
- 하나의 데이터가 여러 개의 데이터와 연결되는 관계입니다.
- 실무에서 가장 자주 사용됩니다.
User 1명 → Order 여러 개
Event 1개 → Banner 여러 개
Product 1개 → Review 여러 개
-
예시:
- 한 명의 사용자가 여러 주문을 할 수 있음
- 하나의 이벤트에 여러 배너가 연결될 수 있음
- 하나의 상담 신청에 여러 처리 이력이 생길 수 있음
➕ 5-3. N:M 관계
- 여러 데이터가 서로 여러 개씩 연결되는 관계입니다.
- 보통 중간 테이블을 만들어서 관리합니다.
Product 여러 개 ↔ Category 여러 개
User 여러 명 ↔ Role 여러 개
- 예를 들어 하나의 상품이 여러 카테고리에 속할 수 있고, 하나의 카테고리에 여러 상품이 들어갈 수 있습니다.
products
categories
product_categories
product_categories 같은 중간 테이블을 통해 상품과 카테고리 관계를 관리합니다.
✅ 6. SQL 기본 개념
- SQL(Structured Query Language)은 관계형 DB에 명령을 내리는 언어입니다.
- 데이터를 조회, 생성, 수정, 삭제할 때 사용합니다.
➕ 6-1. SELECT
SELECT * FROM users;
SELECT id, name, email FROM users;
➕ 6-2. INSERT
INSERT INTO users (name, email, phone)
VALUES ('홍길동', 'test@example.com', '01012345678');
➕ 6-3. UPDATE
UPDATE users
SET phone = '01099998888'
WHERE id = 1;
-
주의할 점
WHERE 조건 없이 UPDATE를 실행하면 모든 데이터가 수정될 수 있습니다.
- 운영 DB에서 직접 UPDATE를 실행할 때는 매우 조심해야 합니다.
➕ 6-4. DELETE
DELETE FROM users
WHERE id = 1;
-
주의할 점
WHERE 조건 없이 DELETE를 실행하면 전체 데이터가 삭제될 수 있습니다.
- 실무에서는 실제 삭제보다
deletedAt 컬럼을 사용하는 소프트 삭제를 많이 사용합니다.
✅ 7. WHERE, ORDER BY, LIMIT
➕ 7-1. WHERE
- 특정 조건에 맞는 데이터만 조회할 때 사용합니다.
SELECT * FROM consults
WHERE status = 'PENDING';
- 예를 들어 처리 대기 중인 상담 신청만 조회할 수 있습니다.
➕ 7-2. ORDER BY
SELECT * FROM consults
ORDER BY createdAt DESC;
DESC는 내림차순, ASC는 오름차순입니다.
- 최신 상담 신청을 먼저 보여줄 때는 보통
createdAt DESC를 사용합니다.
➕ 7-3. LIMIT
SELECT * FROM consults
ORDER BY createdAt DESC
LIMIT 20;
- 관리자 페이지 목록 조회나 페이지네이션에서 자주 사용합니다.
✅ 8. JOIN 기본 개념
- JOIN은 여러 테이블에 나누어 저장된 데이터를 함께 조회할 때 사용합니다.
- 관계형 DB에서 매우 중요한 개념입니다.
➕ 8-1. JOIN이 필요한 이유
- 데이터를 하나의 큰 테이블에 모두 넣으면 중복이 많아지고 관리가 어려워집니다.
- 그래서 사용자, 주문, 상품처럼 성격이 다른 데이터는 각각 다른 테이블에 저장하고, 필요할 때 JOIN으로 연결합니다.
orders 테이블에는 userId와 productId만 저장
users 테이블에는 사용자 정보 저장
products 테이블에는 상품 정보 저장
주문 목록 화면에서는 JOIN으로 사용자 이름과 상품명을 함께 조회
➕ 8-2. JOIN 예시
SELECT
orders.id,
users.name AS userName,
products.name AS productName,
orders.createdAt
FROM orders
JOIN users ON orders.userId = users.id
JOIN products ON orders.productId = products.id;
- 위 쿼리는 주문 정보와 사용자 이름, 상품명을 함께 조회합니다.
✅ 9. 인덱스 기본 개념
- 인덱스(Index)는 DB 조회 속도를 빠르게 하기 위한 자료구조입니다.
- 책의 목차나 색인처럼, 원하는 데이터를 더 빨리 찾을 수 있게 도와줍니다.
➕ 9-1. 인덱스가 필요한 경우
- 자주 검색하는 컬럼
- 자주 정렬하는 컬럼
- JOIN에 자주 사용되는 외래키 컬럼
- WHERE 조건에 자주 들어가는 컬럼
CREATE INDEX idx_consults_phone ON consults(phone);
CREATE INDEX idx_consults_createdAt ON consults(createdAt);
- 상담 신청 목록에서 전화번호 검색을 자주 한다면
phone 컬럼에 인덱스를 고려할 수 있습니다.
- 최신순 정렬을 자주 한다면
createdAt 컬럼도 인덱스 후보가 될 수 있습니다.
➕ 9-2. 인덱스 주의점
- 인덱스가 많다고 무조건 좋은 것은 아닙니다.
- 조회는 빨라질 수 있지만, INSERT, UPDATE, DELETE 성능은 느려질 수 있습니다.
- 사용하지 않는 인덱스가 많으면 DB 용량도 늘어납니다.
- 실무에서는 느린 쿼리를 확인한 뒤 필요한 컬럼에만 인덱스를 추가하는 것이 좋습니다.
✅ 10. ORM이란 무엇인가?
- ORM(Object Relational Mapping)은 객체 지향 코드와 관계형 DB를 연결해주는 도구입니다.
- SQL을 직접 많이 작성하지 않아도 TypeScript나 JavaScript 코드로 DB를 다룰 수 있게 해줍니다.
➕ 10-1. ORM을 사용하는 이유
- SQL을 직접 작성하는 양을 줄일 수 있습니다.
- 타입 안정성을 얻을 수 있습니다.
- 테이블 구조를 코드로 관리할 수 있습니다.
- CRUD 작업을 더 일관되게 작성할 수 있습니다.
- 마이그레이션을 통해 DB 구조 변경을 관리할 수 있습니다.
➕ 10-2. 대표적인 ORM
| 언어/환경 | ORM |
|---|
| Node.js / TypeScript | Prisma, TypeORM, Sequelize |
| Java / Kotlin | JPA, Hibernate |
| Python | SQLAlchemy, Django ORM |
| PHP | Eloquent |
- NestJS와 TypeScript 환경에서는 Prisma 또는 TypeORM을 많이 사용합니다.
- 최근 실무에서는 타입 안정성과 개발 경험 때문에 Prisma를 사용하는 경우가 많습니다.
✅ 11. Prisma 기본 구조
- Prisma는 Node.js/TypeScript 환경에서 사용하는 ORM입니다.
schema.prisma 파일에 데이터 모델을 정의하고, Prisma Client를 통해 DB에 접근합니다.
➕ 11-1. Prisma 모델 예시
model Consult {
id Int @id @default(autoincrement())
name String
phone String
model String
status String @default("PENDING")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
- 위 모델은 상담 신청 데이터를 저장하는
Consult 테이블 구조를 의미합니다.
| 필드 | 의미 |
|---|
id | 기본키 |
name | 신청자 이름 |
phone | 전화번호 |
model | 관심 모델 |
status | 상담 상태 |
createdAt | 생성일 |
updatedAt | 수정일 |
➕ 11-2. Prisma Client 사용 예시
const consult = await prisma.consult.create({
data: {
name: '홍길동',
phone: '01012345678',
model: 'Galaxy S25',
},
});
- 위 코드는 SQL의
INSERT와 비슷한 역할을 합니다.
const consults = await prisma.consult.findMany({
where: {
status: 'PENDING',
},
orderBy: {
createdAt: 'desc',
},
take: 20,
});
- 위 코드는 처리 대기 중인 상담 신청을 최신순으로 20개 조회합니다.
✅ 12. 마이그레이션
- 마이그레이션(Migration)은 DB 테이블 구조 변경 이력을 관리하는 작업입니다.
- 예를 들어 상담 신청 테이블에
memo 컬럼을 추가하거나, 상품 테이블에 isVisible 컬럼을 추가하는 것이 마이그레이션입니다.
➕ 12-1. 마이그레이션이 필요한 이유
- DB 구조 변경을 기록으로 남길 수 있습니다.
- 개발 환경, 테스트 환경, 운영 환경의 DB 구조를 맞출 수 있습니다.
- 팀원 간 DB 구조 차이를 줄일 수 있습니다.
- 배포 시 어떤 DB 변경이 필요한지 추적할 수 있습니다.
➕ 12-2. Prisma 마이그레이션 예시
npx prisma migrate dev --name add_consult_memo
- 개발 환경에서 마이그레이션 파일을 생성하고 DB에 적용합니다.
npx prisma migrate deploy
- 운영 환경에서는 이미 생성된 마이그레이션 파일을 적용할 때 사용합니다.
➕ 12-3. 주의할 점
- 운영 DB에서 컬럼 삭제, 타입 변경, 대량 데이터 수정은 매우 조심해야 합니다.
- 마이그레이션 전에는 반드시 백업을 확인해야 합니다.
- 운영 배포 전 로컬 또는 테스트 DB에서 먼저 검증해야 합니다.
✅ 13. Prisma와 SQL의 관계
- ORM을 사용한다고 해서 SQL을 몰라도 되는 것은 아닙니다.
- ORM은 SQL을 편하게 작성하게 도와주는 도구일 뿐, 실제로는 DB에 SQL이 실행됩니다.
await prisma.consult.findMany({
where: {
status: 'PENDING',
},
});
- 위 Prisma 코드는 내부적으로 다음과 비슷한 SQL로 변환됩니다.
SELECT * FROM Consult
WHERE status = 'PENDING';
- 실무에서 문제가 생겼을 때는 ORM 코드만 보는 것이 아니라, 실제 실행되는 쿼리와 DB 상태를 함께 확인해야 합니다.
✅ 14. 실무에서 자주 생기는 DB 문제
➕ 14-1. DB 연결 실패
Can't reach database server at localhost:5432
- 이런 오류가 발생하면 먼저 DB가 실제로 실행 중인지, 포트가 맞는지, 환경변수가 맞는지 확인해야 합니다.
➕ 14-2. 느린 쿼리
➕ 14-3. 데이터 중복
model User {
id Int @id @default(autoincrement())
email String @unique
}
➕ 14-4. 잘못된 삭제
model Consult {
id Int @id @default(autoincrement())
name String
phone String
deletedAt DateTime?
}
✅ 15. 실무 체크리스트
➕ 15-1. 테이블 설계 체크리스트
- 이 데이터는 어떤 테이블에 저장해야 하는가?
- 기본키가 있는가?
- 필수값과 선택값이 구분되어 있는가?
- 중복되면 안 되는 컬럼에 unique 제약조건이 있는가?
- 다른 테이블과 관계가 필요한가?
- 자주 검색하는 컬럼에 인덱스가 필요한가?
- 생성일과 수정일 컬럼이 있는가?
- 삭제가 필요한 데이터라면 소프트 삭제를 고려했는가?
- 개인정보가 포함되는가?
- 백업과 복구가 필요한 중요한 데이터인가?
➕ 15-2. Prisma 사용 체크리스트
schema.prisma 모델이 실제 요구사항과 맞는가?
- 마이그레이션 파일을 생성했는가?
- 운영 배포 전 테스트 DB에서 검증했는가?
DATABASE_URL이 환경별로 올바른가?
- Prisma Client를 재생성했는가?
- 관계 설정이 올바른가?
- unique, index, relation 설정이 필요한 곳에 적용되었는가?
- 대량 조회 API에
take, skip 같은 제한이 있는가?
- 삭제 로직이 안전한가?
- 에러 발생 시 서버 로그에 원인을 남기는가?
📌 요약
- 데이터베이스는 서비스의 핵심 데이터를 저장하고 관리하는 공간입니다.
- 관계형 DB는 데이터를 테이블, 컬럼, 레코드 형태로 저장하며, 기본키와 외래키를 통해 데이터 관계를 표현합니다.
- SQL은 데이터를 조회, 생성, 수정, 삭제하기 위한 언어이며,
SELECT, INSERT, UPDATE, DELETE는 반드시 알아야 합니다.
- JOIN은 여러 테이블의 데이터를 함께 조회할 때 사용하고, 인덱스는 조회 성능을 개선하는 데 사용합니다.
- ORM은 코드로 DB를 다룰 수 있게 해주는 도구이며, Prisma는 TypeScript/NestJS 환경에서 자주 사용됩니다.
- ORM을 사용해도 SQL과 DB 기본 개념은 반드시 알아야 합니다.
- 마이그레이션은 DB 구조 변경 이력을 관리하는 작업이며, 운영 배포 전 백업과 테스트가 중요합니다.
- 실무에서는 DB 연결 실패, 느린 쿼리, 데이터 중복, 잘못된 삭제 같은 문제가 자주 발생하므로 기본 체크리스트를 갖추는 것이 중요합니다.