콘텐츠로 이동

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
(알고리즘)  (데이터)   (무결성 검증)

절대 하면 안 되는 것

  1. 비밀번호를 평문으로 저장
  2. SQL을 문자열 포매팅으로 조합
  3. 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);

fetchcatch는 네트워크 오류만 잡습니다. 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 → 설계 → 구현. 코딩 전 무엇을 만들지 명확히
측정 먼저 성능 최적화는 추측 말고 측정 후 진행
테스트 정상 동작뿐 아니라 에러 케이스도 검증