Jinja2 템플릿: 데이터를 HTML로

Flask와 Django가 쓰는 템플릿 엔진 Jinja2. 변수 출력과 제어 구문, 필터, 그리고 중복을 없애는 템플릿 상속과 자동 이스케이프까지 서버가 HTML을 그려 내는 방법을 정리합니다.

· 6 min read · PALDYN Team

지난 글에서 서버가 어떻게 요청을 받아 앱에 넘기는지 그 토대를 봤다면, 이번엔 앱이 만든 데이터를 사람이 볼 화면, 즉 HTML로 그려 내는 단계를 다룬다. 파이썬 문자열을 +로 이어 붙여 HTML을 만들면 금세 지저분해지고 실수가 늘어난다. 그래서 쓰는 것이 템플릿 엔진이다. Python 진영의 사실상 표준은 Jinja2이고, Flask는 기본 내장, Django의 기본 엔진도 Jinja2와 거의 같은 문법을 쓴다.

두 가지 구분 기호

Jinja2 문법은 딱 두 종류만 기억하면 된다. 값을 출력할 때는 {{ }}, 로직을 실행할 때는 {% %}다.

템플릿 + 데이터 → HTML

<h1>{{ name }}님의 장바구니</h1>

{% if items %}
  <ul>
  {% for item in items %}
    <li>{{ item.title }} — {{ item.price }}원</li>
  {% endfor %}
  </ul>
{% else %}
  <p>장바구니가 비어 있습니다.</p>
{% endif %}

{{ name }}은 넘겨받은 데이터에서 name 값을 꺼내 그 자리에 박는다. {% for %}, {% if %}는 파이썬과 비슷한 제어 구문이지만, 반드시 {% endfor %}, {% endif %}로 블록을 닫아 줘야 한다는 점이 다르다.

템플릿에 데이터 넘기기

템플릿은 혼자서는 빈 양식일 뿐이고, 여기에 채울 데이터(context)를 파이썬 쪽에서 건네야 한다. Flask에서는 render_template을 쓴다.

from flask import render_template

@app.route("/cart")
def cart():
    items = [{"title": "ORM 입문", "price": 18000}]
    return render_template("cart.html", name="PALDYN", items=items)

키워드 인자로 넘긴 name, items가 템플릿 안의 {{ name }}, {% for item in items %}에 그대로 대응된다. 렌더링은 양식(템플릿)과 데이터(context)를 합쳐 완성된 HTML 문자열을 만드는 과정이다.

필터로 값 다듬기

출력하는 값에 파이프(|)로 필터를 붙이면 형식을 손쉽게 바꾼다. 대문자 변환, 기본값 지정, 길이 등 자주 쓰는 필터가 내장되어 있다.

<p>{{ name | upper }}</p>            <!-- 대문자로 -->
<p>{{ price | default(0) }}원</p>     <!-- 값 없으면 0 -->
<p>항목 {{ items | length }}개</p>    <!-- 개수 -->

필터는 왼쪽 값을 받아 가공한 결과를 돌려준다. 여러 개를 연달아 이어 붙일 수도 있어서, 표시용 가공 로직을 템플릿 안에 깔끔하게 둘 수 있다.

상속으로 중복 없애기

여러 페이지가 같은 헤더·푸터·메뉴를 공유한다면, 그것을 매번 복사하는 대신 공통 뼈대 템플릿을 하나 두고 페이지마다 달라지는 부분만 채운다. 이것이 Jinja2의 가장 강력한 기능인 템플릿 상속이다.

템플릿 상속: 공통 뼈대 + 빈칸

<!-- base.html -->
<header>로고와 메뉴</header>
{% block content %}{% endblock %}
<footer>공통 푸터</footer>
<!-- cart.html -->
{% extends "base.html" %}
{% block content %}
  <h1>장바구니</h1>
{% endblock %}

base.html에 사이트 공통 구조와 {% block content %}라는 빈칸을 만들어 두고, 각 페이지는 {% extends %}로 그 뼈대를 물려받아 자기 block만 채운다. 헤더와 푸터를 한 곳에서만 관리하면 되니, 디자인이 바뀌어도 base 하나만 고치면 모든 페이지에 반영된다.

자동 이스케이프는 켜 두자

Jinja2는 기본적으로 {{ }}로 출력되는 값의 특수문자를 HTML 엔티티로 바꾼다. 사용자가 입력한 <script>가 코드로 실행되지 않고 글자로 표시되게 만들어, XSS 공격을 막는 안전장치다. 정말 신뢰할 수 있는 HTML만 | safe 필터로 예외 처리하고, 그 외에는 자동 이스케이프를 끄지 않는 것이 원칙이다.

템플릿 엔진의 핵심은 “구조는 양식에, 값은 데이터에” 분리하는 것이다. 출력엔 {{ }}, 로직엔 {% %}, 그리고 상속으로 중복을 없애면 화면 코드가 한결 단정해진다. 다음 글에서는 다시 클라이언트 쪽으로 시선을 돌려, 다른 서버에 HTTP 요청을 보내는 두 라이브러리 requests와 httpx를 비교해 본다.


지난 글: uvicorn과 ASGI: 비동기 서버의 토대

다음 글: requests vs httpx: HTTP 클라이언트 고르기


읽어주셔서 감사합니다. 😊