앞서 FastAPI 개발하기 2편에서 CORS 개념을 정리하였다.
CORS를 통해 브라우저 보안 모델을 이해했다면, 이제는 서버 내부 구조를 이해할 차례이다.
2026.02.06 - [Development environment] - [FastAPI] FastAPI로 백엔드 개발하기 2 - CORS 개념 및 설정
[FastAPI] FastAPI로 백엔드 개발하기 2 - CORS 개념 및 설정
― CORS 개념부터 실제 설정 의미까지 정리 FastAPI로 API 서버를 만들때 반드시 마주치는 설정이 CORS이다.특히 프론트엔드(React, Vue 등)와 API 서버를 분리해서 개발할 때 CORS 설정이 없으면 브라우저
hjjummy.tistory.com
CORS 다음으로 반드시 마주치는 개념이 엔드포인트(endpoint) 와 라우터(router) 이다.

처음에는 다음과 같은 코드 한 줄로 API가 완성된 것처럼 보인다.
@app.get("/health")
async def health():
return {"status": "ok"}
하지만 프로젝트가 조금만 커져서 엔드포인트가 늘어나기 시작하면 다음과 같은 문제가 발생한다.
- main.py에 라우팅 코드가 계속 늘어나며 파일이 비대해짐
- 비즈니스 로직과 HTTP 핸들링 코드가 섞여서 읽기 어려워짐
- 버전(v1/v2), 도메인(users/search/admin) 단위로 분리하기 어려움
- 팀 작업에서 API 위치를 찾는 비용 증가
이번 글에서는
1. 엔드포인트의 구성 요소
2. URL · 메서드 · 함수의 관계
3. 라우터의 역할
4. 라우터가 필수인지 여부
5. 라우터를 사용하는 경우와 그렇지 않은 경우의 차이
를 중심으로 FastAPI의 구조를 정리하려고 한
1. 엔드포인트(endpoint)란 ?
엔드포인트는 클라이언트가 호출하는 API의 ‘진입 지점’
좀 더 정확히 말하면, 다음 3가지 조합으로 하나의 엔드포인트가 결정된다.
- HTTP 메서드 (GET/POST/PUT/DELETE …)
- 경로(Path) (/search, /users/{id} …)
- 핸들러 함수(처리 함수) (요청을 받아서 응답을 반환하는 함수)
예를 들어 아래는 하나의 엔드포인트이다.
@app.get("/health")
async def health():
return {"status": "ok"}
의미는 다음과 같다.
- GET /health 요청이 오면
- health() 함수를 실행하고
- 결과를 JSON 응답으로 반환한다.
즉 엔드포인트 = URL + HTTP 메서드 + 함수
- URL = /health
- 메서드 = GET
- 함수 = health()
1.1 URL이란?
URL은 클라이언트가 요청을 보내는 주소이다.
http://localhost:8003/api/v1/search
| 구성 요소 | 의미 |
| http | 프로토콜 |
| localhost | 서버 주소 |
| 8003 | 포트 번호 |
| /api/v1/search | 경로(Path) |
FastAPI 코드에서 우리가 직접 정의하는 부분은 경로(Path) 이다.
여기서 "/search"는 전체 URL이 아니라 경로이다.
👉 URL은 “어디에 접근하는가”를 구분하는 역할을 한다.
- /users → 사용자 목록
- /users/1 → 특정 사용자
- /search → 검색 API
- /health → 서버 상태 확인
위 처럼 구분이 되도록 설정하는것이 좋다.
1.2 HTTP 메서드란?
HTTP 메서드는 “무엇을 하려는가”를 나타낸다.
- GET → 데이터 조회
- POST → 데이터 생성
- PUT → 전체 수정
- DELETE → 삭제
같은 URL이라도 메서드가 다르면 완전히 다른 엔드포인트가 된다.
@app.get("/users")
async def get_users():
...
@app.post("/users")
async def create_user():
...
- GET /users → 조회
- POST /users → 생성
1.3 함수 (Function)란?
함수는 실제로 동작하는 프로그램 코드 이다.
@app.get("/users")
async def get_users():
return {"data": []}
여기서 get_users()가 함수이다.
2. 라우터(router)란 ?
라우터는 엔드포인트들을 묶어서 관리하는 그룹 단위
엔드포인트가 몇 개 안 될 때는 굳이 필요하지 않다.
그러나 서비스가 커질수록 엔드포인트는 수십~수백 개로 늘어난다.
이를 main.py에 모두 작성하면 유지보수가 어려워진다.
그래서 FastAPI는 APIRouter라는 기능을 제공한다.
라우터는 다음을 담당한다.
- 관련 엔드포인트를 파일 단위로 묶음
- 공통 prefix 설정 (/api/v1/search 등)
- 공통 tags 설정 → Swagger 문서 그룹화
- 공통 dependency(인증 등) 적용
정리하면 다음과 같다.
엔드포인트 = API 1개
라우터 = 엔드포인트 묶음(그룹)
3. FastAPI에서 app과 router의 관계
FastAPI의 app은 서버 전체의 중심 객체이다.
라우터는 그 app에 등록되어 동작한다.
.
구조는 다음과 같다.
- APIRouter()로 라우터 생성
- 라우터에 엔드포인트 정의
- app.include_router(router)로 앱에 등록
from fastapi import FastAPI
from fastapi.routing import APIRouter
app = FastAPI()
router = APIRouter()
@router.get("/health")
async def health():
return {"status": "ok"}
app.include_router(router)
이 라우터를 사용한 구조의 장점은 다음과 같다.
- 라우터 단위로 파일 분리가 가능해짐
- 도메인 단위로 API를 관리 가능
- 프로젝트가 커져도 main.py는 얇게 유지 가능해짐
4. 라우터는 필수인가?
결론부터 말하면 필수가 아니다.
FastAPI는 다음 코드만으로도 완벽히 동작한다.
app = FastAPI()
@app.get("/health")
async def health():
...
라우터는 구조를 정리하기 위한 도구이지, 반드시 사용해야 하는 문법은 아니다.
4.1. 라우터 없이 사용하는 경우 vs 사용하는 경우 비교
- 1. 라우터 없이 작성 경우 -> main 파일에 작성
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/users")
async def get_users():
return {"data": []}
@app.post("/users")
async def create_user():
return {"status": "created"}
장점: 구조가 단순함, 작은 프로젝트에 적합, 빠르게 개발 가능
단점: 파일이 빠르게 비대해짐, 기능 단위 분리가 어려움, 협업 시 관리 어려움
- 2. 라우터를 사용하는 경우- > router.py와 main 파일 분리
users_router.py
from fastapi import APIRouter
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/")
async def get_users():
return {"data": []}
@router.post("/")
async def create_user():
return {"status": "created"}
main.py
from fastapi import FastAPI
from routers.users_router import router as users_router
app = FastAPI()
app.include_router(users_router)
5. 언제 라우터를 쓰는 것이 좋은가? 라우터 설계 방식은?
개발자 스타일마다 다르지만, 다음 중 하나라도 해당하면 사용하는 것이 좋다.
- 엔드포인트가 5~10개 이상
- 기능이 명확히 분리됨 (/search, /users, /admin 등)
- API 버전 관리 필요
- 인증/권한같은 공통 로직을 도메인 단위로 적용하고 싶음
- 팀 단위 협업
작은 토이 프로젝트라면 굳이 필요하지 않다.
그러나 서비스로 운영할 계획이라면 초기에 구조를 잡는 것이 유리하다.
5-1. 도메인 기준 분리
- search 관련 API → search_router.py
- auth 관련 API → auth_router.py
- users 관련 API → users_router.py
이 방식이 가장 일반적이다.
5-2. 버전(prefix) 관리
서비스가 운영되면 API 변경이 필연적으로 생긴다.
이때 v1/v2를 분리하지 않으면 FE/외부 연동이 한 번에 깨질 수 있다.
그래서 보통 아래처럼 prefix를 둔다.
- /api/v1/...
- /api/v2/...
라우터에 prefix를 붙이면 구조가 깔끔해진다.
5-3. 라우터 있는 경우 구조
search_app/
├── main.py
├── schemas.py
├── routers/
│ └── search_router.py
├── services/
│ └── search.py
├── requirements.txt
└── search_api.log
역할 분리
- main.py → 앱 생성 + 라우터 등록
- routers → HTTP 엔드포인트 정의
- services → 비즈니스 로직
- schemas → Request/Response 계약
이 구조는 코드가 짧은 프로젝트에도 부담이 적고, 동시에 확장 가능하다.
마무리
CORS가 브라우저와 서버 사이의 보안 규칙이라면,
엔드포인트와 라우터는 서버 내부 설계의 기본 구조이다.
핵심 정리
- 엔드포인트 = URL + 메서드 + 함수
- 라우터 = 엔드포인트 묶음
- 라우터는 필수가 아니라 구조 관리 도구
- 작은 프로젝트는 없어도 가능
- 서비스 규모라면 사용하는 것이 권장됨
FastAPI를 잘 사용한다는 것은 문법을 많이 아는 것이 아니라, 상황에 맞게 적절한 구조를 설계할 수 있는 것이다!
'Development environment' 카테고리의 다른 글
| [FastAPI] FastAPI 개발하기4 - Request / Response 모델(Pydantic) (1) | 2026.02.13 |
|---|---|
| [FastAPI] FastAPI 개발하기2 - CORS 개념 및 설정 (0) | 2026.02.06 |
| [FastAPI] FastAPI 개발하기 1- 환경 셋팅 및 실행명령어 (0) | 2026.02.06 |
| [DBeaver] DBeaver 설치 및 연동하기 (0) | 2026.02.03 |
| [Git] Git 커밋 메세지 규칙 + 커밋 수정하기 (0) | 2025.12.17 |