0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FastAPI + React SPAのコンフォータブルな開発テンプレートを作る

Posted at

この記事を書いた半年ほど前(2024/6)の時点では、「検証用のちょっとしたWebアプリをViteReactの組み合わせで作ることが多」かったのですが、最近は猫も杓子もAIであり、そうすると検証用のアプリといえばそれも大体AIであり、となれば必然的にバックエンドをPythonにしたくなるわけです。1

そういうわけで、いろいろ開発環境を整備したり模索したりしていたのですが、ある程度「快適」と言えるところまで来たので、記事にしました。

本格的で大規模な開発用ではなく、ちょっとしたツールや小規模なWebアプリの構築を目的としているので、そういうのを求めている人には先にごめんなさいしておきます。

2024年11月時点の情報です。プラットフォームは問わないと思いますが、私が個人的にLinux、Windows環境で試した限りにおいて問題なく動作している、ということに留意ください。

最終的なツールスタック

こうなります。

開発用ツール

  • uv - Rust製のPythonバージョン管理・パッケージマネージャ
  • Bun - 高速なJavaScriptランタイム兼パッケージマネージャ
  • Task - Go製のタスクランナー
    • uvにタスクランナーがついたらこれはなくてもいいかも

バックエンド

  • FastAPI - StarlettePydanticをベースとしたPython製Webアプリケーションフレームワーク
    • Flaskでもいいと思います(未検証)
    • 個人的にはFastAPIのほうが"薄い"感じで好みです
  • Uvicorn - Python用ASGI Webサーバー

フロントエンド

静的サイトにビルドできるならフレームワークは別になんでもいいと思いますが、開発の快適さから言うとViteが使えるとよいです。今回はなんだかんだで結構手に馴染んできたRemix v2の後継であるReact Routerのv7(プレリリース) を使っています。

  • React Router (v7) - フルスタック(になった)Webアプリケーションフレームワーク
    • Routerとは
    • 2024年11月現在、まだプレリリースなので、気になる場合はRemix v2のSPAモードでもよいです。ほとんどいっしょ
    • 今回はSSR無しで使います

ビルダー(optional)

  • Nuitka - Pythonスクリプトをスタンドアロンアプリにビルドするツール
    • 身近な人に試してもらうとき用
    • 非開発者のPCにPythonは入っていないことが多い2
      • 先の記事にも書きましたが、いちいちWebアプリとしてホストするの面倒で…
    • 先に有名どころでPyInstallerを試したのですが、exeの起動にえらい時間がかかったのでこちらにしてみました
      • NuitkaはNuitkaで、ビルドにかなり時間かかるという問題があります…痛し痒しです

あとはお好みでblackBiomeあたりのLinter/Formatterを入れましょう。

環境構築

uvBunのインストール

この2つが起点になります。公式のインストール手順に沿ってインストール。

Linuxでの例
# uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Bun
curl -fsSL https://bun.sh/install | bash

プロジェクトフォルダを作る

# `template-python-spa`の部分は任意
uv init template-python-spa
cd template-python-spa

Pythonのインストール

# バージョン指定する場合は`uv python install 3.12`などにする
uv python install

各種Python系フレームワーク・ツールのインストール

pip install xxxではなくuv add xxxでインストールします

# FastAPI
uv add fastapi

# Uvicorn
uv add "uvicorn[standard]"

# Task
# pipのレジストリにあるのがありがたい
uv add --dev go-task-bin

# Nuitka
uv add --dev nuitka

Pythonのエントリーポイントづくり

uv initするとディレクトリ内にhello.pyというファイルができるのですが、削除して新たにmain.pyというファイルを作ります。

rm hello.py
touch main.py
main.py
import os
import sys
from pathlib import Path

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# 開発用CORS
origins = [
    "http://127.0.0.1:8000",
    "http://localhost:5173",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.get("/hello")
def read_root():
    return {"Hello": "World"}


def is_nuitka():
    # Nuitkaでコンパイルされているかどうかを確認
    return "__compiled__" in globals()


def get_dir():
    # ディレクトリパスの取得
    if is_nuitka():
        return os.path.dirname(os.path.abspath(sys.argv[0]))
    else:
        return "."


# フロントエンドの配信
frontend_dir = directory = Path(get_dir()) / "frontend/build/client"
# フロントエンドのビルド前にuv run task devしたときにコケないように
if frontend_dir.exists():
    app.mount(
        "/",
        StaticFiles(directory=frontend_dir, html=True),
        name="rr",
    )

if __name__ == "__main__":
    import uvicorn

    if is_nuitka():
        # Nuitkaで実行時はブラウザを開く
        import webbrowser

        webbrowser.open("http://127.0.0.1:8000/", new=2, autoraise=True)

    uvicorn.run(app, host="127.0.0.1", port=8000)
  • ファイルパスを相対パスで指定してたらうまく動作しなかったので、Nuitkaで実行中は絶対パスを指定するようにしています

フロントエンド側の準備

React Router公式のテンプレートがあるので、frontendフォルダにそれを配置します。

# v7が正式リリースされたら`@pre`は消えると思います
bunx create-react-router@pre frontend

バックエンドはPythonに一任するので、SSRは無効化します。

frontend/vite.config.ts
  import { reactRouter } from "@react-router/dev/vite";
  import tsconfigPaths from "vite-tsconfig-paths";
  import { defineConfig } from "vite";
  
  export default defineConfig({
    plugins: [
    reactRouter({
-       ssr: true,
+       ssr: false, // SPAモードにする
      }),
      tsconfigPaths(),
    ],
  });

Vite環境変数

開発環境の時はフロントエンドがhttp://localhost:5173で動くので、APIを叩く処理は開発時のみオリジンをhttp://127.0.0.1:8000へ置き換える必要があります。

frontend/.env.development
VITE_API_BASE=http://127.0.0.1:8000

こんな感じで設定しておき、fetchするときに、

const response = await fetch(
  `${import.meta.env.VITE_API_BASE}/hello`
);

のように呼び出せばよいです。

Taskfile.yamlの準備

タスクを設定していきます

version: '3'

tasks:
  run:
    cmds:
      - uv run task build
      - uv run main.py

  build:
    cmds:
      - cd frontend && bun install && bun run build

  dev:
    cmds:
      - uv run uvicorn main:app --reload & (cd frontend && bun install && bun run dev)

  make:
    cmds:
      - uv run task build
      - nuitka ./main.py --standalone --include-data-dir=./frontend/build/client=./frontend/build/client

uv run task xxxでタスクが実行されます。開発サーバーの起動はuv run task devで、バックエンド・フロントエンドともに、ホットリロードが効きます。
通常のWebアプリとしてホストするときはuv run task run、Nuitkaでビルドする場合はuv run task makeです。

  • Nuitkaには--onefileという、必要なファイルを一つの*.exeに全部入れてくれるオプションがあるのですが、これを使うとどうも一部のマルウェア検知ツールにマルウェア扱いされてしまいます
    • 社内のシステム管理者に逮捕されたくない場合は、やめておくのが無難かと思います(1敗)
  • Nuitkaは現在のところ、クロスコンパイルができません
    • Windows用のexeを作りたい場合は、Windows環境でやる必要があります

運用

これでテンプレートとしては完成です。GitHubにでも置いておきましょう。
開発端末上にuvBunが必要になるのがネックかもですが、それさえ乗り越えられれば、git cloneからのuv syncですぐ作り始められます。

  • 試してはいないですが、おそらくBunを使ってるところは全部npmに置き換えられると思うので、Bunはまだちょっと…という方はNodeに差し替えましょう

終わりに

uvに早くタスクランナーがついてほしいのと、バックエンドがPythonであるElectronの登場が待たれます。3

  1. 別にPythonが好きなわけではない

  2. それはそう

  3. 別にPythonを書きたいわけではない

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?