566 lines
13 KiB
Markdown
566 lines
13 KiB
Markdown
|
|
# API 사용 가이드
|
||
|
|
|
||
|
|
이 문서는 데이터베이스에 데이터를 생성하는 API의 사용 방법을 설명합니다.
|
||
|
|
|
||
|
|
## 📋 목차
|
||
|
|
|
||
|
|
1. [기본 설정](#기본-설정)
|
||
|
|
2. [사용자 API](#1-사용자-api)
|
||
|
|
3. [교육과정 API](#2-교육과정-api)
|
||
|
|
4. [강좌 API](#3-강좌-api)
|
||
|
|
5. [공지사항 API](#4-공지사항-api)
|
||
|
|
6. [에러 처리](#에러-처리)
|
||
|
|
7. [실전 예제](#실전-예제)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 기본 설정
|
||
|
|
|
||
|
|
### 환경 변수
|
||
|
|
|
||
|
|
`.env` 파일에 데이터베이스 연결 정보가 설정되어 있어야 합니다:
|
||
|
|
|
||
|
|
```env
|
||
|
|
DATABASE_URL="postgresql://user:password@localhost:5432/dbname"
|
||
|
|
```
|
||
|
|
|
||
|
|
### API 기본 URL
|
||
|
|
|
||
|
|
- 개발 환경: `http://localhost:3000/api`
|
||
|
|
- 프로덕션: `https://your-domain.com/api`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. 사용자 API
|
||
|
|
|
||
|
|
### POST /api/users - 사용자 생성
|
||
|
|
|
||
|
|
새로운 사용자를 생성합니다.
|
||
|
|
|
||
|
|
#### 요청
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const response = await fetch('/api/users', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
email: 'user@example.com',
|
||
|
|
password: 'hashed_password_here', // 실제로는 해시화된 비밀번호
|
||
|
|
name: '홍길동',
|
||
|
|
phone: '010-1234-5678', // 선택사항
|
||
|
|
gender: 'M', // 선택사항: 'M' 또는 'F'
|
||
|
|
birthdate: '1990-01-01', // 선택사항: YYYY-MM-DD 형식
|
||
|
|
role: 'LEARNER', // 선택사항: 'LEARNER' 또는 'ADMIN' (기본값: 'LEARNER')
|
||
|
|
status: 'ACTIVE', // 선택사항: 'ACTIVE' 또는 'INACTIVE' (기본값: 'ACTIVE')
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 성공 응답 (201)
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"message": "사용자가 성공적으로 생성되었습니다.",
|
||
|
|
"user": {
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"email": "user@example.com",
|
||
|
|
"name": "홍길동",
|
||
|
|
"phone": "010-1234-5678",
|
||
|
|
"gender": "M",
|
||
|
|
"birthdate": "1990-01-01T00:00:00.000Z",
|
||
|
|
"role": "LEARNER",
|
||
|
|
"status": "ACTIVE",
|
||
|
|
"joinDate": "2024-11-21T00:00:00.000Z",
|
||
|
|
"createdAt": "2024-11-21T00:00:00.000Z",
|
||
|
|
"updatedAt": "2024-11-21T00:00:00.000Z"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 에러 응답
|
||
|
|
|
||
|
|
**400 Bad Request** - 필수 필드 누락
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"error": "이메일, 비밀번호, 이름은 필수입니다."
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**409 Conflict** - 이메일 중복
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"error": "이미 존재하는 이메일입니다."
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### GET /api/users - 사용자 목록 조회
|
||
|
|
|
||
|
|
사용자 목록을 조회합니다. 필터링 및 페이지네이션을 지원합니다.
|
||
|
|
|
||
|
|
#### 요청
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 전체 사용자 조회
|
||
|
|
const response = await fetch('/api/users');
|
||
|
|
|
||
|
|
// 필터링 및 페이지네이션
|
||
|
|
const response = await fetch('/api/users?role=LEARNER&status=ACTIVE&page=1&limit=10');
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 쿼리 파라미터
|
||
|
|
|
||
|
|
- `role` (선택): `LEARNER` 또는 `ADMIN`
|
||
|
|
- `status` (선택): `ACTIVE` 또는 `INACTIVE`
|
||
|
|
- `page` (선택): 페이지 번호 (기본값: 1)
|
||
|
|
- `limit` (선택): 페이지당 항목 수 (기본값: 10)
|
||
|
|
|
||
|
|
#### 성공 응답 (200)
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"users": [
|
||
|
|
{
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"email": "user@example.com",
|
||
|
|
"name": "홍길동",
|
||
|
|
"role": "LEARNER",
|
||
|
|
"status": "ACTIVE",
|
||
|
|
...
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"pagination": {
|
||
|
|
"page": 1,
|
||
|
|
"limit": 10,
|
||
|
|
"total": 30,
|
||
|
|
"totalPages": 3
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. 교육과정 API
|
||
|
|
|
||
|
|
### POST /api/courses - 교육과정 생성
|
||
|
|
|
||
|
|
새로운 교육과정을 생성합니다.
|
||
|
|
|
||
|
|
#### 요청
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const response = await fetch('/api/courses', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
courseName: '웹 개발 기초',
|
||
|
|
instructorId: 'instructor-uuid-here', // 필수: 강사(ADMIN 역할)의 ID
|
||
|
|
createdById: 'admin-uuid-here', // 선택사항: 등록자 ID (기본값: instructorId)
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 성공 응답 (201)
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"message": "교육과정이 성공적으로 생성되었습니다.",
|
||
|
|
"course": {
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"courseName": "웹 개발 기초",
|
||
|
|
"instructorId": "instructor-uuid",
|
||
|
|
"createdById": "admin-uuid",
|
||
|
|
"createdAt": "2024-11-21T00:00:00.000Z",
|
||
|
|
"instructor": {
|
||
|
|
"id": "instructor-uuid",
|
||
|
|
"name": "최예준",
|
||
|
|
"email": "instructor@example.com"
|
||
|
|
},
|
||
|
|
"createdBy": {
|
||
|
|
"id": "admin-uuid",
|
||
|
|
"name": "관리자"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 에러 응답
|
||
|
|
|
||
|
|
**400 Bad Request** - 필수 필드 누락
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"error": "교육과정명과 강사 ID는 필수입니다."
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**404 Not Found** - 강사를 찾을 수 없음
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"error": "강사를 찾을 수 없습니다."
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### GET /api/courses - 교육과정 목록 조회
|
||
|
|
|
||
|
|
교육과정 목록을 조회합니다.
|
||
|
|
|
||
|
|
#### 요청
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 전체 교육과정 조회
|
||
|
|
const response = await fetch('/api/courses');
|
||
|
|
|
||
|
|
// 특정 강사의 교육과정 조회
|
||
|
|
const response = await fetch('/api/courses?instructorId=instructor-uuid&page=1&limit=10');
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 쿼리 파라미터
|
||
|
|
|
||
|
|
- `instructorId` (선택): 강사 ID로 필터링
|
||
|
|
- `page` (선택): 페이지 번호 (기본값: 1)
|
||
|
|
- `limit` (선택): 페이지당 항목 수 (기본값: 10)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. 강좌 API
|
||
|
|
|
||
|
|
### POST /api/lessons - 강좌 생성
|
||
|
|
|
||
|
|
새로운 강좌를 생성합니다.
|
||
|
|
|
||
|
|
#### 요청
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const response = await fetch('/api/lessons', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
courseId: 'course-uuid-here', // 필수: 교육과정 ID
|
||
|
|
lessonName: 'HTML 기초', // 필수: 강좌명
|
||
|
|
learningGoal: 'HTML의 기본 문법을 이해하고 활용할 수 있다.', // 선택사항: 학습 목표
|
||
|
|
createdById: 'admin-uuid-here', // 선택사항: 등록자 ID
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 성공 응답 (201)
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"message": "강좌가 성공적으로 생성되었습니다.",
|
||
|
|
"lesson": {
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"courseId": "course-uuid",
|
||
|
|
"lessonName": "HTML 기초",
|
||
|
|
"learningGoal": "HTML의 기본 문법을 이해하고 활용할 수 있다.",
|
||
|
|
"createdAt": "2024-11-21T00:00:00.000Z",
|
||
|
|
"course": {
|
||
|
|
"id": "course-uuid",
|
||
|
|
"courseName": "웹 개발 기초"
|
||
|
|
},
|
||
|
|
"createdBy": {
|
||
|
|
"id": "admin-uuid",
|
||
|
|
"name": "관리자"
|
||
|
|
},
|
||
|
|
"_count": {
|
||
|
|
"videos": 0,
|
||
|
|
"vrContents": 0,
|
||
|
|
"questions": 0
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### GET /api/lessons - 강좌 목록 조회
|
||
|
|
|
||
|
|
강좌 목록을 조회합니다.
|
||
|
|
|
||
|
|
#### 요청
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 전체 강좌 조회
|
||
|
|
const response = await fetch('/api/lessons');
|
||
|
|
|
||
|
|
// 특정 교육과정의 강좌 조회
|
||
|
|
const response = await fetch('/api/lessons?courseId=course-uuid&page=1&limit=10');
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. 공지사항 API
|
||
|
|
|
||
|
|
### POST /api/notices - 공지사항 생성
|
||
|
|
|
||
|
|
새로운 공지사항을 생성합니다.
|
||
|
|
|
||
|
|
#### 요청
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const response = await fetch('/api/notices', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
title: '공지사항 제목',
|
||
|
|
content: '공지사항 내용입니다.\n여러 줄로 작성할 수 있습니다.',
|
||
|
|
writerId: 'admin-uuid-here', // 필수: 작성자 ID
|
||
|
|
hasAttachment: false, // 선택사항: 첨부파일 여부 (기본값: false)
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 성공 응답 (201)
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"message": "공지사항이 성공적으로 생성되었습니다.",
|
||
|
|
"notice": {
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"title": "공지사항 제목",
|
||
|
|
"content": "공지사항 내용입니다.\n여러 줄로 작성할 수 있습니다.",
|
||
|
|
"writerId": "admin-uuid",
|
||
|
|
"views": 0,
|
||
|
|
"hasAttachment": false,
|
||
|
|
"date": "2024-11-21T00:00:00.000Z",
|
||
|
|
"writer": {
|
||
|
|
"id": "admin-uuid",
|
||
|
|
"name": "관리자",
|
||
|
|
"email": "admin@example.com"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### GET /api/notices - 공지사항 목록 조회
|
||
|
|
|
||
|
|
공지사항 목록을 조회합니다.
|
||
|
|
|
||
|
|
#### 요청
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 전체 공지사항 조회
|
||
|
|
const response = await fetch('/api/notices');
|
||
|
|
|
||
|
|
// 특정 작성자의 공지사항 조회
|
||
|
|
const response = await fetch('/api/notices?writerId=admin-uuid&page=1&limit=10');
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 에러 처리
|
||
|
|
|
||
|
|
모든 API는 일관된 에러 응답 형식을 사용합니다:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
try {
|
||
|
|
const response = await fetch('/api/users', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
body: JSON.stringify(userData),
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const error = await response.json();
|
||
|
|
console.error('에러:', error.error);
|
||
|
|
// 에러 처리 로직
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
console.log('성공:', data);
|
||
|
|
} catch (error) {
|
||
|
|
console.error('네트워크 오류:', error);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### HTTP 상태 코드
|
||
|
|
|
||
|
|
- `200` - 성공 (GET 요청)
|
||
|
|
- `201` - 생성 성공 (POST 요청)
|
||
|
|
- `400` - 잘못된 요청 (필수 필드 누락 등)
|
||
|
|
- `404` - 리소스를 찾을 수 없음
|
||
|
|
- `409` - 충돌 (중복 데이터 등)
|
||
|
|
- `500` - 서버 오류
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 실전 예제
|
||
|
|
|
||
|
|
### React 컴포넌트에서 사용하기
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
'use client';
|
||
|
|
|
||
|
|
import { useState } from 'react';
|
||
|
|
|
||
|
|
export default function CreateUserForm() {
|
||
|
|
const [formData, setFormData] = useState({
|
||
|
|
email: '',
|
||
|
|
password: '',
|
||
|
|
name: '',
|
||
|
|
role: 'LEARNER',
|
||
|
|
});
|
||
|
|
const [loading, setLoading] = useState(false);
|
||
|
|
const [message, setMessage] = useState('');
|
||
|
|
|
||
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
||
|
|
e.preventDefault();
|
||
|
|
setLoading(true);
|
||
|
|
setMessage('');
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await fetch('/api/users', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
},
|
||
|
|
body: JSON.stringify(formData),
|
||
|
|
});
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
setMessage(`오류: ${data.error}`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setMessage('사용자가 성공적으로 생성되었습니다!');
|
||
|
|
// 폼 초기화
|
||
|
|
setFormData({ email: '', password: '', name: '', role: 'LEARNER' });
|
||
|
|
} catch (error) {
|
||
|
|
setMessage('네트워크 오류가 발생했습니다.');
|
||
|
|
} finally {
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<form onSubmit={handleSubmit}>
|
||
|
|
<input
|
||
|
|
type="email"
|
||
|
|
placeholder="이메일"
|
||
|
|
value={formData.email}
|
||
|
|
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
||
|
|
required
|
||
|
|
/>
|
||
|
|
<input
|
||
|
|
type="password"
|
||
|
|
placeholder="비밀번호"
|
||
|
|
value={formData.password}
|
||
|
|
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
||
|
|
required
|
||
|
|
/>
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
placeholder="이름"
|
||
|
|
value={formData.name}
|
||
|
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||
|
|
required
|
||
|
|
/>
|
||
|
|
<select
|
||
|
|
value={formData.role}
|
||
|
|
onChange={(e) => setFormData({ ...formData, role: e.target.value })}
|
||
|
|
>
|
||
|
|
<option value="LEARNER">학습자</option>
|
||
|
|
<option value="ADMIN">관리자</option>
|
||
|
|
</select>
|
||
|
|
<button type="submit" disabled={loading}>
|
||
|
|
{loading ? '생성 중...' : '사용자 생성'}
|
||
|
|
</button>
|
||
|
|
{message && <p>{message}</p>}
|
||
|
|
</form>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Server Component에서 사용하기
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// app/admin/users/page.tsx
|
||
|
|
import { prisma } from '@/lib/prisma';
|
||
|
|
|
||
|
|
export default async function UsersPage() {
|
||
|
|
const users = await prisma.user.findMany({
|
||
|
|
take: 10,
|
||
|
|
orderBy: { createdAt: 'desc' },
|
||
|
|
});
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div>
|
||
|
|
<h1>사용자 목록</h1>
|
||
|
|
<ul>
|
||
|
|
{users.map((user) => (
|
||
|
|
<li key={user.id}>
|
||
|
|
{user.name} ({user.email}) - {user.role}
|
||
|
|
</li>
|
||
|
|
))}
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### cURL로 테스트하기
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 사용자 생성
|
||
|
|
curl -X POST http://localhost:3000/api/users \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{
|
||
|
|
"email": "test@example.com",
|
||
|
|
"password": "test123",
|
||
|
|
"name": "테스트 사용자",
|
||
|
|
"role": "LEARNER"
|
||
|
|
}'
|
||
|
|
|
||
|
|
# 사용자 목록 조회
|
||
|
|
curl http://localhost:3000/api/users?role=LEARNER&page=1&limit=10
|
||
|
|
|
||
|
|
# 교육과정 생성
|
||
|
|
curl -X POST http://localhost:3000/api/courses \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{
|
||
|
|
"courseName": "웹 개발 기초",
|
||
|
|
"instructorId": "instructor-uuid-here"
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔒 보안 고려사항
|
||
|
|
|
||
|
|
1. **비밀번호 해시화**: 실제 프로덕션에서는 bcrypt 등을 사용하여 비밀번호를 해시화해야 합니다.
|
||
|
|
2. **인증/인가**: 현재 API는 인증이 없습니다. 프로덕션에서는 JWT 또는 세션 기반 인증을 추가해야 합니다.
|
||
|
|
3. **입력 검증**: 클라이언트 측 검증 외에도 서버 측 검증이 필요합니다.
|
||
|
|
4. **CORS 설정**: 필요시 CORS 설정을 추가해야 합니다.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📚 추가 리소스
|
||
|
|
|
||
|
|
- [Next.js API Routes 문서](https://nextjs.org/docs/app/building-your-application/routing/route-handlers)
|
||
|
|
- [Prisma Client 문서](https://www.prisma.io/docs/concepts/components/prisma-client)
|
||
|
|
|