Flaskルーティングとビュー関数

2025-12-03

はじめに

前回の記事でFlask開発環境の構築を完了したら、次はWebアプリケーションの核心である「ルーティング」と「ビュー関数」について学びましょう。ルーティングは、ユーザーがアクセスしたURLに応じてどの処理を実行するかを決定する仕組みです。この記事では、Flaskにおけるルーティングの基本から応用まで、詳細に解説していきます。

ルーティングの基本概念

ルーティングとは?

ルーティングとは、特定のURLパスにアクセスされたときに実行する関数(ビュー関数)を関連付けるプロセスです。例えば以下のようなものになります。

  • https://example.com/ → ホームページを表示
  • https://example.com/about → 会社情報ページを表示
  • https://example.com/users/123 → ユーザーID123のページを表示

ビュー関数とは?

ビュー関数は、特定のルートにアクセスがあったときに実行されるPython関数です。この関数はリクエストを処理し、レスポンス(通常はHTML)を返します。

基本的なルートの定義

最小限のFlaskアプリケーション

まずは前回学んだ基本的なアプリケーションを振り返りましょう。

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'ホームページ'

if __name__ == '__main__':
    app.run(debug=True)

複数ルートの追加

1つのアプリケーションに複数のルートを定義してみましょう。

from flask import Flask

app = Flask(__name__)

# ホームページ
@app.route('/')
def index():
    return '''
    <h1>ようこそ!</h1>
    <p>これはホームページです。</p>
    <ul>
        <li><a href="/about">会社情報</a></li>
        <li><a href="/contact">お問い合わせ</a></li>
        <li><a href="/user/john">Johnのプロフィール</a></li>
    </ul>
    '''

# 会社情報ページ
@app.route('/about')
def about():
    return '''
    <h1>会社情報</h1>
    <p>当社は2020年に設立されたテクノロジー企業です。</p>
    <a href="/">ホームに戻る</a>
    '''

# お問い合わせページ
@app.route('/contact')
def contact():
    return '''
    <h1>お問い合わせ</h1>
    <p>お問い合わせは以下のメールアドレスまで:</p>
    <p>contact@example.com</p>
    <a href="/">ホームに戻る</a>
    '''

if __name__ == '__main__':
    app.run(debug=True)

ルート定義の詳細解説

@app.route() デコレータ

@app.route() はPythonのFlaskウェブフレームワークで使用されるデコレータで、URLパスと関数をマッピングする役割を持ちます。具体的には、特定のURLへのHTTPリクエストを処理する関数を指定します。

  • デコレータ: Pythonの機能で、関数の前に@記号で記述します
  • 役割: 関数をWebのルートに関連付けます
  • パラメータ: URLパスを文字列で指定します
@app.route('/path')
def function_name():
    return 'レスポンス'

関数名の規則

  • 関数名は自由ですが、分かりやすい名前をつけましょう
  • Pythonの関数命名規則に従います(小文字、アンダースコア)
  • 例: user_profile(), show_posts(), handle_login()

HTMLを返すより洗練された方法

文字列でHTMLを書くのは大変なので、複数行文字列を使いましょう。

@app.route('/nice-page')
def nice_page():
    html_content = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>素敵なページ</title>
        <style>
            body { font-family: Arial, sans-serif; margin: 40px; }
            .container { max-width: 800px; margin: 0 auto; }
            .button { background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; }
        </style>
    </head>
    <body>
        <div class="container">
            <h1>ようこそ!</h1>
            <p>これはスタイリングされたページです。</p>
            <a href="/" class="button">ホームに戻る</a>
        </div>
    </body>
    </html>
    """
    return html_content

動的URLパラメータ

動的ルートとは?

URLの一部を変数として扱うことができる機能です。例えば、ユーザーIDや商品IDなど、同じパターンだが異なる内容を表示したい場合に使用します。

基本的な動的パラメータ

Flaskの動的パラメータとは、URLの一部を変数として受け取り、処理に利用できる仕組みのことです。
例えば /user/<username> のように書くと、アクセス時の username の値をPython側で取得して、そのユーザー専用ページを表示するなど、柔軟なルーティングが可能になります。

from flask import Flask

app = Flask(__name__)

# ユーザープロフィール - 文字列パラメータ
@app.route('/user/<username>')
def show_user_profile(username):
    return f'''
    <h1>ユーザープロフィール</h1>
    <p>ユーザー名: {username}</p>
    <a href="/">ホームに戻る</a>
    '''

様々な型の動的パラメータ

Flaskはいくつかの型の動的パラメータをサポートしています。次のコードは、URL の一部を動的パラメータ <username> として受け取り、その値に応じてユーザー名を表示するプロフィールページを返す仕組みを持つ Flask のルーティング処理を示しています。

from flask import Flask

app = Flask(__name__)

# ユーザープロフィール - 文字列パラメータ
@app.route('/user/<username>')
def show_user_profile(username):
    return f'''
    <h1>ユーザープロフィール</h1>
    <p>ユーザー名: {username}</p>
    <a href="/">ホームに戻る</a>
    '''

型コンバータの種類

説明
string任意のテキスト(デフォルト)<name>
int正の整数<int:id>
float正の浮動小数点数<float:amount>
pathスラッシュを含む文字列<path:directory>
uuidUUID文字列<uuid:token>

実践的な例:ブログアプリケーション

投稿ページユーザーページ を動的に表示する簡易ブログアプリを構成しており、ID やユーザー名を URL パラメータとして受け取って内容を切り替える仕組みが特徴です。投稿やユーザー情報は辞書で管理され、存在しない場合は 404 エラーページを返すようになっています。

from flask import Flask

app = Flask(__name__)

# 仮のデータベース代わり
posts = {
    1: {"title": "最初の投稿", "content": "これは最初の投稿です。"},
    2: {"title": "二番目の投稿", "content": "Flaskの学習を進めています。"},
    3: {"title": "ルーティングについて", "content": "動的ルートは便利です。"}
}

users = {
    "taro": "田中太郎 - Web開発者",
    "hanako": "佐藤花子 - デザイナー",
    "jiro": "鈴木次郎 - システムエンジニア"
}

@app.route('/')
def index():
    return '''
    <h1>マイブログ</h1>
    <h2>投稿一覧</h2>
    <ul>
        <li><a href="/post/1">最初の投稿</a></li>
        <li><a href="/post/2">二番目の投稿</a></li>
        <li><a href="/post/3">ルーティングについて</a></li>
    </ul>
    <h2>ユーザー一覧</h2>
    <ul>
        <li><a href="/user/taro">田中太郎</a></li>
        <li><a href="/user/hanako">佐藤花子</a></li>
        <li><a href="/user/jiro">鈴木次郎</a></li>
    </ul>
    '''

@app.route('/post/')
def show_post(post_id):
    post = posts.get(post_id)
    if post:
        return f'''
        <h1>{post['title']}</h1>
        <p>{post['content']}</p>
        <p><small>投稿ID: {post_id}</small></p>
        <a href="/">ホームに戻る</a>
        '''
    else:
        return f'''
        <h1>エラー</h1>
        <p>投稿 #{post_id} は見つかりませんでした。</p>
        <a href="/">ホームに戻る</a>
        ''', 404

@app.route('/user/')
def show_user(username):
    user_info = users.get(username)
    if user_info:
        return f'''
        <h1>ユーザー情報</h1>
        <h2>{username}</h2>
        <p>{user_info}</p>
        <a href="/">ホームに戻る</a>
        '''
    else:
        return f'''
        <h1>エラー</h1>
        <p>ユーザー '{username}' は見つかりませんでした。</p>
        <a href="/">ホームに戻る</a>
        ''', 404

if __name__ == '__main__':
    app.run(debug=True)

HTTPメソッドの扱い(GET/POST)

HTTPメソッドとは?

HTTPメソッドは、クライアントがサーバーに何をしたいかを伝える方法です。主なメソッドは以下です。

  • GET: データの取得(ページ表示など)
  • POST: データの送信(フォーム送信など)
  • PUT: データの更新
  • DELETE: データの削除

デフォルトの動作

@app.route()でメソッドを指定しない場合、デフォルトでGETメソッドのみを受け付けます。

# これはGETリクエストのみを受け付ける
@app.route('/page')
def page():
    return 'これはGETリクエストでアクセスされました'

複数メソッドの許可

同じ URL に対して GET と POST の両方を処理するログイン機能を実装した例です。GET ではログインフォームを表示し、POST ではフォーム送信後の処理を行う構造になっています。

from flask import Flask, request

app = Flask(__name__)

# GETとPOSTの両方を受け付ける
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        # POSTリクエストの処理
        return '''
        <h1>ログイン成功!</h1>
        <p>POSTリクエストを受け付けました</p>
        <a href="/login">戻る</a>
        '''
    else:
        # GETリクエストの処理(フォーム表示)
        return '''
        <h1>ログイン</h1>
        <form method="POST">
            <p>ユーザー名: <input type="text" name="username"></p>
            <p>パスワード: <input type="password" name="password"></p>
            <p><input type="submit" value="ログイン"></p>
        </form>
        <a href="/">ホームに戻る</a>
        '''

実践的なコンタクトフォーム

お問い合わせフォームの送信と内容表示を行う処理を実装したものです。GET では入力フォームを表示し、POST では送信された名前・メールアドレス・メッセージを受け取り、確認画面として整形して表示する仕組みになっています。

from flask import Flask, request

app = Flask(__name__)

@app.route('/contact', methods=['GET', 'POST'])
def contact():
    if request.method == 'POST':
        # フォームデータの取得
        name = request.form.get('name', '未入力')
        email = request.form.get('email', '未入力')
        message = request.form.get('message', '未入力')

        # ここでデータを処理(データベース保存、メール送信など)

        return f'''
        <h1>お問い合わせありがとうございます!</h1>
        <div style="background-color: #f0f0f0; padding: 20px; border-radius: 5px;">
            <h2>送信内容:</h2>
            <p><strong>お名前:</strong> {name}</p>
            <p><strong>メールアドレス:</strong> {email}</p>
            <p><strong>メッセージ:</strong> {message}</p>
        </div>
        <p><a href="/contact">別のメッセージを送信</a></p>
        <a href="/">ホームに戻る</a>
        '''
    else:
        # GETリクエスト - フォームを表示
        return '''
        <!DOCTYPE html>
        <html>
        <head>
            <title>お問い合わせ</title>
            <style>
                body { font-family: Arial, sans-serif; margin: 40px; }
                .container { max-width: 600px; margin: 0 auto; }
                .form-group { margin-bottom: 20px; }
                label { display: block; margin-bottom: 5px; font-weight: bold; }
                input[type="text"], input[type="email"], textarea { 
                    width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; 
                }
                textarea { height: 150px; }
                input[type="submit"] { 
                    background-color: #007bff; color: white; padding: 10px 20px; 
                    border: none; border-radius: 4px; cursor: pointer; 
                }
            </style>
        </head>
        <body>
            <div class="container">
                <h1>お問い合わせ</h1>
                <form method="POST">
                    <div class="form-group">
                        <label for="name">お名前:</label>
                        <input type="text" id="name" name="name" required>
                    </div>
                    <div class="form-group">
                        <label for="email">メールアドレス:</label>
                        <input type="email" id="email" name="email" required>
                    </div>
                    <div class="form-group">
                        <label for="message">メッセージ:</label>
                        <textarea id="message" name="message" required></textarea>
                    </div>
                    <div class="form-group">
                        <input type="submit" value="送信">
                    </div>
                </form>
                <p><a href="/">ホームに戻る</a></p>
            </div>
        </body>
        </html>
        '''

すべてのメソッドを受け付ける

GET・POST・PUT・DELETE など複数のHTTPメソッドに対応する単一のAPIエンドポイントを実装した例であり、アクセス時に使用されたメソッド名と現在時刻を表示するシンプルな確認用 API になっています。

@app.route('/api', methods=['GET', 'POST', 'PUT', 'DELETE'])
def handle_api():
    method = request.method
    return f'''
    <h1>APIエンドポイント</h1>
    <p>使用されたHTTPメソッド: {method}</p>
    <p>現在の時刻: {datetime.now()}</p>
    <a href="/">ホームに戻る</a>
    '''

高度なルーティングテクニック

デフォルトパラメータ

同じ関数に複数のルートを割り当て、ページ番号付き・なしの両方の URL に対応できるようにしたページネーションの例です。ページ番号が指定されない場合は 1 をデフォルトとして書籍一覧ページを表示します。

@app.route('/books', defaults={'page': 1})
@app.route('/books/page/<int:page>')
def show_books(page):
    return f'''
    <h1>書籍一覧</h1>
    <p>現在のページ: {page}</p>
    <a href="/">ホームに戻る</a>
    '''

複数のルートで同じ関数を使用

1つの関数に複数のURLパス(/、/home、/index)を紐づけることで、同じホームページに異なるURLからアクセスできるようにした Flask のルーティング例です。

@app.route('/')
@app.route('/home')
@app.route('/index')
def home():
    return '''
    <h1>ホームページ</h1>
    <p>このページには複数のURLからアクセスできます:</p>
    <ul>
        <li><a href="/">/</a></li>
        <li><a href="/home">/home</a></li>
        <li><a href="/index">/index</a></li>
    </ul>
    '''

URLの生成(url_for)

url_for() を使ってルートに依存しない安全な URL を動的生成する例であり、関数名とパラメータからリンクを自動構築することで、URL変更にも強い柔軟なナビゲーションを実現しています。

from flask import Flask, url_for

app = Flask(__name__)

@app.route('/')
def index():
    return f'''
    <h1>URL生成の例</h1>
    <p>以下のリンクはurl_for関数で生成されています:</p>
    <ul>
        <li><a href="{url_for('about')}">会社情報</a></li>
        <li><a href="{url_for('user_profile', username='taro')}">Taroのプロフィール</a></li>
        <li><a href="{url_for('show_post', post_id=1)}">投稿1</a></li>
    </ul>
    '''

@app.route('/about')
def about():
    return '<h1>会社情報</h1><a href="/">ホームに戻る</a>'

@app.route('/user/<username>')
def user_profile(username):
    return f'<h1>ユーザー: {username}</h1><a href="/">ホームに戻る</a>'

@app.route('/post/<int:post_id>')
def show_post(post_id):
    return f'<h1>投稿 #{post_id}</h1><a href="/">ホームに戻る</a>'

カスタムエラーページ

エラーハンドラー機能を使って 404(未検出)と 500(サーバーエラー)の専用ページを返す処理を定義しており、発生したエラーに応じてカスタムメッセージとホームリンクを表示する仕組みになっています。

@app.errorhandler(404)
def page_not_found(error):
    return '''
    <h1>ページが見つかりません</h1>
    <p>お探しのページは存在しないか、移動した可能性があります。</p>
    <a href="/">ホームに戻る</a>
    ''', 404

@app.errorhandler(500)
def internal_server_error(error):
    return '''
    <h1>サーバーエラー</h1>
    <p>申し訳ございません。サーバーで問題が発生しました。</p>
    <a href="/">ホームに戻る</a>
    ''', 500

ベストプラクティス

1. 一貫した命名規則

REST風の一貫した命名規則の良い例と悪い例 を示しています。

  • 良い例は /users を基本に一覧、詳細、作成の URL を統一的に設計しており、直感的で拡張しやすい構造です。
  • 悪い例は命名やパスに統一感がなく、可読性やメンテナンス性が低い構造になっています。
# 良い例
@app.route('/users')
def list_users():
    pass

@app.route('/users/')
def show_user(user_id):
    pass

@app.route('/users/create', methods=['GET', 'POST'])
def create_user():
    pass

# 悪い例(一貫性がない)
@app.route('/getUsers')
def get_users():
    pass

@app.route('/user_info/')
def user_info(id):
    pass

2. RESTfulな設計

RESTful API の設計を意識したユーザーリソース向けルート定義の例です。HTTP メソッドごとに同じ URL パスでも処理を分けることで、一覧取得(GET)、作成(POST)、詳細取得(GET)、更新(PUT)、削除(DELETE)を明確に分離しています。

# ユーザーリソースに対するRESTfulなルート
@app.route('/users', methods=['GET'])          # ユーザー一覧
@app.route('/users', methods=['POST'])         # ユーザー作成
@app.route('/users/', methods=['GET'])     # ユーザー詳細
@app.route('/users/', methods=['PUT'])     # ユーザー更新
@app.route('/users/', methods=['DELETE'])  # ユーザー削除

3. ルートの整理

アプリケーションが大きくなってきたら、ブループリントを使用してルートを整理しましょう(これは後の記事で詳しく説明します)。

実践プロジェクト:シンプルなタスク管理アプリ

これまで学んだことをすべて組み合わせた実践的な例です。

  • / でタスク一覧と追加フォームを表示
  • /add で POST による新しいタスクの追加
  • /delete/<int:task_id> で個別タスクの削除
  • /clear ですべてのタスクを削除

といった基本的な CRUD 操作を URL と HTTP メソッドで実装しており、redirecturl_for を使って処理後にトップページに戻るようになっています。

from flask import Flask, request, redirect, url_for

app = Flask(__name__)

# 簡単なデータストア(本来はデータベースを使用します)
tasks = []
task_id_counter = 1

@app.route('/')
def index():
    task_list = ''.join([f'<li>{task["id"]}: {task["title"]} <a href="/delete/{task["id"]}">[削除]</a></li>' 
                        for task in tasks])
    return f'''
    <h1>タスク管理アプリ</h1>

    <h2>新しいタスクを追加</h2>
    <form method="POST" action="/add">
        <input type="text" name="title" placeholder="タスクを入力" required>
        <input type="submit" value="追加">
    </form>

    <h2>タスク一覧</h2>
    { '<ul>' + task_list + '</ul>' if tasks else '<p>タスクがありません</p>' }

    <h2>操作</h2>
    <p><a href="/clear">すべてのタスクを削除</a></p>
    '''

@app.route('/add', methods=['POST'])
def add_task():
    global task_id_counter
    title = request.form.get('title')
    if title:
        tasks.append({'id': task_id_counter, 'title': title})
        task_id_counter += 1
    return redirect(url_for('index'))

@app.route('/delete/')
def delete_task(task_id):
    global tasks
    tasks = [task for task in tasks if task['id'] != task_id]
    return redirect(url_for('index'))

@app.route('/clear')
def clear_tasks():
    global tasks
    tasks = []
    return redirect(url_for('index'))

if __name__ == '__main__':
    app.run(debug=True)

まとめ

Flask におけるルーティングとビュー関数について、@app.route() デコレータによる基本的なルート定義から、型付きの動的 URL パラメータを用いた柔軟なルーティング、GET/POST を使ったフォーム処理などの HTTP メソッドの扱い、デフォルトパラメータやエラーハンドリングといった高度なテクニック、そして一貫性のある設計や RESTful な考え方といったベストプラクティスまで、幅広く学習しました。

ルーティングはWebアプリケーションの基礎であり、適切に設計することでメンテナンス性の高いアプリケーションを作成できます。次の記事では、テンプレートエンジンを使用してより美しいHTMLを生成する方法を学びましょう。

演習問題

コードの実行方法

  1. 上記コードを app.py として保存
  2. 必要なライブラリをインストール(Flask)
pip install flask
  1. アプリケーションを実行
python app.py
  1. ブラウザまたはcurl等で各エンドポイントにアクセス

各エンドポイントのテスト例

  • http://localhost:5000/hello
  • http://localhost:5000/user/John
  • http://localhost:5000/square/5
  • http://localhost:5000/item/15
  • http://localhost:5000/calc/3/7
  • http://localhost:5000/articles/tech/123

POSTリクエストのテストには、curlやPostmanなどのツールを使用すると便利です。


初級問題(3問)

初級問題1

Flaskアプリで「/hello」にアクセスした時に「Hello World」と表示するルートとビュー関数を定義してください。

初級問題2

トップページ「/」にアクセスした際に「Welcome」を返すシンプルなルートを作成してください。

初級問題3

「/about」で「About Page」という文字列を返すルートを書いてください。

中級問題(6問)

中級問題1

「/user/」という動的URLパラメータを受け取り、受け取った名前をそのまま返すビュー関数を定義してください。

中級問題2

「/square/int:num」にアクセスすると、受け取った整数 num の二乗を返すルートを作成してください。

中級問題3

GETリクエストでアクセスすると「Send POST request」、POSTリクエストでアクセスすると「Posted」と返す「/submit」というルートとビュー関数を定義してください。

中級問題4

「/item/int:item_id」にアクセスした際、item_id が 10 以上なら「Valid item」、10 未満なら「Invalid item」と返すルートを作成してください。

中級問題5

「/calc/int:a/int:b」で a + b の計算結果を返すルートを定義してください。

中級問題6

「/echo」に対して、GET の場合はクエリパラメータ message を返し、POST の場合はフォームデータ message を返すビュー関数を作成してください。

上級問題(3問)

上級問題1

「/login」でGET時はログインフォーム(HTMLで可)を返し、POST時は送信された username と password を判定して、合否メッセージを返すビュー関数を作成してください。

上級問題2

「/articles//int:article_id」のような複数の動的URLパラメータを持つルートを作り、両方の値を整形して返すビュー関数を記述してください。

上級問題3

同じ「/process」ルートで、GETの場合は「Process GET」、POSTの場合はJSONデータを受け取り、その内容を整形して返す処理を行うビュー関数を書いてください。

演習問題 解答例

Flaskアプリケーションのコードとして実装しています。

from flask import Flask, request, jsonify

app = Flask(__name__)

初級問題 解答例

初級問題1

# /hello ルート
@app.route('/hello')
def hello():
    return 'Hello World'

初級問題2

# トップページ
@app.route('/')
def home():
    return 'Welcome'

初級問題3

# aboutページ
@app.route('/about')
def about():
    return 'About Page'

中級問題 解答例

中級問題1

# 動的URLパラメータ(ユーザー名)
@app.route('/user/')
def user(name):
    return name

中級問題2

# 数値の二乗を返す
@app.route('/square/')
def square(num):
    return str(num ** 2)

中級問題3

# GET/POSTで異なるレスポンス
@app.route('/submit', methods=['GET', 'POST'])
def submit():
    if request.method == 'POST':
        return 'Posted'
    else:
        return 'Send POST request'

中級問題4

# アイテムIDのバリデーション
@app.route('/item/')
def item(item_id):
    if item_id >= 10:
        return 'Valid item'
    else:
        return 'Invalid item'

中級問題5

# 計算結果を返す
@app.route('/calc//')
def calc(a, b):
    return str(a + b)

中級問題6

# メッセージのエコー
@app.route('/echo', methods=['GET', 'POST'])
def echo():
    if request.method == 'POST':
        # フォームデータから取得
        return request.form.get('message', 'No message')
    else:
        # クエリパラメータから取得
        return request.args.get('message', 'No message')

上級問題 解答例

上級問題1

# ログイン処理
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')

        # 簡易的な認証(実際の運用では適切な認証処理が必要)
        if username == 'admin' and password == 'password':
            return 'Login successful'
        else:
            return 'Invalid credentials'
    else:
        # GETリクエスト時はログインフォームを返す
        return '''
        <form method="post">
            Username: <input type="text" name="username"><br>
            Password: <input type="password" name="password"><br>
            <input type="submit" value="Login">
        </form>
        '''

上級問題2

# 複数の動的パラメータ
@app.route('/articles//')
def articles(category, article_id):
    return f'Category: {category}, Article ID: {article_id}'

上級問題3

# GET/POSTで異なる処理
@app.route('/process', methods=['GET', 'POST'])
def process():
    if request.method == 'POST':
        # JSONデータを受け取り整形して返す
        data = request.get_json()
        if data:
            return jsonify({
                'status': 'success',
                'received_data': data
            })
        else:
            return jsonify({
                'status': 'error',
                'message': 'No JSON data received'
            })
    else:
        return 'Process GET'

if __name__ == '__main__':
    app.run(debug=True)