포스트

[Python 100일 챌린지] Day 55 - API 설계 기초

[Python 100일 챌린지] Day 55 - API 설계 기초

지금까지 API를 “사용”만 했다면, 이제 직접 “만들어” 봅시다! 😊

Day 51-54에서 requests로 다른 서비스의 API를 호출했죠? 오늘은 내가 API를 만들어서 다른 사람에게 제공하는 방법을 배웁니다! Flask라는 간단한 프레임워크로 나만의 웹 서버를 만들어봐요! 🚀

(40-50분 완독 ⭐⭐⭐)

🎯 오늘의 학습 목표

📚 사전 지식


🎯 학습 목표 1: API 설계 원칙 이해하기

1.0 API 제공자 vs 소비자

지금까지는 API 소비자(Consumer) 입장이었어요:

1
[우리 프로그램] --requests.get()--> [다른 서비스의 API] --응답--> [데이터 받기]

오늘부터는 API 제공자(Provider) 입장을 배웁니다:

1
[다른 사람/프로그램] --요청--> [우리가 만든 API 서버] --응답--> [데이터 제공]

💡 비유: 지금까지 식당에서 손님으로 음식을 주문했다면, 이제 주방장이 되어 메뉴를 만드는 거예요!

1.1 좋은 API란?

좋은 API의 특징:

특징 설명 예시
일관성 네이밍, 구조, 응답이 일관적 모든 리소스 URL이 복수형 (/users, /posts)
직관성 문서 없이도 이해 가능 GET /users/123 → 123번 사용자 조회
예측 가능성 결과를 예측할 수 있음 DELETE 성공 → 204 No Content
단순성 복잡하지 않고 명확함 하나의 엔드포인트는 하나의 역할만
확장 가능성 쉽게 기능 추가 가능 버전 관리 (/api/v1/...)

1.2 RESTful API 설계 원칙

REST (Representational State Transfer) 아키텍처 스타일의 핵심 원칙:

1. 자원 중심 설계 (Resource-Oriented):

1
2
3
4
5
6
7
8
9
10
11
✅ 좋은 예:
GET    /api/users          # 사용자 목록
GET    /api/users/123      # 특정 사용자
POST   /api/users          # 사용자 생성
PUT    /api/users/123      # 사용자 전체 수정
DELETE /api/users/123      # 사용자 삭제

❌ 나쁜 예:
GET    /api/getUsers
POST   /api/createNewUser
POST   /api/deleteUserById?id=123

2. HTTP 메서드 적절히 사용:

메서드 용도 예시 성공 응답
GET 조회 GET /users 200 OK
POST 생성 POST /users 201 Created
PUT 전체 수정 PUT /users/123 200 OK
PATCH 부분 수정 PATCH /users/123 200 OK
DELETE 삭제 DELETE /users/123 204 No Content

3. 무상태성 (Stateless):

1
2
3
4
5
6
7
8
# ✅ 좋은 예: 모든 요청에 필요한 정보 포함
headers = {
    'Authorization': 'Bearer token123',
    'Content-Type': 'application/json'
}

# ❌ 나쁜 예: 서버 세션에 의존
# 클라이언트가 "이전에 로그인했으니 기억하겠지" 가정

1.3 URL 설계 Best Practices

1. 명사 사용, 동사 피하기:

1
2
3
4
5
6
7
✅ /users
✅ /posts
✅ /products

❌ /getUsers
❌ /createPost
❌ /deleteProduct

2. 복수형 사용:

1
2
3
4
✅ /users/123        (일관성)
✅ /posts/456

❌ /user/123         (혼란 유발)

3. 계층 구조 표현:

1
2
3
4
5
6
7
8
# 사용자의 게시글
GET /users/123/posts

# 게시글의 댓글
GET /posts/456/comments

# 특정 댓글
GET /posts/456/comments/789

4. 필터링, 정렬, 페이지네이션:

1
2
# 쿼리 파라미터 사용
GET /users?status=active&sort=name&page=2&limit=20

1.4 Flask로 간단한 API 만들기

Flask란?

Flask는 Python으로 웹 서버를 만드는 마이크로 프레임워크예요. “마이크로”라서 배우기 쉽고, 필요한 기능만 골라 쓸 수 있어요.

1
2
# 설치
pip install flask

설치 확인:

1
python -c "import flask; print(f'Flask {flask.__version__} 설치 완료!')"

첫 번째 API 서버 만들기:

app.py 파일을 만들고 다음 코드를 작성하세요:

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
from flask import Flask, jsonify, request

# Flask 앱 생성 (__name__은 현재 파일을 의미)
app = Flask(__name__)

# 간단한 Hello World API
# @app.route: 이 URL로 요청이 오면 아래 함수를 실행해라!
@app.route('/api/hello', methods=['GET'])
def hello():
    # jsonify: Python 딕셔너리를 JSON 형식으로 변환해서 응답
    return jsonify({
        'message': 'Hello, World!',
        'status': 'success'
    })

# 상태 확인 API
@app.route('/api/health', methods=['GET'])
def health():
    return jsonify({
        'status': 'healthy',
        'version': '1.0.0'
    })

# 이 파일을 직접 실행할 때만 서버 시작
if __name__ == '__main__':
    # debug=True: 코드 수정 시 자동 재시작 + 에러 상세 표시
    # port=5000: 5000번 포트에서 서버 실행
    app.run(debug=True, port=5000)

💡 @app.route() 이해하기: @로 시작하는 건 데코레이터라는 Python 문법이에요. 지금은 “이 URL 패턴과 함수를 연결한다”고만 이해하면 돼요!

실행 및 테스트:

1
2
# 터미널 1: 서버 실행
python app.py

실행하면 다음과 같은 메시지가 나와요:

1
2
 * Running on http://127.0.0.1:5000
 * Debug mode: on

⚠️ 중요: 서버가 실행 중인 터미널은 그대로 두세요! 새 터미널을 열어서 테스트합니다.

1
2
# 터미널 2: 테스트 (새 터미널 열기!)
curl http://localhost:5000/api/hello

또는 웹 브라우저에서 http://localhost:5000/api/hello 주소로 직접 접속해도 됩니다!

💡 localhost란?: 내 컴퓨터 자체를 가리키는 특별한 주소예요. 127.0.0.1과 같은 의미입니다.

서버를 종료하려면 터미널 1에서 Ctrl+C를 누르세요.


🎯 학습 목표 2: 엔드포인트 구조 설계하기

2.1 CRUD API 설계

CRUD란?

데이터를 다루는 4가지 기본 작업의 약자예요:

  • Create (생성) → POST
  • Read (조회) → GET
  • Update (수정) → PUT/PATCH
  • Delete (삭제) → DELETE

💡 자주 쓰는 코드 패턴 미리 알기: 아래 코드에서 next((u for u in users if u['id'] == user_id), None) 패턴이 자주 나와요. 이건 “리스트에서 조건에 맞는 첫 번째 항목을 찾고, 없으면 None을 반환”하는 코드예요. 지금은 이 패턴을 통째로 외워두세요!

User 리소스 완전한 CRUD:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from flask import Flask, jsonify, request, abort

app = Flask(__name__)

# In-memory 데이터 (실제로는 DB 사용)
users = [
    {'id': 1, 'name': 'Alice', 'email': '[email protected]', 'age': 25},
    {'id': 2, 'name': 'Bob', 'email': '[email protected]', 'age': 30}
]
next_id = 3

# 1. CREATE - 사용자 생성
@app.route('/api/users', methods=['POST'])
def create_user():
    global next_id

    data = request.get_json()

    # 유효성 검사
    if not data or 'name' not in data or 'email' not in data:
        return jsonify({'error': 'Name and email are required'}), 400

    # 새 사용자 생성
    new_user = {
        'id': next_id,
        'name': data['name'],
        'email': data['email'],
        'age': data.get('age')  # 선택 사항
    }

    users.append(new_user)
    next_id += 1

    return jsonify(new_user), 201  # 201 Created

# 2. READ - 모든 사용자 조회
@app.route('/api/users', methods=['GET'])
def get_users():
    # 쿼리 파라미터로 필터링
    status = request.args.get('status')
    page = request.args.get('page', 1, type=int)
    limit = request.args.get('limit', 10, type=int)

    # 페이지네이션
    start = (page - 1) * limit
    end = start + limit
    paginated_users = users[start:end]

    return jsonify({
        'data': paginated_users,
        'page': page,
        'limit': limit,
        'total': len(users)
    })

# 3. READ - 특정 사용자 조회
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = next((u for u in users if u['id'] == user_id), None)

    if not user:
        return jsonify({'error': 'User not found'}), 404

    return jsonify(user)

# 4. UPDATE - 사용자 전체 수정 (PUT)
@app.route('/api/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
    user = next((u for u in users if u['id'] == user_id), None)

    if not user:
        return jsonify({'error': 'User not found'}), 404

    data = request.get_json()

    # 전체 교체 (PUT)
    user['name'] = data.get('name', user['name'])
    user['email'] = data.get('email', user['email'])
    user['age'] = data.get('age')

    return jsonify(user)

# 5. UPDATE - 사용자 부분 수정 (PATCH)
@app.route('/api/users/<int:user_id>', methods=['PATCH'])
def patch_user(user_id):
    user = next((u for u in users if u['id'] == user_id), None)

    if not user:
        return jsonify({'error': 'User not found'}), 404

    data = request.get_json()

    # 제공된 필드만 업데이트
    if 'name' in data:
        user['name'] = data['name']
    if 'email' in data:
        user['email'] = data['email']
    if 'age' in data:
        user['age'] = data['age']

    return jsonify(user)

# 6. DELETE - 사용자 삭제
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
    global users

    user = next((u for u in users if u['id'] == user_id), None)

    if not user:
        return jsonify({'error': 'User not found'}), 404

    users = [u for u in users if u['id'] != user_id]

    return '', 204  # 204 No Content (성공, 본문 없음)

if __name__ == '__main__':
    app.run(debug=True)

2.2 중첩 리소스 (Nested Resources)

리소스 간에 부모-자식 관계가 있을 때 URL로 표현할 수 있어요.

💡 예시: “사용자가 작성한 게시글”을 표현하려면 /users/1/posts처럼 사용자(부모) 아래에 게시글(자식)을 배치해요.

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
32
33
34
35
36
37
38
39
40
41
42
43
# 사용자의 게시글
posts = [
    {'id': 1, 'user_id': 1, 'title': '첫 번째 글', 'content': '...'},
    {'id': 2, 'user_id': 1, 'title': '두 번째 글', 'content': '...'},
    {'id': 3, 'user_id': 2, 'title': '밥의 글', 'content': '...'}
]

# 특정 사용자의 모든 게시글
@app.route('/api/users/<int:user_id>/posts', methods=['GET'])
def get_user_posts(user_id):
    # 사용자 존재 확인
    user = next((u for u in users if u['id'] == user_id), None)
    if not user:
        return jsonify({'error': 'User not found'}), 404

    # 해당 사용자의 게시글 필터링
    user_posts = [p for p in posts if p['user_id'] == user_id]

    return jsonify({
        'user_id': user_id,
        'posts': user_posts,
        'count': len(user_posts)
    })

# 특정 사용자의 게시글 생성
@app.route('/api/users/<int:user_id>/posts', methods=['POST'])
def create_user_post(user_id):
    user = next((u for u in users if u['id'] == user_id), None)
    if not user:
        return jsonify({'error': 'User not found'}), 404

    data = request.get_json()

    new_post = {
        'id': len(posts) + 1,
        'user_id': user_id,
        'title': data['title'],
        'content': data['content']
    }

    posts.append(new_post)

    return jsonify(new_post), 201

2.3 검색과 필터링

쿼리 파라미터를 사용하면 URL에 검색 조건을 추가할 수 있어요.

💡 쿼리 파라미터란?: URL 뒤에 ?로 시작하는 부분이에요. 예: /api/users/search?name=alice&min_age=20

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
@app.route('/api/users/search', methods=['GET'])
def search_users():
    # 쿼리 파라미터
    name = request.args.get('name', '').lower()
    min_age = request.args.get('min_age', type=int)
    max_age = request.args.get('max_age', type=int)

    # 필터링
    results = users

    if name:
        results = [u for u in results if name in u['name'].lower()]

    if min_age:
        results = [u for u in results if u.get('age', 0) >= min_age]

    if max_age:
        results = [u for u in results if u.get('age', 999) <= max_age]

    return jsonify({
        'query': {
            'name': name,
            'min_age': min_age,
            'max_age': max_age
        },
        'results': results,
        'count': len(results)
    })

🎯 학습 목표 3: 요청과 응답 형식 정의하기

API를 사용할 때 클라이언트가 보내는 요청(Request)과 서버가 돌려주는 응답(Response)의 형식을 알아봅시다.

3.1 요청 형식 (Request Format)

1. Path Parameters (경로 매개변수):

1
2
3
4
5
# /api/users/123
@app.route('/api/users/<int:user_id>')
def get_user(user_id):
    # user_id는 URL에서 추출
    pass

2. Query Parameters (쿼리 매개변수):

1
2
3
4
5
6
# /api/users?page=2&limit=10&status=active
@app.route('/api/users')
def get_users():
    page = request.args.get('page', 1, type=int)
    limit = request.args.get('limit', 10, type=int)
    status = request.args.get('status')

3. Request Body (요청 본문):

1
2
3
4
5
6
7
# POST /api/users
# Body: {"name": "Charlie", "email": "[email protected]"}
@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()
    name = data.get('name')
    email = data.get('email')

3.2 응답 형식 (Response Format)

표준 응답 구조:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 성공 응답
{
    "status": "success",
    "data": {
        "id": 123,
        "name": "Alice"
    }
}

# 에러 응답
{
    "status": "error",
    "error": {
        "code": "USER_NOT_FOUND",
        "message": "User with ID 123 not found"
    }
}

응답 헬퍼 함수:

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
def success_response(data, status_code=200):
    """성공 응답 생성"""
    return jsonify({
        'status': 'success',
        'data': data
    }), status_code

def error_response(message, code='ERROR', status_code=400):
    """에러 응답 생성"""
    return jsonify({
        'status': 'error',
        'error': {
            'code': code,
            'message': message
        }
    }), status_code

# 사용
@app.route('/api/users/<int:user_id>')
def get_user(user_id):
    user = next((u for u in users if u['id'] == user_id), None)

    if not user:
        return error_response('User not found', 'USER_NOT_FOUND', 404)

    return success_response(user)

3.3 HTTP 상태 코드

자주 사용하는 상태 코드:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2xx 성공
return jsonify(data), 200  # OK
return jsonify(data), 201  # Created
return '', 204             # No Content

# 4xx 클라이언트 오류
return jsonify(error), 400  # Bad Request
return jsonify(error), 401  # Unauthorized
return jsonify(error), 403  # Forbidden
return jsonify(error), 404  # Not Found
return jsonify(error), 409  # Conflict

# 5xx 서버 오류
return jsonify(error), 500  # Internal Server Error

3.4 에러 핸들링

에러가 발생했을 때 적절한 응답을 반환하는 방법입니다. Flask의 에러 핸들러를 사용하면 에러별로 일관된 응답을 만들 수 있어요.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from flask import Flask, jsonify

app = Flask(__name__)

# 404 Not Found 핸들러
@app.errorhandler(404)
def not_found(error):
    return jsonify({
        'status': 'error',
        'error': {
            'code': 'NOT_FOUND',
            'message': 'Resource not found'
        }
    }), 404

# 500 Internal Server Error 핸들러
@app.errorhandler(500)
def internal_error(error):
    return jsonify({
        'status': 'error',
        'error': {
            'code': 'INTERNAL_ERROR',
            'message': 'An internal error occurred'
        }
    }), 500

# 커스텀 예외
class ValidationError(Exception):
    pass

@app.errorhandler(ValidationError)
def handle_validation_error(error):
    return jsonify({
        'status': 'error',
        'error': {
            'code': 'VALIDATION_ERROR',
            'message': str(error)
        }
    }), 400

# 사용
@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()

    if not data or 'name' not in data:
        raise ValidationError('Name is required')

    # 사용자 생성...

🎯 학습 목표 4: API 문서화 방법 익히기

좋은 API는 문서화가 잘 되어 있어야 해요. 다른 개발자가 API를 쉽게 이해하고 사용할 수 있도록 문서를 작성하는 방법을 배워봅시다.

4.1 수동 문서화 (Markdown)

API 문서 예시 (API_DOCS.md):

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
# User API Documentation

## Get All Users

**Endpoint**: `GET /api/users`

**Query Parameters**:
- `page` (integer, optional): 페이지 번호 (default: 1)
- `limit` (integer, optional): 페이지당 항목 수 (default: 10)

**Response**: 200 OK
```json
{
  "data": [
    {
      "id": 1,
      "name": "Alice",
      "email": "[email protected]",
      "age": 25
    }
  ],
  "page": 1,
  "limit": 10,
  "total": 50
}

Errors:

  • 500: Internal server error

Create User

Endpoint: POST /api/users

Request Body:

1
2
3
4
5
{
  "name": "Charlie",
  "email": "[email protected]",
  "age": 28
}

Response: 201 Created

1
2
3
4
5
6
{
  "id": 3,
  "name": "Charlie",
  "email": "[email protected]",
  "age": 28
}

Errors:

  • 400: Validation error (name or email missing) ```

4.2 코드 내 문서화 (Docstrings)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@app.route('/api/users', methods=['GET'])
def get_users():
    """
    Get all users with pagination and filtering.

    Query Parameters:
        page (int): Page number (default: 1)
        limit (int): Items per page (default: 10)
        status (str): Filter by status

    Returns:
        200: List of users with pagination info
        {
            "data": [...],
            "page": 1,
            "limit": 10,
            "total": 50
        }

    Example:
        GET /api/users?page=2&limit=20&status=active
    """
    pass

4.3 Postman Collection

💡 Postman이란?: API를 테스트할 수 있는 무료 도구예요. curl 대신 GUI로 편하게 API를 호출하고, 요청/응답을 저장해서 문서로 만들 수 있어요. postman.com에서 다운로드하세요.

Postman으로 API 테스트하고 문서화:

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
{
  "info": {
    "name": "User API",
    "description": "User management API"
  },
  "item": [
    {
      "name": "Get All Users",
      "request": {
        "method": "GET",
        "url": "http://localhost:5000/api/users",
        "description": "Retrieve all users"
      }
    },
    {
      "name": "Create User",
      "request": {
        "method": "POST",
        "url": "http://localhost:5000/api/users",
        "body": {
          "mode": "raw",
          "raw": "{\"name\": \"Test\", \"email\": \"[email protected]\"}"
        }
      }
    }
  ]
}

4.4 API 버전 관리

API를 업데이트할 때 기존 사용자들이 갑자기 오류를 겪지 않도록 버전을 관리해요.

💡 왜 버전 관리가 필요한가요?: 새 버전에서 응답 형식이 바뀌면 기존 앱이 망가질 수 있어요. 버전을 분리하면 기존 앱은 v1을 계속 쓰고, 새 앱은 v2를 쓸 수 있어요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 버전 1
@app.route('/api/v1/users', methods=['GET'])
def get_users_v1():
    return jsonify(users)

# 버전 2 (다른 응답 형식)
@app.route('/api/v2/users', methods=['GET'])
def get_users_v2():
    return jsonify({
        'version': 'v2',
        'users': users,
        'meta': {
            'total': len(users)
        }
    })

💡 실전 팁 & 주의사항

✅ DO: 이렇게 하세요

  1. 일관된 네이밍
    • 복수형 리소스: /users, /posts
    • 소문자 사용: /api/users (not /API/Users)
  2. 적절한 HTTP 메서드 사용
    • GET: 조회만
    • POST: 생성만
    • PUT/PATCH: 수정만
    • DELETE: 삭제만
  3. 의미 있는 상태 코드
    1
    2
    
    return jsonify(user), 201  # 생성 성공
    return '', 204             # 삭제 성공
    
  4. 에러 정보 명확하게
    1
    2
    3
    4
    
    return jsonify({
        'error': 'Email already exists',
        'field': 'email'
    }), 400
    

❌ DON’T: 이러지 마세요

  1. URL에 동사 사용
    1
    2
    3
    4
    5
    
    # 나쁜 예
    @app.route('/api/getUserById/<id>')
    
    # 좋은 예
    @app.route('/api/users/<id>')
    
  2. 모든 요청을 POST로
    • 각 작업에 맞는 HTTP 메서드 사용
  3. 에러 시 항상 500 반환
    • 클라이언트 오류는 4xx
    • 서버 오류만 5xx

🧪 연습 문제

💡 실습 팁: 2.1절에서 배운 User CRUD 코드를 참고해서 비슷하게 만들어보세요!

문제 1: Blog Post API 설계

블로그 포스트 API를 설계하세요.

요구사항:

  • 게시글 CRUD (생성, 조회, 수정, 삭제)
  • 목록 조회 시 페이지네이션
  • 제목으로 검색
  • 적절한 상태 코드 반환
💡 힌트
  1. /api/posts 엔드포인트
  2. GET, POST, PUT, DELETE 메서드
  3. request.args.get() for 쿼리 파라미터
  4. 201 Created, 204 No Content, 404 Not Found
✅ 정답
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
from flask import Flask, jsonify, request

app = Flask(__name__)

posts = [
    {'id': 1, 'title': '첫 포스트', 'content': '내용 1', 'author': 'Alice'},
    {'id': 2, 'title': '두 번째 포스트', 'content': '내용 2', 'author': 'Bob'}
]
next_id = 3

# 목록 조회 (페이지네이션 + 검색)
@app.route('/api/posts', methods=['GET'])
def get_posts():
    page = request.args.get('page', 1, type=int)
    limit = request.args.get('limit', 10, type=int)
    search = request.args.get('search', '').lower()

    # 검색
    filtered = posts
    if search:
        filtered = [p for p in posts if search in p['title'].lower()]

    # 페이지네이션
    start = (page - 1) * limit
    end = start + limit
    paginated = filtered[start:end]

    return jsonify({
        'data': paginated,
        'page': page,
        'total': len(filtered)
    })

# 생성
@app.route('/api/posts', methods=['POST'])
def create_post():
    global next_id
    data = request.get_json()

    if not data or 'title' not in data or 'content' not in data:
        return jsonify({'error': 'Title and content required'}), 400

    new_post = {
        'id': next_id,
        'title': data['title'],
        'content': data['content'],
        'author': data.get('author', 'Anonymous')
    }

    posts.append(new_post)
    next_id += 1

    return jsonify(new_post), 201

# 조회
@app.route('/api/posts/<int:post_id>', methods=['GET'])
def get_post(post_id):
    post = next((p for p in posts if p['id'] == post_id), None)

    if not post:
        return jsonify({'error': 'Post not found'}), 404

    return jsonify(post)

# 수정
@app.route('/api/posts/<int:post_id>', methods=['PUT'])
def update_post(post_id):
    post = next((p for p in posts if p['id'] == post_id), None)

    if not post:
        return jsonify({'error': 'Post not found'}), 404

    data = request.get_json()
    post.update(data)

    return jsonify(post)

# 삭제
@app.route('/api/posts/<int:post_id>', methods=['DELETE'])
def delete_post(post_id):
    global posts

    post = next((p for p in posts if p['id'] == post_id), None)

    if not post:
        return jsonify({'error': 'Post not found'}), 404

    posts = [p for p in posts if p['id'] != post_id]

    return '', 204

if __name__ == '__main__':
    app.run(debug=True)

📝 오늘 배운 내용 정리

주제 핵심 내용
API 설계 원칙 일관성, 직관성, 예측 가능성, REST 아키텍처
엔드포인트 리소스 중심, HTTP 메서드, URL 구조
요청/응답 경로/쿼리 파라미터, JSON, 상태 코드
문서화 Markdown, Docstring, Postman, 버전 관리

핵심 코드 패턴:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Flask 기본 구조
from flask import Flask, jsonify, request

app = Flask(__name__)

# CRUD 패턴
@app.route('/api/resource', methods=['GET'])     # 목록 조회
@app.route('/api/resource', methods=['POST'])    # 생성
@app.route('/api/resource/<id>', methods=['GET'])    # 단일 조회
@app.route('/api/resource/<id>', methods=['PUT'])    # 수정
@app.route('/api/resource/<id>', methods=['DELETE']) # 삭제

# 응답 패턴
return jsonify(data), 200   # 성공
return jsonify(error), 404  # 실패

🔗 관련 자료


📚 이전 학습

Day 54: 웹 스크래핑 고급 ⭐⭐⭐

어제는 동적 콘텐츠, 페이지네이션, 재시도 로직, 윤리적 스크래핑을 배웠습니다!

📚 다음 학습

Day 56: RESTful API 실전 ⭐⭐⭐

내일은 RESTful 원칙을 실전 적용하고 CRUD API를 완성합니다!


“늦었다고 생각할 때가 가장 빠른 때입니다. 오늘도 한 걸음 성장했어요!” 🚀

Day 55/100 Phase 6: 웹 스크래핑과 API #100DaysOfPython
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.