FastAPI를 이용한 웹 서비스 구현 연습_6

Frye 'de Bacon·2023년 10월 26일

본 시리즈는 '박응용' 님의 '점프 투 FastAPI'를 바탕으로 학습 및 실습한 내용을 정리한 것입니다.


구현 및 파인튜닝한 모델을 사용한 웹 서비스 구현을 위해 FastAPI의 학습 필요성을 느껴 학습 과정을 정리합니다. 내용의 정확성이나 이론적인 부분은 당연히 원본 페이지를 참조하시는 게 좋고, 본 시리즈에서는 구현 도중 발생하는 문제 등을 해결하는 과정을 함께 기록하여 '처음부터 끝까지 따라 할 수 있는' 시리즈를 만드는 것을 목표로 합니다(물론 제1목표는 학습 내용 기록입니다).


앞서 질문 목록 기능을 완성하였으므로, 질문 목록에서 한 개를 선택해 눌렀을 때 해당 질문의 상세 내용을 볼 수 있는 질문 상세 기능을 구현해 보자.

1. 질문 상세 링크 추가하기

먼저 Home.svelte 파일에서 질문 목록으로 출력한 제목 부분에 다음과 같이 링크를 추가한다.

<script>
    import fastapi from "../lib/api"
    import { link } from 'svelte-spa-router'
...
</script>
  
  <ul>
    {#each question_list as question}
      <li><a use:link href="/detail/{question.id}">{question.subject}</a></li>
    {/each}
  </ul>

위에서 보듯 a 태그에 'use:link' 속성을 사용할 수 있도록 link를 import했다.

a 태그에 use:link를 사용하는 이유

일반적으로 a 태그는 다음과 같이 사용한다.

<a href="/some-path">Some path</a>

이렇게 만든 링크를 클릭하면 주소창에는 다음과 같이 주소가 표시된다.

http://127.0.0.1:5173/some-path

반면, a 태그에 use:link를 사용하하면 결과는 다음과 같다.

<a use:link href="/some-path">Some path</a>
http://127.0.0.1:5173/#/some-path

즉, use:link 속성을 사용한 경우 항상 '/#' 문자가 선행하도록 경로가 생성된다. 이 경우 브라우저는 이 경로를 하나의 페이지로 인식한다. 즉, 'http://127.0.0.1:5173/#/question_list' 와 'http://127.0.0.1:5173/#/answer_list' 를 하나의 페이지로 인식한다는 것이다. 이를 해시 기반 라우팅(hash based routing)이라고 한다.

해시 기반 라우팅을 사용하는 이유는 다음과 같다. 일반 방식의 주소를 사용하는 프론트엔드 파일을 서버에 적용한다고 가정했을 때, 만약 회원 가입을 위한 경로가 요청된다면 브라우저 주소창에는 http://fastapi.project1.kr/user-create 와 같은 형태의 주소가 표시될 것이다. 이 상태에서 브라우저를 새로고침하면 브라우저는 fastapi.project1.kr 서버에 /user-create라는 경로를 요청하며, 서버는 해당 경로를 해석할 수 없어 404 Not Found 오류를 발생시킨다. 왜냐하면 /user-create라는 경로는 서버가 아니라 클라이언트, 즉 프론트엔드에서만 사용하는 경로이기 때문이다.
반면 해시 기반의 주소를 사용한다면 주소창에는 http://fastapi.project1.kr/#/user-create라는 주소가 표시되며, 이 경우 브라우저에서 새로고침을 하더라도 서버로 요청이 발생하지 않는다. 왜냐하면 브라우저는 /#으로 시작하는 URL을 동일한 페이지라고 인식하고 서버로 페이지 요청을 보내지 않기 때문이다.
※ html에 대한 배경 지식이 필요한 부분이므로 이후 찾아보도록 하자.


이제 스벨트 페이지로 돌아가면 질문 목록 제목이 링크로 변경되어 있고, 해당 링크를 누르면 /#/detail/2와 같은 url이 호출될 것이다(여기서 2는 question.id에 해당하는 id값이다). 그러나 App.svelte 파일에서 아직 /detail/2와 같은 URL 규칙을 등록하지 않았으므로 빈 화면만 나타난다. 따라서 App.svelte 파일로 이동해 해당 URL 규칙을 등록한다.

<script>
  import Router from 'svelte-spa-router'
  import Home from "./routes/Home.svelte"
  import Detail from "./routes/Detail.svelte"
  
  const routes = {
    '/': Home,
    '/detail/:question_id': Detail
  }
</script>

<Router {routes}/>

※ 여기서 추가한 Detail.svelte 파일은 아직 작성하지 않았다.

'/detail/2'의 경우 /detail/ 뒤에 가변적인 숫자가 붙게 되고, 해당 숫자는 질문 데이터의 id값에 해당한다. 이처럼 URL 경로상의 가변적인 값을 다른 컴포넌트에 전달하기 위해서는 '/detail/:question_id'와 같은 URL 규칙을 적용해야 한다(앞서 다섯 번째 포스트에서 url 주소를 정리하며 간단히 언급했다). 예를 들어 '/detail/2'라는 경로가 요청되면 Detail 컴포넌트가 요청되고, Detail 컴포넌트는 2라는 값을 question_id라는 이름으로 읽을 수 있게 된다.


2. 질문 상세 화면 만들기

이제 routes 디렉토리 내에 Detail.svelte 파일을 생성하고, 다음과 같이 입력하자.

<script>
    export let params = {}
    let question_id = params.question_id
    console.log('question_id:' + question_id)
</script>

<h1>제목</h1>
<div>
    내용
</div>

Detail 컴포넌트를 호출할 때 전달할 파라미터 값을 읽기 위해 다음과 같이 params 변수를 선언했다.

let params = {}

그리고 전달된 파라미터 question_id의 값은 params.question_id로 읽을 수 있다.

이제 질문 목록 중 하나를 눌러 상세 화면을 호출하면 다음과 같이 브라우저 콘솔에 question_id가 출력되는 것을 확인할 수 있다.

브라우저의 주소창에도 'http://localhost:5173/#/detail/2' 와 같이 /#이 선행되며 질문의 id값 2가 컴포넌트의 question_id 변수에 잘 전달된 것을 확인할 수 있다.


3. 질문 상세 API 만들기

이제 다시 백엔드 영역으로 돌아가서 질문 하나에 대한 상세 내용을 리턴하는 질문 상세 API를 만들자.

API 명세

[질문 상세 조회 API]

API명URL요청 방법설명
질문 상세 조회/api/question/detail/{question_id}get질문(question_id)에 대한 상세 내역을 조회한다.

[질문 상세 조회 API의 입력 항목]
없음
[질문 상세 조회 API의 출력 항목]
Question 스키마

CRUD

가장 먼저 question_crud.py 파일을 열고 가장 아래에 질문 1건을 조회하는 get_question 함수를 선언한다.

...
def get_question(db: Session, question_id: int):
    question = db.query(Question).get(question_id)
    return question

함수 get_question은 question_id에 해당하는 질문을 조회하여 리턴한다.

Router

question_router.py 파일도 가장 아래에 다음과 같은 함수를 추가한다.

...
@router.get('/detail/{question_id}', response_model=question_schema.Question)
def question_detail(qeustion_id: int, db: Session = Depends(get_db)):
    question = question_crud.get_question(db, question_id=question_id)
    return question

question_detail 함수는 질문 상세 조회 API에 해당하는 함수로, URL을 통해 얻은 question_id 값으로 질문 상세 내역을 조회하여 Question 스키마로 리턴하는 함수이다.
앞서 App.svelte 파일을 수정할 때와 마찬가지로, '/detail/2'와 같은 URL 요청에서 2와 같이 가변적인 값을 얻으려면 라우터의 URL을 @router.get('/detail/{question_id}')와 같이 설정해야 한다.

Docs

이제 FastAPI의 docs 문서에서 새로 만든 질문 상세 조회 API를 테스트해 볼 수 있다. 아래와 같이 question_id에 2라는 값을 입력하고 실행했을 때 오류 없이 잘 수행된다면 성공이다.


4. 질문 상세 화면 완성하기

이제 질문 상세 API가 준비되었으므로 질문 상세 하면을 완성하자. Detail.svelte 파일을 열어 다음과 같이 수정하자.

<script>
    import fastapi from "../lib/api"

    export let params = {}
    let question_id = params.question_id
    console.log('question_id:' + question_id)  # 삭제
    let question = {}

    function get_question() {
        fastapi("get", "/api/question/detail/" + question_id, {}, (json) => {
            question = json
        })
    }

    get_question()
</script>

<h1>{question.subject}</h1>
<div>
    {question.content}
</div>

우선 fastapi 함수를 사용할 수 있도록 import하고 질문 상세 API를 호출하는 get_question 함수를 선언한다. 그리고 해당 함수의 출력값을 question 변수에 저장한다. 이때 question 변수는 질문 한 건에 대한 상세 정보이므로 {}로 초기화해야 한다.

이렇게 수정한 뒤 스벨트 페이지로 넘어가면 질문의 상세 내용이 잘 출력될 것이다.

profile
AI, NLP, Data analysis로 나아가고자 하는 개발자 지망생

0개의 댓글