Featured image of post [Python Master] 23. 웹 개발 - Flask/Django/REST API

[Python Master] 23. 웹 개발 - Flask/Django/REST API

Flask/Django로 웹 앱과 API를 만드는 핵심 개념을 설명합니다. 라우팅, 요청/응답, 데이터베이스 연동, 인증/보안, 배포 관점까지 연결해 전체 흐름을 잡습니다.

챕터 23: 웹 개발

“웹으로 세상과 연결하라” - Flask와 Django를 활용하여 현대적인 웹 애플리케이션을 개발해봅시다.

학습 목표

  • 웹 개발의 기본 개념과 구조를 이해할 수 있다
  • Flask와 Django 프레임워크를 활용할 수 있다
  • RESTful API를 설계하고 구현할 수 있다
  • 웹 애플리케이션의 보안과 배포를 관리할 수 있다

핵심 개념(이론)

1) 웹 개발의 역할과 경계

이 챕터의 핵심은 “무엇을 할 수 있나”가 아니라, 어떤 문제를 해결하고 어디까지 책임지는지를 분명히 하는 것입니다. 경계가 흐리면 코드는 커질수록 결합이 늘어나고 수정 비용이 커집니다.

2) 왜 이 개념이 필요한가(실무 동기)

실무에서는 예외 상황, 성능, 협업, 테스트가 항상 문제를 만듭니다. 따라서 이 주제는 기능이 아니라 **품질(신뢰성/유지보수성/보안)**을 위한 기반으로 이해해야 합니다.

3) 트레이드오프: 간단함 vs 확장성

대부분의 선택은 “더 단순하게”와 “더 확장 가능하게” 사이에서 균형을 잡는 일입니다. 초기에는 단순함을, 장기 운영/팀 협업이 커질수록 확장성을 더 우선합니다.

4) 실패 모드(Failure Modes)를 먼저 생각하라

무엇이 실패하는지(입력, I/O, 동시성, 외부 시스템)를 먼저 떠올리면 설계가 안정적으로 변합니다. 이 챕터의 예제는 실패 모드를 축소해서 보여주므로, 실제 적용 시에는 더 많은 방어가 필요합니다.

5) 학습 포인트: 외우지 말고 “판단 기준”을 남겨라

핵심은 API를 외우는 것이 아니라, “언제 무엇을 선택할지” 판단 기준을 정리하는 것입니다. 이 기준이 쌓이면 새로운 라이브러리/도구가 나와도 빠르게 적응할 수 있습니다.

선택 기준(Decision Guide)

  • 기본은 가독성/명확성 우선(최적화는 측정 이후).
  • 외부 의존이 늘수록 경계/추상화테스트를 먼저 강화.
  • 복잡도가 증가하면 “규칙을 코드로”가 아니라 “구조로” 담는 방향을 고려.

흔한 오해/주의점

  • 도구/문법이 곧 실력이라는 오해가 있습니다. 실력은 문제를 단순화하고 구조화하는 능력입니다.
  • 극단적 최적화/과설계는 학습과 유지보수를 방해할 수 있습니다.

요약

  • 웹 개발는 기능이 아니라 구조/품질을 위한 기반이다.
  • 트레이드오프와 실패 모드를 먼저 생각하고, 판단 기준을 남기자.

Flask 웹 프레임워크

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
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
from flask import Flask, render_template, request, jsonify, redirect, url_for
from flask import session, flash
import os

# Flask 애플리케이션 생성
app = Flask(__name__)
app.secret_key = 'your-secret-key-here'  # 세션을 위한 비밀키

# 기본 라우트
@app.route('/')
def home():
    """홈페이지"""
    return render_template('index.html')

@app.route('/about')
def about():
    """소개 페이지"""
    return '<h1>About Page</h1><p>This is a Flask web application.</p>'

# 동적 라우트
@app.route('/user/<string:username>')
def user_profile(username):
    """사용자 프로필 페이지"""
    return f'<h1>User Profile</h1><p>Welcome, {username}!</p>'

@app.route('/post/<int:post_id>')
def show_post(post_id):
    """포스트 상세 페이지"""
    return f'<h1>Post #{post_id}</h1>'

# HTTP 메서드 처리
@app.route('/contact', methods=['GET', 'POST'])
def contact():
    """연락처 페이지"""
    if request.method == 'POST':
        name = request.form.get('name')
        email = request.form.get('email')
        message = request.form.get('message')
        
        # 여기서 이메일 발송 등 처리
        flash(f'Thank you, {name}! Your message has been sent.', 'success')
        return redirect(url_for('contact'))
    
    return render_template('contact.html')

# JSON API 엔드포인트
@app.route('/api/users')
def api_users():
    """사용자 목록 API"""
    users = [
        {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'},
        {'id': 2, 'name': 'Bob', 'email': 'bob@example.com'},
        {'id': 3, 'name': 'Charlie', 'email': 'charlie@example.com'}
    ]
    return jsonify(users)

@app.route('/api/users/<int:user_id>')
def api_user_detail(user_id):
    """사용자 상세 정보 API"""
    # 실제로는 데이터베이스에서 조회
    user = {'id': user_id, 'name': f'User {user_id}', 'email': f'user{user_id}@example.com'}
    return jsonify(user)

# 에러 핸들러
@app.errorhandler(404)
def not_found_error(error):
    return render_template('404.html'), 404

@app.errorhandler(500)
def internal_error(error):
    return render_template('500.html'), 500

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)

템플릿과 정적 파일

 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
# templates/base.html
"""
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Flask App{% endblock %}</title>
    <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
</head>
<body>
    <nav>
        <ul>
            <li><a href="{{ url_for('home') }}">Home</a></li>
            <li><a href="{{ url_for('about') }}">About</a></li>
            <li><a href="{{ url_for('contact') }}">Contact</a></li>
        </ul>
    </nav>
    
    <main>
        {% with messages = get_flashed_messages(with_categories=true) %}
            {% if messages %}
                {% for category, message in messages %}
                    <div class="alert alert-{{ category }}">{{ message }}</div>
                {% endfor %}
            {% endif %}
        {% endwith %}
        
        {% block content %}{% endblock %}
    </main>
    
    <script src="{{ url_for('static', filename='js/main.js') }}"></script>
</body>
</html>
"""

# templates/index.html
"""
{% extends "base.html" %}

{% block title %}Home - Flask App{% endblock %}

{% block content %}
<h1>Welcome to Flask App</h1>
<p>This is the home page of our Flask application.</p>

<div id="user-list">
    <h2>Users</h2>
    <div id="users"></div>
    <button onclick="loadUsers()">Load Users</button>
</div>

<script>
async function loadUsers() {
    try {
        const response = await fetch('/api/users');
        const users = await response.json();
        
        const usersDiv = document.getElementById('users');
        usersDiv.innerHTML = users.map(user => 
            `<div>
                <strong>${user.name}</strong> - ${user.email}
             </div>`
        ).join('');
    } catch (error) {
        console.error('Error loading users:', error);
    }
}
</script>
{% endblock %}
"""

# 템플릿 활용 예제
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/dashboard')
def dashboard():
    """대시보드 페이지"""
    user_data = {
        'name': 'Alice',
        'email': 'alice@example.com',
        'posts_count': 15,
        'followers': 142
    }
    
    recent_posts = [
        {'title': 'Flask 입문', 'date': '2024-01-15'},
        {'title': 'Python 웹 개발', 'date': '2024-01-10'},
        {'title': 'REST API 설계', 'date': '2024-01-05'}
    ]
    
    return render_template('dashboard.html', 
                         user=user_data, 
                         posts=recent_posts)

데이터베이스 연동 (Flask-SQLAlchemy)

  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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
import os

app = Flask(__name__)

# 데이터베이스 설정
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

# 모델 정의
class User(db.Model):
    """사용자 모델"""
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    
    # 관계 설정
    posts = db.relationship('Post', backref='author', lazy=True)
    
    def to_dict(self):
        return {
            'id': self.id,
            'username': self.username,
            'email': self.email,
            'created_at': self.created_at.isoformat(),
            'posts_count': len(self.posts)
        }

class Post(db.Model):
    """포스트 모델"""
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), nullable=False)
    content = db.Column(db.Text, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    # 외래키
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    
    def to_dict(self):
        return {
            'id': self.id,
            'title': self.title,
            'content': self.content,
            'created_at': self.created_at.isoformat(),
            'updated_at': self.updated_at.isoformat(),
            'author': self.author.username
        }

# API 엔드포인트들
@app.route('/api/users', methods=['GET', 'POST'])
def handle_users():
    """사용자 목록 조회 및 생성"""
    if request.method == 'GET':
        users = User.query.all()
        return jsonify([user.to_dict() for user in users])
    
    elif request.method == 'POST':
        data = request.get_json()
        
        # 검증
        if not data.get('username') or not data.get('email'):
            return jsonify({'error': 'Username and email are required'}), 400
        
        # 중복 체크
        if User.query.filter_by(username=data['username']).first():
            return jsonify({'error': 'Username already exists'}), 400
        
        if User.query.filter_by(email=data['email']).first():
            return jsonify({'error': 'Email already exists'}), 400
        
        # 사용자 생성
        user = User(
            username=data['username'],
            email=data['email']
        )
        
        db.session.add(user)
        db.session.commit()
        
        return jsonify(user.to_dict()), 201

@app.route('/api/users/<int:user_id>')
def get_user(user_id):
    """사용자 상세 조회"""
    user = User.query.get_or_404(user_id)
    return jsonify(user.to_dict())

@app.route('/api/posts', methods=['GET', 'POST'])
def handle_posts():
    """포스트 목록 조회 및 생성"""
    if request.method == 'GET':
        posts = Post.query.order_by(Post.created_at.desc()).all()
        return jsonify([post.to_dict() for post in posts])
    
    elif request.method == 'POST':
        data = request.get_json()
        
        # 검증
        if not data.get('title') or not data.get('content') or not data.get('user_id'):
            return jsonify({'error': 'Title, content, and user_id are required'}), 400
        
        # 사용자 존재 확인
        user = User.query.get(data['user_id'])
        if not user:
            return jsonify({'error': 'User not found'}), 404
        
        # 포스트 생성
        post = Post(
            title=data['title'],
            content=data['content'],
            user_id=data['user_id']
        )
        
        db.session.add(post)
        db.session.commit()
        
        return jsonify(post.to_dict()), 201

@app.route('/api/posts/<int:post_id>', methods=['GET', 'PUT', 'DELETE'])
def handle_post(post_id):
    """포스트 조회, 수정, 삭제"""
    post = Post.query.get_or_404(post_id)
    
    if request.method == 'GET':
        return jsonify(post.to_dict())
    
    elif request.method == 'PUT':
        data = request.get_json()
        
        if 'title' in data:
            post.title = data['title']
        if 'content' in data:
            post.content = data['content']
        
        post.updated_at = datetime.utcnow()
        db.session.commit()
        
        return jsonify(post.to_dict())
    
    elif request.method == 'DELETE':
        db.session.delete(post)
        db.session.commit()
        
        return '', 204

# 데이터베이스 초기화
@app.before_first_request
def create_tables():
    db.create_all()
    
    # 샘플 데이터 생성
    if User.query.count() == 0:
        sample_user = User(username='admin', email='admin@example.com')
        db.session.add(sample_user)
        db.session.commit()
        
        sample_post = Post(
            title='Welcome to Flask Blog',
            content='This is the first post on our Flask blog!',
            user_id=sample_user.id
        )
        db.session.add(sample_post)
        db.session.commit()

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

실습 프로젝트

프로젝트 1: 완전한 블로그 시스템

  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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
from flask import Flask, render_template, request, redirect, url_for, flash, session
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from functools import wraps
from datetime import datetime

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

# 모델들
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(200), nullable=False)
    is_admin = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    
    posts = db.relationship('Post', backref='author', lazy=True)
    comments = db.relationship('Comment', backref='author', lazy=True)
    
    def set_password(self, password):
        self.password_hash = generate_password_hash(password)
    
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

class Category(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), unique=True, nullable=False)
    description = db.Column(db.Text)
    
    posts = db.relationship('Post', backref='category', lazy=True)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), nullable=False)
    content = db.Column(db.Text, nullable=False)
    summary = db.Column(db.String(300))
    published = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
    
    comments = db.relationship('Comment', backref='post', lazy=True, cascade='all, delete-orphan')
    
    @property
    def comment_count(self):
        return len(self.comments)

class Comment(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.Text, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)

# 인증 데코레이터
def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            flash('로그인이 필요합니다.', 'warning')
            return redirect(url_for('login'))
        return f(*args, **kwargs)
    return decorated_function

def admin_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            flash('로그인이 필요합니다.', 'warning')
            return redirect(url_for('login'))
        
        user = User.query.get(session['user_id'])
        if not user or not user.is_admin:
            flash('관리자 권한이 필요합니다.', 'error')
            return redirect(url_for('home'))
        
        return f(*args, **kwargs)
    return decorated_function

# 라우트들
@app.route('/')
def home():
    """홈페이지 - 최근 포스트들"""
    page = request.args.get('page', 1, type=int)
    posts = Post.query.filter_by(published=True)\
                .order_by(Post.created_at.desc())\
                .paginate(page=page, per_page=5, error_out=False)
    
    categories = Category.query.all()
    
    return render_template('home.html', posts=posts, categories=categories)

@app.route('/post/<int:post_id>')
def post_detail(post_id):
    """포스트 상세 페이지"""
    post = Post.query.get_or_404(post_id)
    
    if not post.published and (
        'user_id' not in session or 
        session['user_id'] != post.user_id
    ):
        flash('접근할 수 없는 포스트입니다.', 'error')
        return redirect(url_for('home'))
    
    comments = Comment.query.filter_by(post_id=post_id)\
                          .order_by(Comment.created_at.desc()).all()
    
    return render_template('post_detail.html', post=post, comments=comments)

@app.route('/login', methods=['GET', 'POST'])
def login():
    """로그인"""
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        
        user = User.query.filter_by(username=username).first()
        
        if user and user.check_password(password):
            session['user_id'] = user.id
            session['username'] = user.username
            session['is_admin'] = user.is_admin
            
            flash(f'환영합니다, {user.username}님!', 'success')
            return redirect(url_for('home'))
        else:
            flash('잘못된 사용자명 또는 비밀번호입니다.', 'error')
    
    return render_template('login.html')

@app.route('/register', methods=['GET', 'POST'])
def register():
    """회원가입"""
    if request.method == 'POST':
        username = request.form['username']
        email = request.form['email']
        password = request.form['password']
        
        # 검증
        if User.query.filter_by(username=username).first():
            flash('이미 사용 중인 사용자명입니다.', 'error')
            return render_template('register.html')
        
        if User.query.filter_by(email=email).first():
            flash('이미 사용 중인 이메일입니다.', 'error')
            return render_template('register.html')
        
        # 사용자 생성
        user = User(username=username, email=email)
        user.set_password(password)
        
        db.session.add(user)
        db.session.commit()
        
        flash('회원가입이 완료되었습니다. 로그인해주세요.', 'success')
        return redirect(url_for('login'))
    
    return render_template('register.html')

@app.route('/logout')
def logout():
    """로그아웃"""
    session.clear()
    flash('로그아웃되었습니다.', 'info')
    return redirect(url_for('home'))

@app.route('/write', methods=['GET', 'POST'])
@login_required
def write_post():
    """포스트 작성"""
    if request.method == 'POST':
        title = request.form['title']
        content = request.form['content']
        summary = request.form.get('summary', '')
        category_id = request.form.get('category_id')
        published = 'published' in request.form
        
        post = Post(
            title=title,
            content=content,
            summary=summary,
            published=published,
            user_id=session['user_id'],
            category_id=category_id if category_id else None
        )
        
        db.session.add(post)
        db.session.commit()
        
        flash('포스트가 작성되었습니다.', 'success')
        return redirect(url_for('post_detail', post_id=post.id))
    
    categories = Category.query.all()
    return render_template('write_post.html', categories=categories)

@app.route('/post/<int:post_id>/comment', methods=['POST'])
@login_required
def add_comment(post_id):
    """댓글 추가"""
    post = Post.query.get_or_404(post_id)
    content = request.form['content']
    
    comment = Comment(
        content=content,
        user_id=session['user_id'],
        post_id=post_id
    )
    
    db.session.add(comment)
    db.session.commit()
    
    flash('댓글이 추가되었습니다.', 'success')
    return redirect(url_for('post_detail', post_id=post_id))

@app.route('/admin')
@admin_required
def admin_dashboard():
    """관리자 대시보드"""
    users_count = User.query.count()
    posts_count = Post.query.count()
    comments_count = Comment.query.count()
    categories_count = Category.query.count()
    
    recent_posts = Post.query.order_by(Post.created_at.desc()).limit(5).all()
    recent_comments = Comment.query.order_by(Comment.created_at.desc()).limit(5).all()
    
    stats = {
        'users': users_count,
        'posts': posts_count,
        'comments': comments_count,
        'categories': categories_count
    }
    
    return render_template('admin_dashboard.html', 
                         stats=stats, 
                         recent_posts=recent_posts,
                         recent_comments=recent_comments)

# 데이터베이스 초기화
@app.before_first_request
def create_tables():
    db.create_all()
    
    # 관리자 계정 생성
    if not User.query.filter_by(username='admin').first():
        admin = User(username='admin', email='admin@example.com', is_admin=True)
        admin.set_password('admin123')
        db.session.add(admin)
        
        # 기본 카테고리 생성
        categories = [
            Category(name='일반', description='일반적인 주제'),
            Category(name='기술', description='기술 관련 포스트'),
            Category(name='일상', description='일상 이야기')
        ]
        
        for category in categories:
            db.session.add(category)
        
        db.session.commit()

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

체크리스트

Flask 기초

  • Flask 애플리케이션 구조 이해
  • 라우팅과 뷰 함수 작성
  • 템플릿과 정적 파일 활용
  • HTTP 메서드 처리

데이터베이스 연동

  • SQLAlchemy ORM 활용
  • 모델 정의와 관계 설정
  • 마이그레이션 관리
  • 쿼리 최적화

API 개발

  • RESTful API 설계
  • JSON 응답 처리
  • 에러 처리와 상태 코드
  • API 문서화

인증과 보안

  • 사용자 인증 구현
  • 세션 관리
  • 비밀번호 해싱
  • 권한 기반 접근 제어

실무 기능

  • 페이지네이션 구현
  • 파일 업로드 처리
  • 폼 검증
  • 에러 핸들링

다음 단계

🎉 축하합니다! 웹 개발을 마스터했습니다.

Flask를 통해 웹 개발의 기초를 익혔습니다. 이제 24. 테스팅과 디버깅으로 넘어가서 코드 품질을 보장하는 방법을 학습해봅시다.


💡 웹 개발 가이드:

  • Flask는 간단한 API와 프로토타입에 적합
  • 보안을 항상 최우선으로 고려
  • 데이터베이스 설계를 신중하게 계획
  • API 문서화로 협업 효율성 향상
  • 테스트 코드 작성으로 품질 보장