본문 바로가기

꾸미기는 AI에게

1. 이번 절의 목표

이번 절에서는 코드를 손으로 치지 않습니다. AI에게 꾸미기를 시키고, 돌아온 코드를 읽습니다.

지금 게시판은 제목 세 줄이 밋밋하게 나옵니다. 게시판처럼 보이려면 표가 있어야 하고, 제목을 누르면 게시물로 들어가야 하고, 최신 글이 위에 와야 합니다. 이런 일은 HTML과 CSS를 길게 써야 합니다. 뼈대는 이미 있으니, 이 살은 AI에게 시킵니다.

2. AI에게 시키기

AI에게 아래 프롬프트를 지금 main.py 전체와 함께 던집니다.

AI에게 시키기

아래는 FastAPI로 만든 게시판이야. 구조는 바꾸지 말고 화면만 꾸며줘. 조건은 이래.

1. /notice 는 번호, 제목, 작성일 세 칸이 있는 표 모양으로. 제목을 누르면 /notice/번호 로 이동.
2. 최신 글이 표 맨 위에 오게.
3. /notice/번호 는 제목, 작성일, 내용이 보이고 아래에 "목록으로" 버튼.
4. CSS는 style 이라는 변수 하나에 담아서 모든 화면에서 같이 쓰게.
5. data 의 각 게시물에 'date' 항목을 추가해줘.
6. 외부 라이브러리나 템플릿 파일 없이 main.py 한 파일로.

(main.py 붙여넣기)

조건 6번이 중요합니다. 이 조건이 없으면 AI는 Jinja2 템플릿이나 별도 폴더를 만들어 오고, 갑자기 파일이 네 개가 됩니다. 원하는 범위를 정확히 말해야 원하는 크기의 답이 옵니다.

3. 돌아온 코드

AI마다 조금씩 다르지만 대체로 아래와 비슷한 코드가 옵니다. 이 코드는 실제로 AI가 만든 것을 그대로 옮긴 것입니다. 여러분이 받은 코드와 비교하면서 보세요.

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()

data = [
    {'title': 'fastapi 너무 재미있어요! 1', 'contents': '첫 번째 글입니다.', 'date': '2026-09-17'},
    {'title': 'fastapi 너무 재미있어요! 2', 'contents': '두 번째 글입니다.', 'date': '2026-09-18'},
    {'title': 'fastapi 너무 재미있어요! 3', 'contents': '세 번째 글입니다.', 'date': '2026-09-19'},
]

# 모든 페이지에서 같이 쓰는 꾸미기
style = """
<style>
    body { max-width: 700px; margin: 40px auto; font-family: sans-serif; color: #333; }
    a { color: #333; text-decoration: none; }
    .top { display: flex; justify-content: space-between; align-items: center; }
    .btn { display: inline-block; padding: 8px 16px; background: #2E6FF2;
           color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; }

    /* 게시판 목록 */
    table { width: 100%; border-collapse: collapse; border-top: 2px solid #333; }
    th { padding: 12px; background: #f5f5f5; border-bottom: 1px solid #ddd; }
    td { padding: 12px; border-bottom: 1px solid #ddd; text-align: center; }
    td.title { text-align: left; }
    tr:hover td { background: #f0f5ff; }

    /* 게시물 상세 */
    .post { border-top: 2px solid #333; border-bottom: 1px solid #ddd; }
    .post-head { padding: 16px; background: #f5f5f5; border-bottom: 1px solid #ddd; }
    .post-head h2 { margin: 0 0 8px; }
    .post-head span { color: #888; font-size: 14px; }
    .post-body { padding: 24px 16px; min-height: 150px; line-height: 1.6; }
</style>
"""


@app.get("/", response_class=HTMLResponse)
def index():
    return "<h1>hello world</h1>"


@app.get("/about", response_class=HTMLResponse)
def about():
    return "<h1>about 입니다</h1>"


# 게시판 목록
@app.get("/notice", response_class=HTMLResponse)
def notice():
    rows = ''
    # 최신 글이 위로 오도록 뒤에서부터 꺼내기
    for id in range(len(data), 0, -1):
        i = data[id - 1]
        rows += f"""
            <tr>
                <td>{id}</td>
                <td class="title"><a href="/notice/{id}">{i['title']}</a></td>
                <td>{i['date']}</td>
            </tr>"""
    return f"""
        {style}
        <div class="top">
            <h1>게시판</h1>
            <a class="btn" href="/notice/write">글쓰기</a>
        </div>
        <table>
            <tr>
                <th width="60">번호</th>
                <th>제목</th>
                <th width="120">작성일</th>
            </tr>
            {rows}
        </table>
    """


# 게시물 상세
@app.get("/notice/{id}", response_class=HTMLResponse)
def notice_detail(id: int):
    if id < 1 or id > len(data):
        return "존재하지 않는 게시물입니다."
    post = data[id - 1]
    return f"""
        {style}
        <h1>게시판</h1>
        <div class="post">
            <div class="post-head">
                <h2>{post['title']}</h2>
                <span>번호 {id} · 작성일 {post['date']}</span>
            </div>
            <div class="post-body">{post['contents']}</div>
        </div>
        <p><a class="btn" href="/notice">목록으로</a></p>
    """

main.py를 이 코드로 바꾸고 /notice를 열면 표 모양 게시판이 나옵니다. 제목을 누르면 게시물로 들어가고, 목록으로 버튼을 누르면 돌아옵니다.

4. 코드 읽기, 바뀐 것만

코드가 두 배로 길어졌지만 뼈대는 그대로입니다. 주소 두 개, 함수 두 개, 리스트 하나. 바뀐 것은 함수가 돌려주는 글자의 모양뿐입니다. 바뀐 자리만 읽습니다.

  • style = """...""": CSS를 글자로 담아 둔 변수입니다. 두 함수 모두 {style}로 이 글자를 앞에 끼워 넣습니다. 같은 내용을 두 번 쓰지 않으려고 변수로 뺐습니다. 지난 절에서 게시물 함수를 하나로 합친 것과 같은 생각입니다.
  • range(len(data), 0, -1): 3, 2, 1 순서로 셉니다. range(시작, 끝, 간격)이고, 간격이 -1이면 거꾸로 셉니다. 끝인 0은 포함하지 않습니다. 최신 글을 위에 올리려고 거꾸로 돕니다.
  • <a href="/notice/{id}">: 제목을 링크로 만듭니다. 누르면 브라우저가 그 주소를 서버에 달라고 하고, 서버는 notice_detail을 실행합니다. 화면끼리 이어지는 방법은 결국 주소입니다.
  • <a class="btn" href="/notice/write">글쓰기</a>: 글쓰기 버튼이 생겼습니다. 눌러 보면 Not Found가 나옵니다. /notice/write에 붙은 함수가 아직 없기 때문입니다. 다음 절에서 만듭니다.

파이썬 다시 보기: range 거꾸로

range(3, 0, -1)은 3, 2, 1입니다. range(1, 4)는 1, 2, 3입니다. 세 번째 값이 간격이고, 음수면 거꾸로 셉니다. 헷갈리면 터미널에서 python을 치고 list(range(3, 0, -1))을 쳐 보세요. 나올 때는 exit()입니다.

5. 마음에 안 들면 다시 시키기

색이 마음에 안 들거나 글자가 작으면 다시 시킵니다. 이때도 코드를 같이 붙여 넣고, 바꿀 곳을 정확히 짚습니다.

AI에게 시키기

아래 코드에서 style 변수 안의 CSS만 바꿔줘. 파란색 버튼을 진한 회색으로, 표의 글자 크기를 조금 키워줘. 파이썬 코드는 건드리지 마.
(main.py 붙여넣기)

"파이썬 코드는 건드리지 마"라는 한 줄이 있고 없고의 차이가 큽니다. 이 한 줄이 없으면 AI는 친절하게 함수 이름까지 바꿔 놓고, 여러분은 어디가 바뀌었는지 찾느라 시간을 씁니다. 뼈대를 아는 사람만 이 한 줄을 쓸 수 있습니다.

꾸미기는 AI에게 - AI와 함께하는 백엔드 베이스캠프 | 위니버시티