python+fastAPI+postgresql 로 구성된 환경에서 간단한 CRUD 구현1

2025. 3. 28. 00:45·Python

python 도 fastAPI 도 잘 모르지만(기본적인 것만 알고 있음)

ai코딩툴을 이용해서 간단한 CRUD 코딩을 해보았습니다.

대략 어떻게 흘러가고, 어떻게 데이터를 관리하는지 알아보기 위해서였습니다.

 

기본적으로 환경설정용으로 .env, .python-version, requirements.txt 파일을 생성했습니다.

DB_HOST=localhost
DB_NAME=postgres
DB_USER=postgres
DB_PASSWORD=xxxxxx
DB_PORT=5432

 

requirements.txt 는 pip 패키지목록을 정리해 두고, 

pip install -r requirements.txt

 

로 실행하면 모든 패키지를 한번에 설치해 줍니다.

fastapi==0.104.1
uvicorn==0.24.0
sqlalchemy==2.0.23
psycopg2-binary==2.9.9
python-dotenv==1.0.0
pydantic==2.5.2
jinja2==3.1.3

 

 

main.py 파일은 설정과 초기화를 정의합니다.

"""
FastAPI 애플리케이션의 메인 모듈
이 모듈은 FastAPI 애플리케이션의 설정과 초기화를 담당합니다.
"""

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from database.database import engine, Base
from dotenv import load_dotenv
from controller.user_controller import router

# 환경 변수 로드
load_dotenv()

# FastAPI 애플리케이션 생성
app = FastAPI(
    title="FastAPI Backend",
    description="사용자 관리를 위한 RESTful API",
    version="1.0.0"
)

# Jinja2 템플릿 설정
templates = Jinja2Templates(directory="templates")

# 라우터 등록
app.include_router(router)

# 데이터베이스 테이블 생성
Base.metadata.create_all(bind=engine)

@app.get("/", response_class=HTMLResponse)
async def root(request: Request):
    """
    루트 경로 핸들러
    웹 애플리케이션의 메인 페이지를 반환합니다.
    
    Args:
        request (Request): FastAPI의 요청 객체
    
    Returns:
        HTMLResponse: index.html 템플릿을 렌더링한 응답
    """
    return templates.TemplateResponse("index.html", {"request": request})

 

환경변수 .env 를 로드하고

FastAPI 애플리케이션을 생성하고

Jinja2 템플릿 설정을 하고

라우터 등록하고

데이터베이스 테이블 생성(없으면) 하고

최초 화면으로 index.html 템플릿을 호출해서 화면을 시작합니다.

 

여기서 @app.get 에서 app 은 FastAPI 클래스의 '인스턴스' 입니다.

 

실행은 아래 명령어를 입력해서 실행합니다.

uvicorn main:app --reload

 

localhost:8000

으로 브라우저에서 실행하면

 

 

저는 이런 화면이 나타납니다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FastAPI Backend</title>
    <style>
		.....
    </style>
    <script src="https://accounts.google.com/gsi/client" async defer></script>
</head>
<body>
    <div class="container">
        <h1>Welcome to FastAPI Backend</h1>
        <div id="loginStatus">
            <!-- Login status will be shown here -->
        </div>
        <div id="googleSignInButton">
            <div id="g_id_onload"
                data-client_id="881299416391-xxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com"
                data-callback="handleCredentialResponse"
                data-auto_prompt="false">
            </div>
            <div class="g_id_signin"
                data-type="standard"
                data-size="large"
                data-theme="outline"
                data-text="sign_in_with"
                data-shape="rectangular"
                data-logo_alignment="left">
            </div>
        </div>
    </div>

    <script>
        let isLoggedIn = false;
        let currentUserId = null;

        function showLoginStatus(message, isError = false) {
            const loginStatus = document.getElementById('loginStatus');
            loginStatus.innerHTML = `
                <div class="user-info" ${isError ? 'style="color: red;"' : ''}>
                    ${message}
                </div>
            `;
        }

        function updateUIForLoggedInUser(userData, payload) {
            isLoggedIn = true;
            currentUserId = userData.id;
            const googleSignInButton = document.getElementById('googleSignInButton');
            googleSignInButton.style.display = 'none';
            
            showLoginStatus(`
                <p>로그인 중: ${payload.name}</p>
                <p>이메일: ${payload.email}</p>
                <p>DB에 저장된 ID: ${userData.id}</p>
                <button class="logout-btn" onclick="handleLogout()">로그아웃</button>
                <button class="btn" onclick="viewLoginHistory()">로그인 기록 보기</button>
            `);
        }

        function updateUIForLoggedOutUser() {
            isLoggedIn = false;
            currentUserId = null;
            const googleSignInButton = document.getElementById('googleSignInButton');
            googleSignInButton.style.display = 'block';
            showLoginStatus('<p>로그인이 필요합니다</p>');
        }

        async function handleCredentialResponse(response) {
            if (response.credential) {
                // JWT 디코딩
                const base64Url = response.credential.split('.')[1];
                const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
                const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
                    return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
                }).join(''));

                const payload = JSON.parse(jsonPayload);
                
                try {
                    // 백엔드 API 호출하여 사용자 정보 저장
                    const response = await fetch('/users/google-login', {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json',
                        },
                        body: JSON.stringify({
                            name: payload.name,
                            email: payload.email
                        })
                    });

                    if (!response.ok) {
                        throw new Error('Failed to save user data');
                    }

                    const userData = await response.json();
                    updateUIForLoggedInUser(userData, payload);
                    
                } catch (error) {
                    console.error('Error:', error);
                    showLoginStatus('로그인 정보 저장 중 오류가 발생했습니다.', true);
                }
            }
        }

        async function handleLogout() {
            if (!currentUserId) {
                updateUIForLoggedOutUser();
                return;
            }

            try {
                // 백엔드에 로그아웃 기록
                const response = await fetch(`/users/google-logout/${currentUserId}`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    }
                });

                if (!response.ok) {
                    throw new Error('Failed to save logout data');
                }

                google.accounts.id.disableAutoSelect();
                google.accounts.id.revoke(localStorage.getItem('userEmail'), done => {
                    localStorage.removeItem('userEmail');
                    updateUIForLoggedOutUser();
                });
            } catch (error) {
                console.error('Error:', error);
                showLoginStatus('로그아웃 처리 중 오류가 발생했습니다.', true);
            }
        }

        async function viewLoginHistory() {
            if (!currentUserId) {
                showLoginStatus('로그인이 필요합니다', true);
                return;
            }

            try {
                const response = await fetch(`/users/login-history/${currentUserId}`);
                if (!response.ok) {
                    throw new Error('Failed to fetch login history');
                }

                const history = await response.json();
                let historyHtml = '<div class="login-history">';
                historyHtml += '<h3>로그인/로그아웃 기록</h3>';
                historyHtml += '<ul>';
                
                history.forEach(record => {
                    const actionType = record.action_type === 'login' ? '로그인' : '로그아웃';
                    const time = record.action_time;
                    const formattedTime = `${time.slice(0,4)}-${time.slice(4,6)}-${time.slice(6,8)} ${time.slice(8,10)}:${time.slice(10,12)}:${time.slice(12,14)}`;
                    historyHtml += `<li>${actionType} - ${formattedTime}</li>`;
                });
                
                historyHtml += '</ul></div>';
                showLoginStatus(historyHtml);
            } catch (error) {
                console.error('Error:', error);
                showLoginStatus('로그인 기록을 가져오는 중 오류가 발생했습니다.', true);
            }
        }

        // 페이지 로드 시 로그인 상태 체크
        window.onload = function() {
            if (!google.accounts.id.initialize) {
                showLoginStatus('로그인이 필요합니다');
            }
        }
    </script>
</body>
</html>

 

 

최초 실행할때 database 에 연결하고 테이블이 존재하는지 확인하고 없으면 테이블도 생성해줍니다.

database/database.py

"""
데이터베이스 연결 및 세션 관리
이 모듈은 PostgreSQL 데이터베이스 연결 설정과 세션 관리를 담당합니다.
"""

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

# 데이터베이스 연결 URL 설정
# 환경 변수에서 URL을 가져오거나, 기본값 사용
SQLALCHEMY_DATABASE_URL = os.getenv(
    "DATABASE_URL",
    "postgresql://postgres:xxxxxxxx@localhost/postgres"
)

# 데이터베이스 엔진 생성
engine = create_engine(SQLALCHEMY_DATABASE_URL)

# 세션 팩토리 생성
# autocommit=False: 명시적 커밋 필요
# autoflush=False: 명시적 플러시 필요
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# 모든 모델의 기본 클래스
Base = declarative_base()

def get_db():
    """
    데이터베이스 세션 생성 및 관리를 위한 제너레이터 함수
    
    Yields:
        Session: 데이터베이스 세션 객체
    
    Note:
        FastAPI의 의존성 주입 시스템에서 사용됩니다.
        세션은 요청 처리가 완료되면 자동으로 닫힙니다.
    """
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

 

user_test 라는 테이블 생성

model/user.py

"""
사용자 데이터베이스 모델 정의
이 모듈은 사용자 테이블의 구조와 기본 설정을 정의합니다.
"""

from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import relationship
from database.database import Base
from datetime import datetime

def get_current_time():
    """
    현재 시간을 'yyyymmddhhmmss' 형식의 문자열로 반환
    
    Returns:
        str: 'yyyymmddhhmmss' 형식의 현재 시간 (예: '20250326123456')
    """
    return datetime.now().strftime('%Y%m%d%H%M%S')

class UserTest(Base):
    """
    사용자 정보를 저장하는 데이터베이스 모델
    
    Attributes:
        id (int): 사용자 고유 식별자 (기본키)
        username (str): 사용자 이름 (고유값)
        email (str): 이메일 주소 (고유값)
        created_at (str): 계정 생성 시간 ('yyyymmddhhmmss' 형식)
        updated_at (str): 정보 수정 시간 ('yyyymmddhhmmss' 형식)
    """
    __tablename__ = "user_test"  # 테이블 이름

    # 컬럼 정의
    id = Column(Integer, primary_key=True, index=True)  # 기본키
    username = Column(String, unique=True, index=True)  # 사용자 이름 (유니크 인덱스)
    email = Column(String, unique=True, index=True)     # 이메일 (유니크 인덱스)
    created_at = Column(String, default=get_current_time)  # 생성 시간
    updated_at = Column(String, default=get_current_time, onupdate=get_current_time)  # 수정 시간

    # 관계 정의
    login_history = relationship("LoginInfo", back_populates="user")

 

이렇게 하면서 postgreSql 에서 자동으로 테이블을 생성합니다.

-- public.user_test definition

-- Drop table

-- DROP TABLE public.user_test;

CREATE TABLE public.user_test (
	id serial4 NOT NULL,
	username varchar NULL,
	email varchar NULL,
	created_at varchar NULL,
	updated_at varchar NULL,
	CONSTRAINT user_test_pkey PRIMARY KEY (id)
);
CREATE UNIQUE INDEX ix_user_test_email ON public.user_test USING btree (email);
CREATE INDEX ix_user_test_id ON public.user_test USING btree (id);
CREATE UNIQUE INDEX ix_user_test_username ON public.user_test USING btree (username);

 

나머지는 다음에...

저작자표시 (새창열림)

'Python' 카테고리의 다른 글

KOSIS 통계정보를 이용한 데이터 조회 방법  (0) 2025.03.29
python+fastAPI+postgresql 로 구성된 환경에서 간단한 CRUD 구현2  (0) 2025.03.28
FastAPI + Python 으로 구성한 Back-End  (0) 2025.03.26
table 에 crud 할 수 있는 python code  (0) 2025.03.10
db 연결하는 python 코드  (0) 2025.03.10
'Python' 카테고리의 다른 글
  • KOSIS 통계정보를 이용한 데이터 조회 방법
  • python+fastAPI+postgresql 로 구성된 환경에서 간단한 CRUD 구현2
  • FastAPI + Python 으로 구성한 Back-End
  • table 에 crud 할 수 있는 python code
크레비즈
크레비즈
  • 크레비즈
    크레비즈 커뮤니티
    크레비즈
  • 전체
    오늘
    어제
    • 분류 전체보기 (69) N
      • Front-end (14) N
      • Back-end (7)
      • Python (11)
      • AI 공부 (7)
      • Mobile (4)
      • Script (4)
      • 인프라 (5)
      • 참고할 내용 (12)
      • reference page (0)
      • Project (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 미디어로그
    • 위치로그
    • 방명록
  • 링크

    • Spring: the source for modern java
  • 공지사항

    • 크레비즈 커뮤니티
  • 인기 글

  • 태그

    토큰
    claude sonnet
    PYTHON
    windsurf
    claude 3.7 sonnet
    Refresh Token
    vuejs
    CNN
    Claude
    Gemini
    fastapi
    ChatGPT
    Access Token
    vue
    딥러닝
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
크레비즈
python+fastAPI+postgresql 로 구성된 환경에서 간단한 CRUD 구현1
상단으로

티스토리툴바