どうも、コンです。
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 アプリのデプロイ先としては Render や Fly.io が手軽だと思います。どちらも無料枠があり、GitHub リポジトリと連携すれば数クリックでデプロイできます。(多分)
本番環境では flask run の代わりに gunicorn を使うのが一般的です。
まとめ
この記事では、Flask + Jinja2 を使って Python + HTML だけで Web アプリを作りました。振り返ると、やったことは以下の4ステップです。
app.py5行で「Hello, World」を表示- Jinja2 テンプレートで HTML を返す
- フォームからデータを受け取る
- JSON ファイルに保存して一覧表示
Python の基本的な文法と HTML、そして Flask の数個の関数だけで、ブラウザで動く Web アプリが作れました。
社内ツールや検証用のアプリなら、この構成で十分なケースは多いと思って紹介させてもらいました!
解析や分析で使用しているコードと合わせる本格的なPoCアプリの感じになると思われます。
「Web アプリを作ってみたいけど、JavaScriptとか他の言語を本格的に覚えるところから始めるのはしんどい」という方の、最初の一歩になれば嬉しいです。
