Skip to main content
Summary

라우팅은 웹 애플리케이션의 입구다

지난 장에서는 / 주소로 들어왔을 때 Hello, Flask!를 보여 주는 가장 작은 Flask 앱을 만들었다. 이제 한 걸음 더 나아가 보자. 실제 웹사이트에는 홈 화면만 있지 않다. 소개 페이지도 있고, 게시글 목록도 있고, 특정 게시글을 보여 주는 상세 페이지도 있다.

이때 필요한 것이 라우팅이다. 라우팅은 "이 주소로 요청이 오면 이 함수를 실행하라"는 연결표를 만드는 일이다. 건물 입구에 안내 표지판을 붙이는 것과 비슷하다. /about으로 오면 소개 페이지로, /posts/1로 오면 1번 게시글로 보내는 식이다.

Flask에서는 이 연결을 @app.route()로 만든다. 공식 문서에서도 @app.route()는 URL 규칙을 뷰 함수에 연결하는 방식으로 설명한다.1 여기서 뷰 함수는 요청을 처리하고 응답을 돌려주는 파이썬 함수다.

라우팅으로 URL과 함수를 연결하기AI 생성 이미지

첫 번째 라우트 다시 보기

지난 장에서 만든 app.py는 대략 이런 모양이었다.

from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return "Hello, Flask!"

한 줄씩 역할을 보자.

from flask import Flask

Flask 애플리케이션을 만들기 위해 Flask 클래스를 가져온다.

app = Flask(__name__)

Flask 앱 객체를 만든다. 앞으로 URL을 등록할 때 이 app 객체에 연결한다.

@app.route("/")

/ 주소로 요청이 들어오면 바로 아래 함수를 실행하라고 Flask에 알려 준다. /는 사이트의 가장 기본 주소, 즉 홈 화면을 뜻한다.

def home():
    return "Hello, Flask!"

home() 함수는 문자열을 반환한다. Flask는 이 문자열을 HTTP 응답으로 바꾸어 브라우저에 보낸다.

중요한 점은 함수 이름이 주소가 되는 것이 아니라는 점이다. 주소는 @app.route("/") 안에 적은 값으로 정해진다. 함수 이름은 Flask 내부에서 이 라우트를 구분하는 이름으로 쓰인다. 뒤에서 배울 url_for()가 바로 이 함수 이름을 사용한다.

여러 URL 만들기

이제 홈 화면, 소개 페이지, 게시글 목록 페이지를 만들어 보자. app.py를 다음처럼 수정한다.

from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return """
    <h1>Flask 메모장</h1>
    <p>짧은 글을 남기는 작은 웹 애플리케이션입니다.</p>
    <ul>
        <li><a href="/about">소개</a></li>
        <li><a href="/posts">게시글 목록</a></li>
    </ul>
    """

@app.route("/about")
def about():
    return """
    <h1>소개</h1>
    <p>이 앱은 Flask 라우팅을 연습하기 위해 만든 예제입니다.</p>
    <p><a href="/">홈으로</a></p>
    """

@app.route("/posts")
def post_list():
    return """
    <h1>게시글 목록</h1>
    <ul>
        <li><a href="/posts/1">1번 게시글</a></li>
        <li><a href="/posts/2">2번 게시글</a></li>
        <li><a href="/posts/3">3번 게시글</a></li>
    </ul>
    <p><a href="/">홈으로</a></p>
    """

실행은 지난 장과 같다.

flask --app app --debug run

브라우저에서 다음 주소를 열어 보자.

  • http://127.0.0.1:5000/

  • http://127.0.0.1:5000/about

  • http://127.0.0.1:5000/posts

각 URL마다 다른 함수가 실행되고, 다른 응답이 표시된다. 이것이 라우팅의 기본이다.

URL을 나누면 기능도 나누기 쉬워진다

라우트는 단순히 주소를 예쁘게 정리하는 기능이 아니다. 웹 애플리케이션의 기능을 나누는 기준이 된다.

예를 들어 지금 만든 앱은 다음처럼 나뉜다.

URL실행되는 함수역할
/home()홈 화면
/aboutabout()앱 소개
/postspost_list()게시글 목록

이 표를 보면 앱의 구조가 한눈에 보인다. 초보자일수록 처음부터 복잡한 기술을 많이 쓰기보다, URL과 함수의 관계를 명확히 잡는 것이 중요하다. 라우팅은 모든 웹 기능의 출발점이다.

다만 지금 코드에는 아쉬운 점이 있다. 게시글 상세 페이지 주소가 /posts/1, /posts/2, /posts/3처럼 반복된다. 게시글이 100개라면 함수를 100개 만들어야 할까? 그렇지 않다. 이때 동적 라우팅을 사용한다.

동적 라우팅으로 URL 일부를 변수처럼 받기

동적 라우팅은 URL의 일부를 변수처럼 받는 방법이다. 예를 들어 /posts/1에서 1만 바뀌고, /posts/2에서 2만 바뀐다면 이 숫자를 함수의 인자로 받을 수 있다.

app.py 아래쪽에 다음 코드를 추가한다.

@app.route("/posts/<int:post_id>")
def post_detail(post_id):
    return f"""
    <h1>{post_id}번 게시글</h1>
    <p>이 페이지는 {post_id}번 게시글의 상세 화면입니다.</p>
    <p><a href="/posts">목록으로</a></p>
    """

핵심은 이 부분이다.

@app.route("/posts/<int:post_id>")

<int:post_id>는 URL의 해당 부분을 숫자로 받아 post_id라는 이름의 변수로 전달하라는 뜻이다. 그래서 /posts/1로 접속하면 post_detail(post_id=1)처럼 실행된다. /posts/2로 접속하면 post_detail(post_id=2)처럼 실행된다.

여기서 int는 타입 변환기다. 타입 변환기는 URL에서 받은 값을 어떤 형태로 받을지 정한다. int를 사용하면 정수 형태의 숫자만 허용한다. 그래서 /posts/abc처럼 숫자가 아닌 주소로 접속하면 이 라우트와 맞지 않아 404 Not Found가 나온다. 이것은 오류라기보다 잘못된 주소를 걸러 주는 정상적인 동작이다.

이제 브라우저에서 확인해 보자.

  • http://127.0.0.1:5000/posts/1

  • http://127.0.0.1:5000/posts/2

  • http://127.0.0.1:5000/posts/abc

마지막 주소는 404가 나와야 한다. Flask가 "이 주소에 맞는 라우트를 찾지 못했다"고 알려 주는 것이다.

문자열, 숫자, 경로 타입 변환기

Flask의 동적 라우팅은 여러 타입 변환기를 제공한다. 처음에는 다음 세 가지만 알아도 충분하다.

변환기예시의미
string/users/<username>슬래시(/)를 제외한 문자열을 받는다. 기본값이다.
int/posts/<int:post_id>정수 형태의 숫자를 받아 파이썬 정수로 바꾼다.
path/files/<path:file_path>슬래시(/)가 포함된 경로 형태의 문자열도 받는다.

문자열 변환기부터 보자.

@app.route("/users/<username>")
def user_profile(username):
    return f"<h1>{username}님의 프로필</h1>"

/users/mina로 접속하면 username에는 "mina"가 들어간다. /users/jun으로 접속하면 username에는 "jun"이 들어간다. 여기서 <username>은 사실 <string:username>을 줄여 쓴 형태다. string이 기본값이기 때문이다.

다음은 int 변환기다.

@app.route("/products/<int:product_id>")
def product_detail(product_id):
    return f"<h1>{product_id}번 상품</h1>"

/products/10으로 접속하면 product_id에는 문자열 "10"이 아니라 정수 10이 들어간다. 그래서 함수 안에서 숫자 비교나 계산을 해야 할 때 편하다.

마지막으로 path 변환기를 보자.

@app.route("/files/<path:file_path>")
def show_file(file_path):
    return f"<p>요청한 파일 경로: {file_path}</p>"

/files/images/cat.png로 접속하면 file_path에는 "images/cat.png"가 들어간다. 일반 문자열 변환기인 string은 중간에 슬래시(/)가 들어가면 거기서 URL 구간이 나뉘었다고 보기 때문에 받을 수 없다. 여러 단계의 경로를 한 번에 받고 싶을 때 path를 사용한다.

다만 실제 서비스에서 사용자가 입력한 파일 경로를 그대로 믿고 파일을 열어 주면 보안 문제가 생길 수 있다. 지금은 라우팅 문법을 이해하기 위한 예제로만 봐 두자.

라우트가 많아질 때 읽기 좋은 코드로 정리하기

지금까지 만든 예제를 모두 합치면 app.py는 다음과 비슷해진다.

from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return """
    <h1>Flask 메모장</h1>
    <p>짧은 글을 남기는 작은 웹 애플리케이션입니다.</p>
    <ul>
        <li><a href="/about">소개</a></li>
        <li><a href="/posts">게시글 목록</a></li>
    </ul>
    """

@app.route("/about")
def about():
    return """
    <h1>소개</h1>
    <p>이 앱은 Flask 라우팅을 연습하기 위해 만든 예제입니다.</p>
    <p><a href="/">홈으로</a></p>
    """

@app.route("/posts")
def post_list():
    return """
    <h1>게시글 목록</h1>
    <ul>
        <li><a href="/posts/1">1번 게시글</a></li>
        <li><a href="/posts/2">2번 게시글</a></li>
        <li><a href="/posts/3">3번 게시글</a></li>
    </ul>
    <p><a href="/">홈으로</a></p>
    """

@app.route("/posts/<int:post_id>")
def post_detail(post_id):
    return f"""
    <h1>{post_id}번 게시글</h1>
    <p>이 페이지는 {post_id}번 게시글의 상세 화면입니다.</p>
    <p><a href="/posts">목록으로</a></p>
    """

@app.route("/users/<username>")
def user_profile(username):
    return f"<h1>{username}님의 프로필</h1>"

코드가 길어지기 시작했지만 아직은 괜찮다. 초보 단계에서는 "주소 하나에 함수 하나"라는 감각을 익히는 것이 먼저다. 나중에 앱이 커지면 템플릿으로 HTML을 분리하고, 블루프린트로 기능별 파일을 나누게 된다. 하지만 그때도 기본 생각은 변하지 않는다. 사용자가 어떤 URL로 들어오는지 보고, 그 URL을 처리할 함수를 정하는 것이 Flask 웹 애플리케이션의 출발점이다.

404 Not Found는 무조건 나쁜 오류가 아니다

라우팅을 연습하다 보면 404 Not Found 화면을 자주 만나게 된다. 초보자 입장에서는 빨간 오류처럼 느껴질 수 있지만, 404는 서버가 완전히 망가졌다는 뜻이 아니다. "요청한 주소에 해당하는 라우트를 찾지 못했다"는 뜻이다.

예를 들어 다음과 같은 경우 404가 나올 수 있다.

  • @app.route("/about")만 만들었는데 /About처럼 대문자로 접속한 경우

  • @app.route("/posts/<int:post_id>")를 만들었는데 /posts/abc처럼 숫자가 아닌 값을 넣은 경우

  • 코드에는 /posts만 있는데 브라우저에서 /post처럼 다른 주소로 접속한 경우

Flask의 라우트는 기본적으로 대소문자를 구분한다. 따라서 /about과 /About은 같은 주소가 아니다. 404가 나오면 먼저 내가 브라우저에 입력한 주소와 @app.route() 안에 적은 주소가 정확히 같은지 확인해 보자.

직접 해 보기

마지막으로 작은 연습을 해 보자. 지금 만든 app.py에 다음 라우트를 추가해 보라.

  1. /contact 주소로 접속하면 연락처 안내 문구를 보여 주기

  2. /hello/<name> 주소로 접속하면 name에 들어온 이름을 사용해 인사하기

  3. /posts/<int:post_id>에서 post_id가 1이면 "첫 번째 게시글입니다."라는 문구를, 그 외 숫자이면 "아직 내용을 작성하지 않았습니다."라는 문구를 보여 주기

예를 들면 세 번째 연습은 이렇게 바꿔 볼 수 있다.

@app.route("/posts/<int:post_id>")
def post_detail(post_id):
    if post_id == 1:
        message = "첫 번째 게시글입니다."
    else:
        message = "아직 내용을 작성하지 않았습니다."

    return f"""
    <h1>{post_id}번 게시글</h1>
    <p>{message}</p>
    <p><a href="/posts">목록으로</a></p>
    """

여기서 중요한 것은 HTML을 멋지게 꾸미는 것이 아니다. 어떤 URL이 어떤 함수로 연결되는지, 그리고 URL 안의 값이 함수 인자로 어떻게 들어오는지를 손으로 확인하는 것이다.

정리

이번 장에서는 Flask 라우팅의 기본을 살펴보았다.

  • 라우팅은 URL 요청을 특정 뷰 함수에 연결하는 일이다.

  • Flask에서는 @app.route() 데코레이터로 라우트를 등록한다.

  • /, /about, /posts처럼 URL을 나누면 앱의 기능 구조가 더 잘 보인다.

  • /posts/<int:post_id>처럼 동적 라우팅을 사용하면 URL의 일부를 함수 인자로 받을 수 있다.

  • string, int, path 같은 타입 변환기로 URL에서 받을 값의 형태를 정할 수 있다.

  • 라우트와 맞지 않는 주소로 접속하면 Flask는 404 Not Found를 보여 준다.

라우팅은 Flask 앱의 입구다. 다음 단계에서 요청 데이터, 템플릿, 데이터베이스를 배우더라도 결국 사용자는 URL을 통해 들어오고, Flask는 그 URL에 맞는 함수를 실행한다. 이 감각을 잡아 두면 앞으로 배우는 내용이 훨씬 자연스럽게 이어질 것이다.

참고

1Flask Documentation, "Quickstart: Routing", https://flask.palletsprojects.com/en/stable/quickstart/#routing

책 목록

초보자를 위한 파이썬 Flask 학습

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기