FastAPI 서버개발, FastAPI 애플리케이션 구조화, 이벤트 플래너 애플리케이션 개발

FastAPI는 Python으로 작성된 현대적인 웹 프레임워크로, 빠르고 효율적인 API를 구축하는 데 최적화되어 있습니다. 이 글에서는 FastAPI를 사용해 간단한 이벤트 플래너 애플리케이션을 개발하는 과정을 상세히 설명하겠습니다. 이 과정은 FastAPI 서버의 구조화, RESTful API 설계, 데이터베이스 연동, 인증 및 권한 부여 등을 포함합니다.

FastAPI 소개

FastAPI는 다음과 같은 특징을 가지고 있습니다:

  • ⚡ 신속한 성능: Starlette로 구축된 비동기 기반이므로 매우 빠릅니다.
  • 🔄 자동 문서화: Swagger UI와 ReDoc을 사용하여 API 문서를 자동 생성합니다.
  • 📦 타입 힌팅: Python 타입 힌트를 사용하여 코드 품질과 가독성을 높입니다.
  • 🔒 보안 & 인증: OAuth2와 JWT를 통해 강력한 인증과 인가 기능을 제공합니다.

환경 설정

FastAPI 애플리케이션을 개발하기 위해 필요한 기본 환경을 설정해보겠습니다. 먼저, FastAPI와 Uvicorn을 설치해야 합니다.

pip install fastapi uvicorn

프로젝트 구조화

이벤트 플래너 애플리케이션의 코드를 구조화하는 방법은 다음과 같습니다:


event_planner/
├── app/
│   ├── main.py
│   ├── models.py
│   ├── schemas.py
│   ├── database.py
│   └── routes/
│       └── events.py
└── requirements.txt

1. main.py

FastAPI 애플리케이션의 엔트리 포인트인 main.py 파일에서는 FastAPI 인스턴스를 생성하고, 중간웨어 및 라우터를 설정합니다.

from fastapi import FastAPI
from .routes import events

app = FastAPI()

@app.get("/")
async def read_root():
    return {"message": "이벤트 플래너 API에 오신 것을 환영합니다!"}

app.include_router(events.router)

2. models.py

models.py 파일은 SQLAlchemy 데이터 모델을 정의합니다. 이벤트 플래너 애플리케이션에서는 이벤트에 대한 기본적인 모델을 설정합니다.

from sqlalchemy import Column, Integer, String
from .database import Base

class Event(Base):
    __tablename__ = "events"
    
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String, index=True)
    description = Column(String)
    location = Column(String)
    time = Column(String)

3. schemas.py

schemas.py에서는 Pydantic을 사용하여 데이터 검증 및 직렬화를 합니다. 이 파일은 API의 요청 바디와 응답 모델을 정의합니다.

from pydantic import BaseModel

class EventBase(BaseModel):
    title: str
    description: str
    location: str
    time: str

class EventCreate(EventBase):
    pass

class Event(EventBase):
    id: int

    class Config:
        orm_mode = True

4. database.py

database.py에서는 데이터베이스 연결을 설정하고 세션을 관리합니다. SQLAlchemy를 통한 데이터베이스 설정을 진행합니다.

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = "sqlite:///./events.db"

engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()

5. routes/events.py

이벤트 관련 API 엔드포인트를 담당하는 events.py 파일에서는 CRUD 기능을 작성합니다.

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from .. import models, schemas
from ..database import SessionLocal

router = APIRouter()

# 의존성 주입을 위한 함수
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@router.post("/events/", response_model=schemas.Event)
async def create_event(event: schemas.EventCreate, db: Session = Depends(get_db)):
    db_event = models.Event(**event.dict())
    db.add(db_event)
    db.commit()
    db.refresh(db_event)
    return db_event

@router.get("/events/{event_id}", response_model=schemas.Event)
async def read_event(event_id: int, db: Session = Depends(get_db)):
    db_event = db.query(models.Event).filter(models.Event.id == event_id).first()
    if db_event is None:
        raise HTTPException(status_code=404, detail="이벤트를 찾을 수 없습니다.")
    return db_event

@router.get("/events/", response_model=list[schemas.Event])
async def read_events(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
    events = db.query(models.Event).offset(skip).limit(limit).all()
    return events

FastAPI 애플리케이션 실행

모든 설정이 완료되면 Uvicorn을 통해 애플리케이션을 실행할 수 있습니다.

uvicorn app.main:app --reload

브라우저에서 http://127.0.0.1:8000/docs를 열어 자동 생성된 API 문서와 Swagger UI를 확인하며 API를 테스트할 수 있습니다.

데이터베이스 설정

SQLite 데이터베이스를 사용할 경우, 데이터베이스를 초기화하고 테이블을 생성해야 합니다. 다음 코드를 main.py에 추가하여 처음 실행할 때 테이블을 생성합니다.

from fastapi import FastAPI
from .database import engine, Base

# DB 초기화
Base.metadata.create_all(bind=engine)

인증 및 권한 부여

이번 애플리케이션에서는 간단한 사용자 인증을 추가할 수 있습니다. FastAPI는 OAuth2와 JWT를 지원하므로, 이를 이용해 보호된 엔드포인트를 만들 수 있습니다. 예를 들어, 이벤트 삭제 엔드포인트에 인증을 추가하는 방식으로 접근할 수 있습니다.

from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi import Depends
from .. import models, schemas
import bcrypt

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def fake_hash_password(password: str):
    return "fakehashed" + password

@router.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = db.query(models.User).filter(models.User.username == form_data.username).first()
    if not user or not bcrypt.checkpw(form_data.password.encode('utf-8'), user.hashed_password.encode('utf-8')):
        raise HTTPException(status_code=400, detail="잘못된 사용자명 또는 비밀번호")
    return {"access_token": user.username, "token_type": "bearer"}

결론

이러한 단계를 통해 FastAPI와 SQLAlchemy를 활용하여 이벤트 플래너 애플리케이션의 기본 구조를 설정했습니다. 이 애플리케이션에서 더 발전된 기능을 추가해보며 FastAPI의 다양한 특징을 경험할 수 있습니다. FastAPI의 문서화와 비동기 처리가 효율적이기 때문에, 복잡한 웹 애플리케이션 개발의 도전 과제를 쉽게 극복할 수 있습니다. 이 프로젝트를 통해 FastAPI의 실제 사용 사례를 접해보길 바랍니다!

FastAPI 서버개발, 액세스 토큰 생성과 검증

작성자: 조광형

날짜: 2024년 11월 26일

1. 서론

FastAPI는 Python을 기반으로 하는 현대적인 웹 프레임워크로, 비동기 프로그래밍을 지원하며 최적화된 성능을 자랑합니다. RESTful API를 쉽게 구축할 수 있도록 돕는 FastAPI의 특징 중 하나는 데이터 검증과 자동 문서 생성 기능입니다. 이 강좌에서는 FastAPI를 사용하여 액세스 토큰을 생성하고 검증하는 방법에 대해 다룹니다. 액세스 토큰은 인증 및 권한 부여에 필수적이며, 깔끔하고 안전한 API를 만드는 데 중요한 요소입니다.

2. FastAPI 기본 설정

FastAPI를 사용하기 위해 먼저 필요한 라이브러리를 설치해야 합니다. 아래 명령어를 통해 FastAPI와 필요한 종속성을 설치합니다.

pip install fastapi[all] python-jose

다음으로 FastAPI 애플리케이션을 생성합니다. 아래의 코드는 가장 기본적인 FastAPI 서버를 설정하는 방법을 보여줍니다.


from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello, FastAPI!"}
            

위 코드를 실행한 후 웹 브라우저에서 http://localhost:8000에 접속하면 “Hello, FastAPI!”라는 메시지를 확인할 수 있습니다.

3. JWT(Json Web Token) 이해하기

액세스 토큰을 구현하기 위해서는 JWT를 사용합니다. JWT는 클라이언트와 서버 간의 안전한 정보를 전달하기 위한 표준으로, 세 부분으로 구성됩니다: 헤더, 페이로드, 서명. 이를 통해 데이터를 안전하게 전송할 수 있습니다.

  • 헤더: 토큰의 타입과 사용되는 알고리즘을 정의합니다.
  • 페이로드: 전달할 정보를 포함합니다. 예를 들어, 사용자 ID, 만료 시간 등의 정보를 담을 수 있습니다.
  • 서명: 헤더와 페이로드를 비밀키로 해시하여 생성된 값으로, 데이터의 무결성을 확인하는 데 사용됩니다.

4. 액세스 토큰 생성 및 검증 구현하기

이제 액세스 토큰을 생성하고 검증하는 작업을 진행해 보겠습니다. 이를 위해 Python의 python-jose 라이브러리를 사용할 것입니다. 먼저, 토큰을 생성하는 함수를 만들어 보겠습니다.


from datetime import datetime, timedelta
from jose import JWTError, jwt
from fastapi import Depends, HTTPException, status, Security
from fastapi.security import OAuth2PasswordBearer
from typing import Optional

SECRET_KEY = "mysecretkey"  # 비밀키
ALGORITHM = "HS256"  # 알고리즘
ACCESS_TOKEN_EXPIRE_MINUTES = 30  # 만료 시간 설정

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt
            

위 코드는 액세스 토큰을 생성하는 함수입니다. 사용자가 로그인 할 때 이 함수를 호출하여 액세스 토큰을 생성합니다.

5. 액세스 토큰을 사용하는 사용자 인증

다음으로 사용자 인증을 위한 엔드포인트를 만들어 보겠습니다. 사용자 로그인을 처리하고, 액세스 토큰을 반환하는 API 를 작성합니다.


from pydantic import BaseModel

class User(BaseModel):
    username: str
    password: str

fake_users_db = {
    "testuser": {
        "username": "testuser",
        "full_name": "Test User",
        "email": "testuser@example.com",
        "hashed_password": "fakehashedpassword",
        "disabled": False,
    }
}

def fake_hash_password(password: str):
    return "fakehashed" + password

@app.post("/token")
async def login(user: User):
    user_dict = fake_users_db.get(user.username)
    if not user_dict or user_dict["hashed_password"] != fake_hash_password(user.password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(data={"sub": user.username}, expires_delta=access_token_expires)
    return {"access_token": access_token, "token_type": "bearer"}
            

위 코드는 로그인을 처리하고 액세스 토큰을 반환하는 /token 엔드포인트입니다. 사용자가 올바른 사용자 이름과 비밀번호를 제공하면, 액세스 토큰이 생성되어 반환됩니다.

6. 토큰 검증

액세스 토큰이 생성되면, 이를 검증하는 것이 중요합니다. 특정 엔드포인트에 액세스하기 위해서는 유효한 토큰이 필요합니다. 아래 코드를 통해 이를 구현할 수 있습니다.


async def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    user = fake_users_db.get(username)
    if user is None:
        raise credentials_exception
    return user

@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_user)):
    return current_user
            

위의 코드는 액세스 토큰을 검증하고 해당 사용자 정보를 반환하는 /users/me 엔드포인트입니다. 요청에서 토큰을 추출하여 사용자 정보를 확인합니다.

7. 테스트

API를 테스트하기 위해, curl이나 Postman과 같은 도구를 사용할 수 있습니다. 아래는 Postman에서의 테스트 방법입니다:

  1. POST 요청을 통해 사용자 로그인:

    POST http://localhost:8000/token

    Body에 JSON 형태로 사용자 이름과 비밀번호를 입력합니다:

    {"username": "testuser", "password": "testpassword"}
  2. 응답으로 받은 액세스 토큰을 이용하여 사용자 정보 요청:

    GET http://localhost:8000/users/me

    Authorization 헤더에 Bearer {access_token}을 추가합니다.

8. 결론

이번 강좌에서는 FastAPI를 사용하여 액세스 토큰을 생성하고 검증하는 방법에 대해 학습했습니다. FastAPI의 강력한 기능은 다양한 인증 방법을 구현하는 데 유용합니다. JWT를 통한 인증은 API의 보안을 강화하는 중요한 방법 중 하나입니다.

추가적인 개선 사항으로는 사용자 데이터베이스 통합, 비밀번호 해싱 강화, 그리고 에러 처리 로직을 추가할 수 있습니다.

이 글이 FastAPI를 통한 API 개발에 도움이 되었기를 바랍니다.

FastAPI 서버개발, docker compose 이미지 빌드

1. FastAPI란?

FastAPI는 Python으로 작성된 현대적인 웹 프레임워크입니다. 비동기 프로그래밍을 지원하며, 속도가 빠르고 코드 작성이 간단한 특징을 가지고 있습니다. FastAPI는 OpenAPI와 JSON Schema를 기반으로 하여, 자동으로 문서화된 API를 생성할 수 있습니다. 또한, 타입 힌팅(Type Hinting)을 활용하여 오류를 사전에 예방할 수 있는 장점이 있습니다.

2. Docker란?

Docker는 애플리케이션을 컨테이너라는 가벼운 격리된 환경에서 실행할 수 있게 해주는 플랫폼입니다. 개발자들은 의존성을 포함한 모든 패키지를 컨테이너에 포함시켜, ‘어디서나 일관된 환경에서 실행되도록’ 할 수 있습니다. Docker를 사용하면 생산성과 효율성을 크게 향상시킬 수 있습니다.

3. Docker Compose란?

Docker Compose는 여러 개의 Docker 컨테이너를 정의하고 관리할 수 있는 도구입니다. YAML 파일을 사용하여 애플리케이션의 서비스를 정의하고, 단일 명령으로 여러 컨테이너를 실행할 수 있습니다. Docker Compose를 사용하면 복잡한 애플리케이션의 관리가 훨씬 수월해집니다.

4. FastAPI 서버 만들기

이제 FastAPI로 간단한 웹 서버를 만들어 보겠습니다. 먼저 필요한 패키지를 설치합니다.

pip install fastapi uvicorn

다음으로, FastAPI 앱을 설정합니다. main.py라는 파일을 생성하고 아래 코드를 작성합니다.

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def read_root():
    return {"Hello": "World"}

@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}

위의 코드는 간단한 FastAPI 애플리케이션을 생성합니다. 루트 경로(“/”)는 “Hello World”를 반환하고, “/items/{item_id}” 경로는 요청한 item_id와 쿼리 매개변수 q를 반환합니다.

5. Dockerfile 생성하기

Docker를 사용하여 FastAPI 애플리케이션을 배포하기 위해 Dockerfile을 생성합니다. 프로젝트의 루트 디렉토리에 Dockerfile을 생성하고 아래 내용을 작성합니다.

FROM python:3.9

WORKDIR /app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]

위의 Dockerfile에서는 Python 3.9 이미지를 기반으로 애플리케이션을 위한 작업 디렉토리 /app를 설정합니다. 필요한 패키지를 설치하고 애플리케이션 코드를 복사한 후, uvicorn을 실행하여 FastAPI 서버를 시작합니다.

6. requirements.txt 생성하기

FastAPI와 Uvicorn을 설치하기 위한 requirements.txt 파일을 생성합니다. 다음 내용을 requirements.txt에 추가합니다.

fastapi
uvicorn

7. Docker Compose 설정하기

여러 서비스를 쉽게 관리하기 위해 docker-compose.yml 파일을 생성합니다. 프로젝트의 루트 디렉토리에 아래와 같이 docker-compose.yml 파일을 작성합니다.

version: '3.8'

services:
  app:
    build: .
    ports:
      - "80:80"

이 구성 파일은 “app”이라는 서비스를 정의하고, 현재 디렉토리에서 이미지를 빌드합니다. 또한, 호스트의 80 포트를 컨테이너의 80 포트에 매핑합니다.

8. Docker Compose를 통해 서버 실행하기

이제 모든 설정이 완료되었습니다. 다음 명령어로 Docker Compose를 통해 FastAPI 서버를 실행할 수 있습니다.

docker-compose up --build

위 명령어를 실행하면 Docker가 이미지를 빌드하고 컨테이너를 실행합니다. 브라우저에서 http://localhost로 접근하면 “Hello World” 메시지를 볼 수 있습니다.

9. 요약 및 결론

이 글에서는 FastAPI 서버를 개발하고 Docker Compose를 사용하여 이미지를 빌드하는 방법을 살펴보았습니다. FastAPI는 경량이며 비동기 처리를 지원하는 프레임워크로, Docker는 환경 구성을 통합하여 배포를 수월하게 만들어 줍니다. 이러한 도구들은 특히 복잡한 애플리케이션을 구축할 때 효과적입니다.

이제 여러분은 FastAPI와 Docker Compose를 활용하여 자신의 프로젝트에 맞게 서버를 구축하고 배포할 수 있는 능력을 갖추게 되었습니다.

추가적으로, 다양한 FastAPI의 기능 및 Docker의 고급 사용법을 탐구해보시기 바랍니다.

Express 개발 강좌, Socket.io를 이용한 실시간 통신 구현

이번 강좌에서는 Node.js의 Express 프레임워크와 Socket.io 라이브러리를 사용하여 실시간 통신을 구현하는 방법에 대해 알아보겠습니다. 실시간 통신은 사용자와 시스템 간의 즉각적인 데이터 전송을 가능하게 하여, 웹애플리케이션의 사용자 경험을 크게 향상시킵니다. 예를 들어, chatting application이나 실시간 데이터 대시보드와 같은 시스템에서 유용하게 사용됩니다.

1. Socket.io란?

Socket.io는 웹 소켓을 기반으로 한 실시간 양방향 통신을 위한 JavaScript 라이브러리입니다. Socket.io는 클라이언트와 서버 간의 실시간 연결을 생성하고, 이를 통해 데이터를 실시간으로 전송할 수 있게 해줍니다. 이 라이브러리는 웹 소켓, HTTP Long Polling 등 여러 전송 방법을 지원합니다.

1.1 특징

  • 실시간 양방향 통신 지원
  • 이벤트 기반 API
  • 모든 브라우저 지원
  • 자동 재연결 기능

2. 환경 설정

이제 Express와 Socket.io를 설치하고 기본적인 서버를 설정해 보겠습니다. Node.js가 설치되어 있어야 합니다. 만약 설치되어 있지 않다면, Node.js 공식 웹사이트(nodejs.org)에서 다운로드하여 설치할 수 있습니다.

2.1 프로젝트 초기화

mkdir socketio-example
cd socketio-example
npm init -y

2.2 필요한 패키지 설치

npm install express socket.io

3. 서버 및 클라이언트 생성

이제 기본적인 Express 서버를 생성하고 Socket.io를 연결하겠습니다.

3.1 Express 서버 설정

const express = require('express');
const http = require('http');
const socketIo = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = socketIo(server);

app.get('/', (req, res) => {
    res.sendFile(__dirname + '/index.html');
});

io.on('connection', (socket) => {
    console.log('새로운 클라이언트 연결됨');

    socket.on('disconnect', () => {
        console.log('클라이언트 연결 해제');
    });
});

const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
    console.log(`서버가 http://localhost:${PORT}에서 실행 중`);
});

3.2 클라이언트 HTML 파일 생성

서버의 기본 페이지로 사용할 index.html 파일을 생성합니다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Socket.io 예제</title>
    <script src="/socket.io/socket.io.js"></script>
    <script>
        const socket = io();
        
        socket.on('connect', () => {
            console.log('서버에 연결됨');
        });
    </script>
</head>
<body>
    <h1>Socket.io와 함께하는 실시간 통신</h1>
</body>
</html>

3.3 서버 실행

이제 서버를 실행해 보겠습니다.

node index.js

서버가 실행 중인 상태에서 브라우저에서 http://localhost:3000으로 이동하면 “Socket.io와 함께하는 실시간 통신”이라는 문구가 표시되는 웹 페이지를 볼 수 있을 것입니다.

4. 기본 이벤트 통신 구현

이제 Socket.io의 기본적인 스트림을 사용하여 클라이언트와 서버 간의 메시지를 주고받는 기능을 구현해 보겠습니다.

4.1 메시지 전송

서버 코드 수정

io.on('connection', (socket) => {
    console.log('새로운 클라이언트 연결됨');

    socket.on('chat message', (msg) => {
        console.log('메시지 수신: ' + msg);
        io.emit('chat message', msg);
    });

    socket.on('disconnect', () => {
        console.log('클라이언트 연결 해제');
    });
});

클라이언트 코드 수정

<body>
    <h1>Socket.io와 함께하는 실시간 통신</h1>
    <ul id="messages"></ul>
    <form id="form" action="<!---->" onsubmit="sendMessage(event)">
        <input id="m" autocomplete="off" />
        <button>전송</button>
    </form>
    
    <script>
        const socket = io();

        function sendMessage(event) {
            event.preventDefault();
            const input = document.getElementById('m');
            socket.emit('chat message', input.value);
            input.value = '';
            return false;
        }

        socket.on('chat message', (msg) => {
            const item = document.createElement('li');
            item.textContent = msg;
            document.getElementById('messages').appendChild(item);
            window.scrollTo(0, document.body.scrollHeight);
        });
    </script>
</body>

4.2 결과 확인

서버를 재실행하고 http://localhost:3000에 접속한 후, 메시지를 전송하면 클라이언트 간에 메시지가 실시간으로 전송되는 것을 볼 수 있습니다.

5. Room 기능 구현

Socket.io의 강력한 기능 중 하나는 방(Room) 기능입니다. 이를 통해 여러 개의 방을 만들어 특정 클라이언트만 메시지를 수신하도록 할 수 있습니다.

5.1 방 만들기

io.on('connection', (socket) => {
    console.log('새로운 클라이언트 연결됨');

    socket.on('join room', (room) => {
        socket.join(room);
        console.log(`방 ${room}에 접속함.`);
    });

    socket.on('chat message', (msg) => {
        const room = msg.room;
        const message = msg.message;
        console.log(`방 ${room}에 메시지 수신: ${message}`);
        io.to(room).emit('chat message', message);
    });

    socket.on('disconnect', () => {
        console.log('클라이언트 연결 해제');
    });
});

5.2 클라이언트 방 입장 기능 추가

const roomInput = document.getElementById('room-input');
socket.emit('join room', roomInput.value);
socket.emit('chat message', { room: roomInput.value, message: input.value });

5.3 결과 확인

방 기능 추가 후, 특정 방에 들어가서 메시지를 주고받을 수 있는지 확인해 보세요.

6. 결론

이번 강좌에서는 Express와 Socket.io를 사용하여 실시간 통신을 구현하는 방법에 대해 알아보았습니다. 실시간 채팅이나 데이터 전송을 필요로 하는 다양한 애플리케이션에서 이 기술을 활용할 수 있습니다.

실시간 통신을 구현하면서 여성화된 성격의 사용자 경험을 제공할 수 있으며, 더 나아가 웹 소켓을 통해 다양한 기능을 확장할 수 있습니다. Socket.io와 Express 조합을 이용한 실시간 통신의 가능성은 무궁무진합니다.

7. 더 알아보기

추가적인 자료나 포괄적인 Socket.io의 기능들을 탐구하고 싶다면 공식 문서(socket.io/docs)를 방문해 보세요. 자주 묻는 질문(FAQ) 섹션이나 다양한 예제 코드도 확인할 수 있습니다.

Express 개발 강좌, 프로젝트 구조 및 파일 구성 이해하기

1. 서론

Express.js는 Node.js를 위한 웹 프레임워크로, RESTful API를 쉽게 구축할 수 있도록 도와준다.
본 강좌에서는 Express로 백엔드 서버를 개발하기 위한 프로젝트 구조와 파일 구성을 깊이 있게 알아보겠다.
또한 최적의 개발 환경을 위한 기본적인 설정 방법과 예제를 제공할 예정이다.

2. Express.js란?

Express.js는 경량화된 Node.js 웹 어플리케이션 프레임워크로, 웹 서버 및 API 구축을
위한 다양한 기능들을 제공한다. 기본적으로 라우팅, 미들웨어 지원, HTTP 요청 및 응답 처리 등을
용이하게 할 수 있도록 지원한다. Express를 사용하면 더욱 효율적으로 웹 서버를 구축할 수 있다.

3. 프로젝트 구조 만들기

Express 프로젝트의 구조를 제대로 이해하면 코드 유지 보수와 확장성이 향상된다.
여기서는 일반적으로 사용하는 Express 프로젝트 구조를 설명하겠다.
아래의 구조는 기본적인 파일과 폴더로 구성되어 있다.

        
        /my-express-app
        ├── node_modules/
        ├── src/
        │   ├── controllers/
        │   ├── models/
        │   ├── routes/
        │   ├── middlewares/
        │   ├── config/
        │   └── app.js
        ├── .env
        ├── .gitignore
        ├── package.json
        └── README.md
        
        

3.1. root 디렉토리

모든 Express 프로젝트는 최상위 디렉터리에서 시작한다. 이 디렉터리에는
프로젝트의 메타데이터와 의존성을 정의하는 package.json 파일이 포함된다.
node_modules 폴더는 설치된 패키지를 포함하며,
.env 파일은 애플리케이션의 환경 변수 설정을 위한 파일이다.
.gitignore 파일은 Git에 포함하지 않을 파일들을 정의한다.

3.2. src 폴더

src 폴더는 애플리케이션의 실제 코드가 위치하는 곳이다.
이 폴더 내의 구성 요소들은 다음과 같다:

  • controllers/: 비즈니스 로직이 포함된 파일들이 위치한다.
    클라이언트의 요청을 처리하고, 필요한 데이터를 가져오며, 응답을 반환하는 역할을 한다.
  • models/: 데이터베이스와의 상호작용을 위한 모델들이다.
    일반적으로 ORM(Object Relational Mapping) 라이브러리와 함께 사용된다.
  • routes/: HTTP 요청을 특정 컨트롤러로 라우팅하는 역할을 한다.
    각 API 엔드포인트에 대한 경로를 정의한다.
  • middlewares/: 요청과 응답 사이에 실행되는 함수들로,
    인증, 로깅, 에러 처리 등을 위한 미들웨어를 포함한다.
  • config/: 애플리케이션의 설정 정보를 저장하는 폴더로,
    데이터베이스 연결 정보나 외부 서비스 API 키 등을 포함할 수 있다.
  • app.js: 애플리케이션을 초기화하고, 라우터를 설정하며,
    서버를 시작하는 등 핵심 기능을 구현하는 파일이다.

4. Express 애플리케이션 생성하기

이제 실제 Express 애플리케이션을 생성해 보겠다.
아래 단계에 따라 애플리케이션을 설정하고, 파일 구조를 구성할 것이다.

4.1. 프로젝트 초기화

        
        mkdir my-express-app
        cd my-express-app
        npm init -y
        
    

위 명령어를 통해 새로운 디렉토리를 생성하고, Node.js 프로젝트를 초기화한다.

4.2. Express 설치

        
        npm install express
        
    

Express 패키지를 설치한다.

4.3. 기본 서버 설정하기

src/app.js 파일을 생성하고 기본 서버 코드를 작성해 보자.

        
        const express = require('express');
        const app = express();
        const PORT = process.env.PORT || 3000;

        app.get('/', (req, res) => {
            res.send('Hello, Express!');
        });

        app.listen(PORT, () => {
            console.log(`Server is running on http://localhost:${PORT}`);
        });
        
    

4.4. 서버 실행

서버를 실행하기 위해 아래 명령어를 사용한다.

        
        node src/app.js
        
    

브라우저에서 http://localhost:3000에 접근하여 “Hello, Express!” 메시지를 확인할 수 있다.

5. 라우팅 구현하기

Express의 라우팅 기능을 사용하여 API 엔드포인트를 추가하겠다.
routes 폴더를 생성하고 userRoutes.js 파일을 만들어 사용자 관련 라우팅을 작성해 보자.

        
        // src/routes/userRoutes.js
        const express = require('express');
        const router = express.Router();

        // 사용자 목록 조회
        router.get('/users', (req, res) => {
            res.json([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]);
        });

        // 사용자 추가
        router.post('/users', (req, res) => {
            // 사용자 추가 로직 구현
            res.status(201).json({ id: 3, name: req.body.name });
        });

        module.exports = router;
        
    

5.1. 라우터 연결하기

app.js 파일에서 userRoutes를 import 하고,
이를 Express 앱에 연결하자.

        
        const express = require('express');
        const app = express();
        const userRoutes = require('./routes/userRoutes');

        app.use(express.json()); // JSON 요청 본문 파싱
        app.use('/api', userRoutes); // '/api' 경로로 라우터 연결

        const PORT = process.env.PORT || 3000;

        app.listen(PORT, () => {
            console.log(`Server is running on http://localhost:${PORT}`);
        });
        
    

5.2. API 테스트하기

Postman 또는 curl을 사용하여 API를 테스트할 수 있다.
GET /api/users 요청을 보내면, 사용자 목록을 확인할 수 있을 것이다.

6. 비즈니스 로직 분리하기

비즈니스 로직을 컨트롤러로 분리하여 코드를 더 깔끔하고 유지보수하기 쉽게 만들자.
controllers 폴더를 생성하고 userController.js 파일을 추가하자.

        
        // src/controllers/userController.js
        exports.getUsers = (req, res) => {
            res.json([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]);
        };

        exports.addUser = (req, res) => {
            res.status(201).json({ id: 3, name: req.body.name });
        };
        
    

6.1. 컨트롤러와 라우터 연결하기

userRoutes.js에서 라우터를 수정하여 컨트롤러를 사용한다.

        
        const express = require('express');
        const router = express.Router();
        const userController = require('../controllers/userController');

        router.get('/users', userController.getUsers);
        router.post('/users', userController.addUser);

        module.exports = router;
        
    

7. 데이터베이스 연결하기

이제 MongoDB를 사용하여 데이터베이스와 연결하는 방법을 살펴보겠다.
mongoose 패키지를 설치하고, 간단한 게시판 예제를 구현해보자.

        
        npm install mongoose
        
    

7.1. 모델 정의하기

models 폴더를 생성하고 User.js 파일을 만들어
사용자 모델을 정의하자.

        
        // src/models/User.js
        const mongoose = require('mongoose');

        const userSchema = new mongoose.Schema({
            name: {
                type: String,
                required: true
            }
        });

        module.exports = mongoose.model('User', userSchema);
        
    

7.2. 데이터베이스 연결 설정하기

        
        const mongoose = require('mongoose');
        const express = require('express');
        const userRoutes = require('./routes/userRoutes');

        const app = express();
        mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true })
            .then(() => console.log('MongoDB Connected'))
            .catch(err => console.error('MongoDB connection error:', err));

        app.use(express.json());
        app.use('/api', userRoutes);
        // ...
        
    

7.3. 사용자 추가 기능 수정하기

사용자를 추가할 때 데이터베이스에 기록하도록 기능을 수정하자.

        
        // src/controllers/userController.js
        const User = require('../models/User');

        exports.addUser = async (req, res) => {
            try {
                const newUser = new User({ name: req.body.name });
                await newUser.save();
                res.status(201).json(newUser);
            } catch (error) {
                res.status(500).json({ message: error.message });
            }
        };
        
    

8. 결론

이번 강좌를 통해 Express.js 프로젝트의 구조와 파일 구성에 대해 알아보았다.
Express를 기반으로 한 백엔드 서버를 간단하게 구현해 보았고, 비즈니스 로직과 데이터베이스 연결을
통해 애플리케이션의 기능을 확장하는 방법을 배웠다.
이러한 기초 지식을 바탕으로 더 복잡한 웹 애플리케이션으로 발전시킬 수 있을 것이다.

추가 자료