はじめに
この記事では、FastAPIを使用してSPA(シングルページアプリケーション)を統合する方法について検証します。
FastAPIは主にバックエンドAPIサーバーとして使用されますが、SPAと統合することで単一のアプリケーションとして動作させることができます。
FastAPIにSPAを統合する
FastAPIには静的ファイルを配信する機能があります。
FastAPIの静的ファイル配信機能
例えば、以下のようなコードをmain.py
に追加することで、static
ディレクトリにあるファイルを配信することができます。
app = FastAPI(
title="FastAPI, SPA",
description="FastAPIでSPAを作成する",
version="1.0.0",
)
app.mount("/", StaticFiles(directory="static"), name="static")
そのため、Nuxt.jsで作成したSPAのフロントエンドをstatic
ディレクトリに配置することで、FastAPIに統合することができます。
SPAのフロントエンドをNuxt.jsで作成する
Nuxt.jsを使用してSPAのフロントエンドを作成する手順を説明します。
まず、Nuxt.jsプロジェクトを初期化します。
npx nuxi init --package-manager yarn frontend
その後、nuxt.config.ts
の ssr: false
を指定し、SPAであることを明示します。
export default defineNuxtConfig({
...
ssr: false,
})
その後、Nuxt.jsプロジェクトを起動し、
localhost:3000
にアクセスして、Nuxt.jsプロジェクトが正常に起動していることを確認します。
cd frontend
yarn dev
Vuetifyを使用するように設定する
Nuxt.jsでVuetifyを使用するように設定します。
yarn add vuetify @mdi/font
nuxt.config.ts
import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
export default defineNuxtConfig({
...
build: {
transpile: ['vuetify'],
},
modules: [
(_options, nuxt) => {
nuxt.hooks.hook('vite:extendConfig', (config) => {
// @ts-expect-error
config.plugins.push(vuetify({ autoImport: true }))
})
}
],
vite: {
vue: {
template: {
transformAssetUrls,
},
},
},
})
plugins/vuetify.ts
import '@mdi/font/css/materialdesignicons.css'
import 'vuetify/styles'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
export default defineNuxtPlugin((app) => {
const vuetify = createVuetify({
ssr: false,
components,
directives,
})
app.vueApp.use(vuetify)
})
フロントエンドのページ作成
Nuxt.jsでページを作成するには、pages
ディレクトリにファイルを作成します。
mkdir -p pages/index.vue
pages/index.vue
<template>
<div>
<h1>Home Page</h1>
<p>Welcome to the Home page!</p>
</div>
</template>
pages/about.vue
<template>
<div>
<h1>About Page</h1>
<p>Learn more about us on this page.</p>
</div>
</template>
フロントエンドのレイアウト作成
Nuxt.jsでレイアウトを作成するには、layouts
ディレクトリにファイルを作成します。
mkdir -p layouts/default.vue
layouts/default.vue
<template>
<v-app>
<v-container :class="{ 'drawer-open': drawer }">
<v-row>
<v-col>
<v-app-bar app>
<v-btn icon @click="drawer = !drawer">
<v-icon>mdi-menu</v-icon>
</v-btn>
<v-toolbar-title>My Website</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn text to="/">Home</v-btn>
<v-btn text to="/about">About</v-btn>
</v-app-bar>
<v-navigation-drawer app v-model="drawer">
<v-list>
<v-list-item link to="/">
<v-list-item-title>Home</v-list-item-title>
</v-list-item>
<v-list-item link to="/about">
<v-list-item-title>About</v-list-item-title>
</v-list-item>
</v-list>
</v-navigation-drawer>
</v-col>
</v-row>
<v-row>
<v-col>
<v-main>
<slot />
</v-main>
</v-col>
</v-row>
<v-row>
<v-col>
<v-footer app fixed class="justify-center">
<p>© 2023 My Website</p>
</v-footer>
</v-col>
</v-row>
</v-container>
</v-app>
</template>
<script>
export default {
data() {
return {
drawer: false,
};
},
};
</script>
<style>
.drawer-open {
margin-left: 256px;
}
</style>
以下のVuetifyコンポーネントが使用されています:
- v-app: アプリケーションのルートコンポーネントで、他のすべてのVuetifyコンポーネントを包含します。
- v-app-bar: アプリケーションの上部にナビゲーションバーを表示します。ここにはホームページとアバウトページへのリンクが含まれています。
- v-navigation-drawer: サイドバーで、ナビゲーションリンクを含んでいます。このドロワーは動的に表示または非表示にすることができます。
- v-list と v-list-item: ナビゲーションドロワー内で使用され、ナビゲーションリンクのリストを表示します。
- v-main: ページの主要コンテンツを表示するためのコンテナです。スロットを使用して、ページ固有のコンテンツを挿入できます。
- v-footer: フッター部分で、著作権情報などを表示します。
app.vueの修正
と を使用して、ページのレイアウトとコンテンツを管理します。これにより、Nuxt.jsのページコンポーネントを動的に表示することができます。
app.vue
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
Nuxt.jsプロジェクトを起動する
上記の修正を行った内容が反映されているかを確認するために、Nuxt.jsプロジェクトを起動します。
yarn dev
Nuxt.jsプロジェクトが起動したら、ブラウザでhttp://localhost:3000
にアクセスして、SPAのフロントエンドが正常に表示されることを確認します。
Nuxt.jsでビルドする
Nuxt.jsでビルドを行い、静的ファイルを生成します。
yarn generate
上記コマンドで、output/public
ディレクトリに静的ファイルが生成されます。
(distディレクトリには、output/publicへのシンボリックリンクが作成されるだけのため、コピーする場合は注意が必要です。)
FastAPIに静的ファイルを配信する
※FastAPIの前提は以下記事リンク先の内容です。
Databricks Apps でカスタムアプリのデプロイ
FastAPIに静的ファイルを配信するには、main.py
に以下のコードを追加しつつ、既存APIのパスを変更します。
from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles
app = FastAPI()
@app.get("/api")
def read_api_root():
return {"Hello": "World"}
@app.get("/api/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
app.mount("/", StaticFiles(directory="./static", html=True), name="static")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
app.mount("/", StaticFiles(directory="./static", html=True), name="static")
この行は、FastAPIアプリケーションに静的ファイルを配信するための設定を追加します。ここで、"/"というルートパスに対して、"./static"ディレクトリ内の静的ファイルを配信するように設定しています。html=Trueオプションを指定することで、HTMLファイルを適切に扱うことができます。
また、backend側staticディレクトリを作成し、Nuxt.jsでビルドしたoutput/publicディレクトリ内の静的ファイルをコピーします。
上記対応後、以下コマンドで、FastAPIアプリケーションを起動します。
gunicorn main:app -k uvicorn.workers.UvicornWorker -w 1
ブラウザでhttp://localhost:8000
にアクセスして、SPAのフロントエンドが正常に表示されることを確認します。
また、http://localhost:8000/api/
にcurlでアクセスして、APIが正常に動作していることを確認します。
curl http://localhost:8000/api/
# {"Hello": "World"}
curl http://localhost:8000/api/items/1?q=hoge
# {"item_id": 1, "q": "hoge}
これで、FastAPIで既存のバックエンドAPIエンドポイントとSPAのフロントエンドを統合することができました。
まとめ
FastAPIとNuxt.jsを使用してSPAを統合する方法を検証しました。
FastAPIの静的ファイル配信機能を利用して、Nuxt.jsでビルドしたSPAのフロントエンドを配信することで実現できました。
この対応でフロントエンドとバックエンドが一つのアプリとして共存することが可能になりました。