プログラミング実践

【Flask ハンズオン】Python + HTML だけで最小の Web アプリを作る

記事内に商品プロモーションを含む場合があります

どうも、コンです。

Python は書けるけど、Web アプリを作ったことはない。Next.js や React は難しそうだし、JavaScript もよくわからない。そんな方に向けて、この記事では Python + HTML だけで Web アプリを作る方法を紹介します。

使うのは Flask と Jinja2。Flask は Python の Web フレームワークで、Jinja2 は HTML にデータを埋め込むためのテンプレートエンジンです。

この2つを組み合わせると、そこそこちゃんとしたWeb アプリが作れます。

今回のお題

今回は「一言メッセージボード」を題材に、4ステップで完成まで進めます。最終的に、名前とメッセージを投稿すると一覧に表示される、というシンプルなアプリです。

最終的に、こんなアプリができあがります。

  • 名前とメッセージを入力して「投稿」ボタンを押す
  • 投稿されたメッセージが下に一覧表示される
  • データは JSON ファイルに保存される(DB は今回触れないです。)

では、始めましょう。

環境構築

まず、プロジェクトを作ります。パッケージ管理には uv を使います。uv は Python のパッケージ管理ツールで、pip より高速で、仮想環境の作成もコマンド一つでやってくれます。

まだ uv をインストールしていない方は、以下を実行してください。

curl -LsSf https://astral.sh/uv/install.sh | sh

プロジェクトを作成して Flask をインストールします。

uv init flask-message-board
cd flask-message-board
uv add flask

これで仮想環境の作成と Flask のインストールが完了です。uv は自動で .venv/ を作ってくれるので、グローバルの Python 環境を汚す心配がありません。

Step 1:最小の Flask アプリ — 5行で「Hello, World」

まずは最小のコードで Flask を動かしてみます。app.py を以下の内容で作成してください。

from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
    return "Hello, World!"

ターミナルで以下を実行します。

uv run flask run --debug

ブラウザで http://127.0.0.1:5000 を開いてみてください。画面に「Hello, World!」と表示されれば成功です。

--debug をつけると、コードを変更するたびにサーバーが自動で再起動してくれます。開発中はつけっぱなしにしておくと便利です。

ここで起きていることを整理します。

  • Flask(__name__) で Flask のアプリを作成する
  • @app.route("/") で「/ にアクセスされたらこの関数を実行する」と指定する
  • 関数の return がブラウザに返す内容になる

普通の Python スクリプトとの違いは、return した値が自分のターミナルではなく、ブラウザに表示されるという点です。

Step 2:Jinja2 テンプレートで HTML を返す

Step 1 では文字列を直接返しましたが、実際の Web アプリでは HTML を返したいですよね。ここで登場するのが Jinja2 テンプレートです。

Jinja2 は Flask に同梱されているので、追加インストールは不要です。

まず、templates/ フォルダを作り、その中に index.html を作成します。

mkdir templates
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>メッセージボード</title>
</head>
<body>
    <h1>メッセージボード</h1>
    <p>ようこそ、{{ name }} さん!</p>
</body>
</html>

app.py を以下のように書き換えます。

from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index():
    return render_template("index.html", name="ゲスト")

ブラウザを更新すると、「ようこそ、ゲスト さん!」と表示されます。

ポイントは2つです。

  • render_template("index.html", name="ゲスト") で、テンプレートに変数を渡している
  • テンプレート側では {{ name }} と書くと、渡された値が埋め込まれる

{{ }} は「この中の変数を表示してね」という Jinja2 の記法です。

Step 3:フォームからメッセージを受け取る

次は、ブラウザから値を送れるようにします。HTML のフォームを使います。

templates/index.html を以下に書き換えてください。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>メッセージボード</title>
</head>
<body>
    <h1>メッセージボード</h1>
    <form method="POST">
        <label>名前:<input type="text" name="username" required></label>
        <br>
        <label>メッセージ:<input type="text" name="message" required></label>
        <br>
        <button type="submit">投稿</button>
    </form>
    {% if post_name %}
        <p>{{ post_name }} さんの投稿:{{ post_message }}</p>
    {% endif %}
</body>
</html>

app.py を以下に書き換えます。

from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/", methods=["GET", "POST"])
def index():
    post_name = None
    post_message = None
    if request.method == "POST":
        post_name = request.form["username"]
        post_message = request.form["message"]
    return render_template(
        "index.html",
        post_name=post_name,
        post_message=post_message,
    )

フォームに名前とメッセージを入力して「投稿」ボタンを押すと、画面に投稿内容が表示されます。

新しく出てきた要素を整理します。

  • methods=["GET", "POST"]:この URL で GET(ページ表示)と POST(データ送信)の両方を受け付ける
  • request.form["username"]:フォームから送られた値を取得する
  • {% if post_name %}:Jinja2 の条件分岐。投稿がないときは何も表示しない

{{ }} が「値を表示する」記法だったのに対して、{% %} は「制御構文(if / for など)を書く」記法です。この2つが Jinja2 の基本です。

Step 4:JSON ファイルに保存して一覧表示 — 完成

Step 3 の状態だと、ページを更新すると投稿が消えてしまいます。JSON ファイルに保存して、過去の投稿を一覧表示できるようにしましょう。

app.py を以下に書き換えます。

import json
from datetime import datetime
from pathlib import Path
from flask import Flask, render_template, request, redirect, url_for
app = Flask(__name__)
DATA_FILE = Path("messages.json")
def load_messages():
    if not DATA_FILE.exists():
        return []
    return json.loads(DATA_FILE.read_text(encoding="utf-8"))
def save_message(name, message):
    messages = load_messages()
    messages.append({
        "name": name,
        "message": message,
        "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M"),
    })
    DATA_FILE.write_text(
        json.dumps(messages, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )
@app.route("/")
def index():
    messages = load_messages()
    return render_template("index.html", messages=messages)
@app.route("/post", methods=["POST"])
def post():
    name = request.form["username"]
    message = request.form["message"]
    save_message(name, message)
    return redirect(url_for("index"))

templates/index.html を以下に書き換えます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>メッセージボード</title>
    <style>
        body { font-family: sans-serif; max-width: 600px; margin: 40px auto; padding: 0 20px; }
        .message { border-bottom: 1px solid #ddd; padding: 10px 0; }
        .message .meta { color: #888; font-size: 0.85em; }
        form { margin-bottom: 30px; }
        input { margin: 5px 0; padding: 5px; }
        button { margin-top: 8px; padding: 6px 16px; }
    </style>
</head>
<body>
    <h1>メッセージボード</h1>
    <form method="POST" action="/post">
        <label>名前:<input type="text" name="username" required></label>
        <br>
        <label>メッセージ:<input type="text" name="message" required></label>
        <br>
        <button type="submit">投稿</button>
    </form>
    <h2>投稿一覧</h2>
    {% for msg in messages | reverse %}
        <div class="message">
            <strong>{{ msg.name }}</strong>:{{ msg.message }}
            <div class="meta">{{ msg.timestamp }}</div>
        </div>
    {% else %}
        <p>まだ投稿がありません。</p>
    {% endfor %}
</body>
</html>

ブラウザを更新して、メッセージを投稿してみてください。投稿が一覧に追加され、ページを閉じて開き直しても消えません。

Step 3 からの変更点を整理します。

  • load_messages() / save_message():JSON ファイルの読み書き。Python の標準ライブラリだけで完結する
  • redirect(url_for("index")):投稿後にトップページにリダイレクト。これがないと、ブラウザの更新ボタンで二重投稿になる
  • {% for msg in messages | reverse %}:Jinja2 のループ。| reverse で新しい投稿が上に来るようにしている
  • {% else %}:ループの対象が空のときに表示される内容

これで完成です。ファイル構成は以下のようになっています。

flask-message-board/
├── app.py              # Flask アプリ本体
├── templates/
│   └── index.html      # HTML テンプレート
├── messages.json       # 投稿データ(自動生成)
└── pyproject.toml      # uv が管理するパッケージ情報

デプロイについて

ここまではローカル(自分の PC)で動かしましたが、友人やチームメンバーに触ってもらいたい場合は、サーバーにデプロイする必要があります。

Flask アプリのデプロイ先としては RenderFly.io が手軽だと思います。どちらも無料枠があり、GitHub リポジトリと連携すれば数クリックでデプロイできます。(多分)

本番環境では flask run の代わりに gunicorn を使うのが一般的です。

まとめ

この記事では、Flask + Jinja2 を使って Python + HTML だけで Web アプリを作りました。振り返ると、やったことは以下の4ステップです。

  1. app.py 5行で「Hello, World」を表示
  2. Jinja2 テンプレートで HTML を返す
  3. フォームからデータを受け取る
  4. JSON ファイルに保存して一覧表示

Python の基本的な文法と HTML、そして Flask の数個の関数だけで、ブラウザで動く Web アプリが作れました。

社内ツールや検証用のアプリなら、この構成で十分なケースは多いと思って紹介させてもらいました!

解析や分析で使用しているコードと合わせる本格的なPoCアプリの感じになると思われます。

「Web アプリを作ってみたいけど、JavaScriptとか他の言語を本格的に覚えるところから始めるのはしんどい」という方の、最初の一歩になれば嬉しいです。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA