CS 강의 핵심 요약
01~20장 전체 내용을 주제별로 압축한 참고 자료입니다.
1. 웹 서비스 구조
클라이언트-서버
[클라이언트 — 브라우저/앱]
│ 요청(Request)
▼
[백엔드 서버 — API 처리]
│ 쿼리
▼
[데이터베이스 — 데이터 영구 저장]
| 계층 |
역할 |
예시 |
| 프론트엔드 |
사용자에게 보이는 화면 |
HTML/CSS/JS |
| 백엔드 |
비즈니스 로직, API |
Python/FastAPI |
| 데이터베이스 |
데이터 영구 저장 |
SQLite/PostgreSQL |
관심사 분리 — 각 계층은 자신의 역할만 담당합니다. 백엔드는 화면을 모르고, 프론트엔드는 DB를 모릅니다.
2. API와 HTTP
HTTP 메서드
| 메서드 |
의미 |
예시 |
| GET |
조회 |
GET /todos |
| POST |
생성 |
POST /todos |
| PATCH |
일부 수정 |
PATCH /todos/1 |
| DELETE |
삭제 |
DELETE /todos/1 |
주요 상태 코드
| 코드 |
의미 |
| 200 |
성공 |
| 201 |
생성 성공 |
| 400 |
잘못된 요청 (클라이언트 오류) |
| 401 |
인증 필요 |
| 403 |
권한 없음 |
| 404 |
리소스 없음 |
| 500 |
서버 내부 오류 |
REST 원칙
- URL = 자원 →
/todos, /users/5
- HTTP 메서드 = 행동 → GET(조회), POST(생성)…
- JSON = 데이터 형식
- 무상태(Stateless) — 각 요청은 독립적, 서버가 상태를 기억하지 않음
JSON
{
"id": 1,
"title": "공부하기",
"is_done": false
}
Python에서 API 호출
import requests
res = requests.get("https://api.example.com/todos")
data = res.json()
print(res.status_code) # 200
3. 설계 방법론 (SRS / SDD)
개발 순서
SRS (무엇을) → SDD 스펙 (어떻게) → 구현 → 인수 조건으로 검증
SRS — 요구사항 명세서
| 항목 |
설명 |
| 기능적 요구사항 |
시스템이 해야 하는 일 |
| 비기능적 요구사항 |
성능, 보안, 제약 등 품질 조건 |
| 유저 스토리 |
나는 ~로서 ~하고 싶다 형식 |
| 인수 조건 |
기능 완성 기준 (Given / When / Then) |
| MoSCoW |
Must / Should / Could / Won't 우선순위 |
| 범위 밖 |
만들지 않을 것을 명시 |
SDD — 스펙 주도 개발
| 수준 |
설명 |
| Spec-First |
스펙이 초기 안내, 이후 별도 관리 |
| Spec-Anchored |
스펙과 코드를 함께 유지 (실서비스 권장) |
| Spec-as-Source |
코드를 스펙에서 자동 생성 |
Vibe Coding (모호한 지시로 AI에 맡기기) → 품질 저하 유발. 스펙을 먼저 작성하세요.
4. 데이터베이스 설계
ER 모델
| 개념 |
설명 |
| 엔터티 |
독립적으로 존재하는 사물/개념 (테이블이 됨) |
| 속성 |
엔터티의 특성 (컬럼이 됨) |
| 관계 |
엔터티 사이의 연결 |
| 카디널리티 |
1:1, 1:N, N:M |
카디널리티
| 관계 |
예시 |
구현 방법 |
| 1:1 |
사용자 ↔ 프로필 |
한쪽에 FK |
| 1:N |
사용자 → 게시글 |
N쪽에 FK |
| N:M |
학생 ↔ 수업 |
중간 테이블 |
키
| 종류 |
설명 |
| 기본 키(PK) |
튜플을 유일하게 식별, NULL 불가 |
| 외래 키(FK) |
다른 테이블의 PK 참조 |
| 후보 키 |
PK가 될 수 있는 최소 속성 집합 |
정규화
| 단계 |
제거 대상 |
| 1NF |
반복 그룹, 비원자 속성 |
| 2NF |
부분 함수 종속 (복합 PK 일부에만 종속) |
| 3NF |
이행 함수 종속 (PK → A → B 형태) |
핵심 원칙: 같은 사실은 한 곳에만 저장한다.
5. SQL
기본 구조
SELECT 컬럼
FROM 테이블
WHERE 조건
ORDER BY 컬럼 DESC
LIMIT 개수;
주요 명령
-- 조회
SELECT title, is_done FROM todos WHERE user_id = 1;
-- 집계
SELECT user_id, COUNT(*) FROM todos GROUP BY user_id HAVING COUNT(*) > 3;
-- JOIN
SELECT u.name, t.title
FROM users u
INNER JOIN todos t ON t.user_id = u.id;
-- 삽입
INSERT INTO todos (title, user_id) VALUES ('공부', 1);
-- 수정 ← WHERE 필수!
UPDATE todos SET is_done = 1 WHERE id = 5;
-- 삭제 ← WHERE 필수!
DELETE FROM todos WHERE id = 5;
SQL 실행 순서
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
UPDATE / DELETE에는 항상 WHERE를 붙이세요. WHERE 없이 실행하면 전체 데이터가 변경됩니다.
6. 백엔드 구현 (FastAPI)
기본 서버
from fastapi import FastAPI
app = FastAPI()
@app.get("/todos")
def get_todos():
return [{"id": 1, "title": "공부"}]
CRUD 패턴
| 기능 |
메서드 |
URL |
상태 코드 |
| 목록 조회 |
GET |
/todos |
200 |
| 단건 조회 |
GET |
/todos/{id} |
200 / 404 |
| 생성 |
POST |
/todos |
201 |
| 수정 |
PATCH |
/todos/{id} |
200 / 404 |
| 삭제 |
DELETE |
/todos/{id} |
204 / 404 |
파라미터 바인딩 (SQL 인젝션 방지)
# 위험 — 절대 하지 마세요
cursor.execute(f"SELECT * FROM todos WHERE title = '{title}'")
# 안전
cursor.execute("SELECT * FROM todos WHERE title = ?", (title,))
입력 검증 (Pydantic)
from pydantic import BaseModel
class TodoCreate(BaseModel):
title: str # 자동으로 타입 검증, 없으면 400 반환
7. 인증과 보안
인증 vs 인가
| 구분 |
질문 |
예시 |
| 인증 (Authentication) |
당신은 누구인가? |
로그인 |
| 인가 (Authorization) |
무엇을 할 수 있는가? |
권한 검사 |
비밀번호 해싱
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"])
# 저장 시
hashed = pwd_context.hash("my_password")
# 검증 시
pwd_context.verify("my_password", hashed) # True/False
비밀번호는 절대 평문으로 저장하지 않습니다.
JWT 흐름
로그인 → 서버가 JWT 발급 → 클라이언트 저장
→ 이후 요청 시 Authorization: Bearer <token> 헤더에 포함
→ 서버가 토큰 검증 (DB 조회 불필요)
JWT 구조
헤더.페이로드.서명
eyJ... . eyJ... . sig
(알고리즘) (데이터) (무결성 검증)
절대 하면 안 되는 것
- 비밀번호를 평문으로 저장
- SQL을 문자열 포매팅으로 조합
- SECRET_KEY를 코드에 하드코딩
환경 변수
import os
SECRET_KEY = os.getenv("SECRET_KEY") # 코드 밖에서 관리
# .env 파일 (반드시 .gitignore에 추가)
SECRET_KEY=super-secret-key
DATABASE_URL=postgresql://...
8. 프론트엔드
HTML 구조
<!DOCTYPE html>
<html>
<head><title>제목</title></head>
<body>
<h1>큰 제목</h1>
<p>문단</p>
<button id="btn">클릭</button>
<input type="text" placeholder="입력">
<ul><li>항목</li></ul>
</body>
</html>
CSS 핵심
/* 선택자 { 속성: 값; } */
.card { background: white; padding: 16px; border-radius: 8px; }
/* Flexbox 정렬 */
.container {
display: flex;
justify-content: center; /* 가로 */
align-items: center; /* 세로 */
gap: 8px;
}
JavaScript 핵심
// 변수
const name = "지민"; // 재할당 불가
let count = 0; // 재할당 가능
// 함수
const double = (x) => x * 2;
// DOM 조작
const btn = document.querySelector("#btn");
btn.addEventListener("click", () => {
btn.textContent = "클릭됨";
});
// 요소 추가
const li = document.createElement("li");
li.textContent = "새 항목";
document.querySelector("ul").appendChild(li);
Python vs JavaScript 비교
| 개념 |
Python |
JavaScript |
| 조건 |
if x > 0: |
if (x > 0) { |
| 반복 |
for i in range(5): |
for (let i=0; i<5; i++) { |
| 함수 |
def add(a, b): |
const add = (a, b) => a + b; |
| 리스트/배열 |
[1, 2, 3] |
[1, 2, 3] |
| 딕셔너리/객체 |
{"key": "val"} |
{key: "val"} |
9. fetch API — 프론트-백엔드 연결
// GET
const res = await fetch("/api/todos");
const todos = await res.json();
// POST
const res = await fetch("/api/todos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: "공부" })
});
// 인증 헤더 포함
const res = await fetch("/api/todos", {
headers: { "Authorization": `Bearer ${token}` }
});
// 에러 처리
if (!res.ok) throw new Error("요청 실패: " + res.status);
fetch의 catch는 네트워크 오류만 잡습니다. 404, 500은 res.ok로 별도 확인하세요.
10. 배포
개발 vs 운영 환경
| 항목 |
개발 |
운영 |
| DB |
SQLite |
PostgreSQL |
| URL |
localhost:8000 |
https://example.com |
| 환경변수 |
.env 파일 |
서비스 환경변수 설정 |
| HTTPS |
없음 |
필수 |
배포 옵션
| 서비스 |
적합한 용도 |
| Vercel |
프론트엔드, 서버리스 API |
| Railway / Render |
백엔드 서버 (FastAPI 등) |
| AWS EC2 |
전통적 백엔드 서버 |
| AWS Lambda |
서버리스 백엔드 |
배포 체크리스트
- [ ] 비밀 값을 환경 변수로 분리
- [ ]
requirements.txt 최신화
- [ ]
.env가 .gitignore에 포함
- [ ] DB를 PostgreSQL로 교체
- [ ] HTTPS 설정 확인
11. Docker
핵심 개념
| 개념 |
설명 |
| 이미지 |
실행 환경을 담은 읽기 전용 설계도 |
| 컨테이너 |
이미지를 실행한 인스턴스 |
| Dockerfile |
이미지를 만드는 명령 목록 |
Dockerfile 기본
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
주요 명령
docker build -t myapp . # 이미지 빌드
docker run -p 8000:8000 myapp # 컨테이너 실행
docker ps # 실행 중인 컨테이너 목록
docker stop <id> # 컨테이너 중지
Docker Compose
# docker-compose.yml
services:
app:
build: .
ports: ["8000:8000"]
environment:
- DATABASE_URL=postgresql://...
db:
image: postgres:15
volumes: ["pgdata:/var/lib/postgresql/data"]
docker compose up -d # 시작
docker compose down # 중지
12. Git 협업
기본 흐름
git init # 저장소 초기화
git add 파일명 # 스테이징
git commit -m "메시지" # 커밋
git push origin main # 원격에 올리기
git pull # 원격에서 받기
브랜치 워크플로우 (GitHub Flow)
git switch -c feature/login # 브랜치 생성
# ... 코드 작성 ...
git add . && git commit -m "..."
git push origin feature/login
# GitHub에서 Pull Request → 코드 리뷰 → main에 merge
자주 쓰는 명령
git status # 현재 상태
git log --oneline # 커밋 이력
git diff # 변경사항 확인
git restore 파일명 # 변경사항 되돌리기
git revert <커밋해시> # 커밋을 되돌리는 새 커밋 (안전)
.gitignore 필수 항목
.env
__pycache__/
*.pyc
node_modules/
.DS_Store
좋은 커밋 메시지
feat: 로그인 API 추가
fix: 비밀번호 검증 오류 수정
docs: README 배포 방법 추가
refactor: DB 연결 함수 분리
13. 테스트
테스트 종류
| 종류 |
범위 |
예시 |
| 단위 테스트 |
함수 하나 |
해싱 함수 검증 |
| 통합 테스트 |
여러 컴포넌트 |
API + DB |
| E2E 테스트 |
전체 흐름 |
로그인 → CRUD |
pytest 기본
# AAA 패턴
def test_add():
# Arrange (준비)
a, b = 3, 4
# Act (실행)
result = add(a, b)
# Assert (검증)
assert result == 7
FastAPI TestClient
from fastapi.testclient import TestClient
client = TestClient(app)
def test_get_todos():
res = client.get("/todos")
assert res.status_code == 200
assert isinstance(res.json(), list)
정상 동작뿐 아니라 잘못된 입력을 올바르게 거부하는지도 테스트하세요.
14. 에러 처리와 로깅
FastAPI 에러 처리
from fastapi import HTTPException
@app.get("/todos/{id}")
def get_todo(id: int):
todo = db.get(id)
if not todo:
raise HTTPException(status_code=404, detail="없는 할 일입니다")
return todo
로그 레벨
DEBUG < INFO < WARNING < ERROR < CRITICAL
import logging
logger = logging.getLogger(__name__)
logger.info("사용자 로그인: user_id=%s", user_id)
logger.error("DB 오류: %s", str(e))
사용자에게는 친절한 메시지, 로그에는 디버깅에 필요한 모든 정보를 남기세요.
15. 성능 최적화
인덱스
-- WHERE, JOIN, ORDER BY에 자주 쓰이는 컬럼에 추가
CREATE INDEX idx_todos_user_id ON todos(user_id);
페이지네이션
# OFFSET 방식
SELECT * FROM todos LIMIT 20 OFFSET 40; -- 3페이지
# 커서 방식 (대용량에 적합)
SELECT * FROM todos WHERE id > 200 ORDER BY id LIMIT 20;
N+1 쿼리 방지
# 나쁜 예 — 사용자 100명이면 101번 쿼리
for user in users:
todos = db.get_todos(user.id) # 매번 쿼리
# 좋은 예 — JOIN으로 한 번에
SELECT u.name, t.title FROM users u JOIN todos t ON t.user_id = u.id;
"추측하지 말고 측정하세요." 성능 문제는 예상치 못한 곳에 있습니다.
16. 실전 프로젝트 — 개발 순서
1. 서비스 정의 — 누가, 무엇을, 왜 쓰는가
2. SRS 작성 — Must/Should 기능 목록
3. API 설계 — 엔드포인트·요청·응답 형식
4. DB 설계 — ERD → 테이블·관계·인덱스
5. 파일 구조 — database.py / auth.py / main.py
6. 개발 순서 — DB → 인증 → CRUD → 프론트엔드
7. 테스트 — 핵심 API 단위·통합 테스트
8. 배포 — 환경변수 분리 → Docker → 클라우드
프로젝트 파일 구조
project/
├── main.py # FastAPI 라우터
├── database.py # DB 연결·초기화
├── auth.py # 인증·JWT
├── requirements.txt # 패키지 목록
├── Dockerfile
├── .env # 비밀 값 (gitignore)
└── tests/
└── test_api.py
핵심 원칙 모음
| 원칙 |
내용 |
| 관심사 분리 |
각 계층은 자신의 역할만 담당 |
| 파라미터 바인딩 |
SQL에 값을 넣을 때 ? 사용, 문자열 조합 금지 |
| 비밀값 분리 |
SECRET_KEY, 비밀번호 DB URL은 환경 변수로 |
| WHERE 필수 |
UPDATE, DELETE에는 항상 WHERE 조건 |
| 설계 먼저 |
SRS → 설계 → 구현. 코딩 전 무엇을 만들지 명확히 |
| 측정 먼저 |
성능 최적화는 추측 말고 측정 후 진행 |
| 테스트 |
정상 동작뿐 아니라 에러 케이스도 검증 |