FastAPIでWeb APIを作成してWijmo×Vue.jsのフロントエンドアプリと連携する

FastAPIは、Pythonで構築されたモダンで高性能なWebフレームワークです。主にWeb APIの開発に特化しており、その使いやすさとパフォーマンスの高さから、シンプルなアプリケーションから大規模なプロジェクトまで幅広く利用されています。

今回はこのFastAPIを使用してSQLiteのデータベースと連携するWeb APIを作成し、さらにVue.jsとJavaScript開発ライブラリ「Wijmo(ウィジモ)」で作成したフロントエンドアプリケーションと連携して、データの生成(Create)、読込(Read)、更新(Update)、削除(Delete)を行う方法をご紹介します。

FastAPIでWeb APIを作成してWijmo×Vue.jsのフロントエンドアプリと連携する

バックエンド

Web APIの作成

今回はPythonの標準データベースであるSQLiteを使用して、GET(参照)、POST(登録)、PUT(更新)、DELETE(削除)といったCRUD処理を行うWeb APIを作成します。

まずはvenvを使って新しく「fastapi-backend」という仮想環境を作成します。

python -m venv fastapi-backend

「fastapi-backend」フォルダに移動し、仮想環境を有効化します。

cd fastapi-backend
Scripts\activate

次にFastAPIとASGI Webサーバの「Uvicorn」、Pythonで使えるORMの「SQLAlchemy」をpip経由でインストールします。

pip install fastapi uvicorn sqlalchemy

インストールが完了したらプロジェクトのルートに「app」フォルダを作成し、「__init__.py」ファイルを作成します(中身は空でOKです)。

__init__.pyファイルの配置

続けて同フォルダに「database.py」ファイルを作成し、データベース接続の設定を記載します。

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

DATABASE_URL = "sqlite:///./test.db"

engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

次に「models.py」を作成し、SQLAlchemyのモデル定義を記載します。

from sqlalchemy import Column, Integer, String
from .database import Base

class Order(Base):
    __tablename__ = "orders"
    id = Column('id', Integer, primary_key=True)
    productName = Column('productName', String)
    orderDate = Column('orderDate', String)
    amount = Column('amount', Integer)

次に「schemas.py」を作成し、バリデーションなどを担うPydanticのスキーマ定義を記載します。

from datetime import datetime
from pydantic import BaseModel

class Order(BaseModel):
    productName: str
    orderDate: datetime
    amount: int

次に「crud.py」を作成し、CRUD処理を行うヘルパー関数の定義を記載します。

from sqlalchemy.orm import Session
from . import models, schemas

def get_order(id: int, db_session: Session):
    return db_session.query(models.Order).filter(models.Order.id == id).first()

def create_order(order: schemas.Order, db: Session):
    db_order = models.Order(productName=order.productName, orderDate=order.orderDate, amount=order.amount)
    db.add(db_order)
    db.commit()
    db.refresh(db_order)
    return db_order

def update_order(id: int, order: schemas.Order, db: Session):
    db_order = get_order(id,db)
    db_order.productName = order.productName
    db_order.orderDate = order.orderDate
    db_order.amount = order.amount
    db.commit()
    db.refresh(db_order)
    return db_order

def delete_order(id: int, db: Session):
    db_order = get_order(id,db)
    if db_order is None:
        return None
    db.delete(db_order)
    db.commit()
    return db_order

最後にアプリケーション本体の「main.py」を作成します。CORSの設定も行い、これから作成するVueアプリのオリジンを設定します。

from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import engine, get_db
from starlette.middleware.cors import CORSMiddleware

models.Base.metadata.create_all(bind=engine)

app = FastAPI()

# CORS対応
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173"],
    allow_credentials=True,
    allow_methods=["*"], 
    allow_headers=["*"] 
)

# 受注情報を全件取得
@app.get("/orders/")
def read_orders(db: Session = Depends(get_db)):
    orders = db.query(models.Order).all()
    return orders

# 受注情報を1件取得
@app.get("/orders/{order_id}")
def read_order(order_id: int, db: Session = Depends(get_db)):
    order = crud.get_order(order_id, db)
    return order

# 受注情報の登録
@app.post("/orders/")
def create_order(order: schemas.Order, db: Session = Depends(get_db)):
    return crud.create_order(order=order, db=db)

# 受注情報の更新
@app.put("/orders/{order_id}")
def update_order(order_id: int, order: schemas.Order, db: Session = Depends(get_db)):
    db_order = crud.update_order(id=order_id, order=order, db=db)
    if db_order is None:
        raise HTTPException(status_code=404, detail="Order not found")
    return db_order

# 受注情報の削除
@app.delete("/orders/{order_id}")
def delete_order(order_id: int, db: Session = Depends(get_db)):
    db_order = crud.delete_order(id=order_id, db=db)
    if db_order is None:
        raise HTTPException(status_code=404, detail="Order not found")
    return db_order

Web APIの実行

以上でWeb APIの作成が完了したので実行してみます。以下のコマンドでAPIを起動します。

uvicorn app.main:app --reload

起動後、「http://127.0.0.1:8000/docs」にアクセスすると、自動生成されたOpenAPIのAPIドキュメントが表示されます。

OpenAPIドキュメント

APIドキュメント上で各種APIの動作を確認できます。まずはPOSTのAPIを実行し受注情報を登録します。

次にGETのAPI(「http://127.0.0.1:8000/orders/1」)を実行し、今登録した受注情報を取得します。

以下のように「http://127.0.0.1:8000/orders」でGETを実行すれば受注情報の全件取得もできます。
※ あらかじめ何件かデータを登録しています。

次にPUTのAPI(「http://127.0.0.1:8000/orders/1」)を実行し、受注情報を更新します。

さらにDELETEのAPI(「http://127.0.0.1:8000/orders/1」)を実行し、受注情報を削除します。

再度「http://127.0.0.1:8000/orders」のGETリクエストを実行し、正しく受注情報が削除されていることを確認します。

フロントエンド

FastAPIを使用したバックエンドのWeb APIが作成できましたので、そのAPIと連携するフロントエンドのアプリをVue.jsとWijmoで作成していきます。npmを使用しますので、あらかじめNode.jsのインストールが必要です。

Vueアプリケーションの作成

まずはcreate-vueを利用して、Vueアプリケーションを作成します。

npm create vue@latest

create-vueがインストールされていない場合は、上記のコマンド実行時に以下のようなメッセージが表示されインストールを促されます。Yキーを押下してインストールを行います。

Need to install the following packages:
  create-vue@3.14.0
Ok to proceed? (y)

今回プロジェクト名には「wijmo-frontend」を設定しました。

? Project name: » wijmo-frontend

上記のほか、create-vueによって様々なオプションの選択を求められますが、今回は「Add TypeScript?」のみYesを選択してTypeScriptを追加し、その他のオプションはすべてデフォルトの「NO」を選択してシンプルなアプリケーションを作成します。

√ Project name: ... wijmo-frontend
√ Add TypeScript? ... No / Yes
√ Add JSX Support? ... No / Yes
√ Add Vue Router for Single Page Application development? ... No / Yes
√ Add Pinia for state management? ... No / Yes
√ Add Vitest for Unit Testing? ... No / Yes
√ Add an End-to-End Testing Solution? » No
√ Add ESLint for code quality? » No

プロジェクトを作成したら、動作確認のために実行してみます。以下のコマンドを実行してプロジェクトフォルダ「wijmo-frontend」に移動します。

cd wijmo-frontend

次に以下のコマンドを実行してアプリケーションを起動します。

npm install
npm run dev

ブラウザで「http://localhost:5173/」を開くと以下のようにVueアプリケーションの実行を確認できます。

Vueアプリの起動

動作を確認したらCtrl+Cキーを押下して終了しておきます。

Wijmoのインストールとアプリへの組み込み

「npm install」コマンドを実行して、WijmoのVue.js用パッケージをアプリケーションにインストールします。

npm install @mescius/wijmo.vue2.all

「src/App.vue」ファイルを編集し、WijmoのFlexGridをアプリに組み込んでいきます。まずはWijmoの各種コンポーネントのインポートやCRUD処理の実装を行います。シンタックスシュガー(糖衣構文)の<script setup>を使用して記述しています。

urlには先ほど作成したFastAPIのURLを設定し、WijmoのhttpRequestメソッドを使用してWeb APIからデータを取得します。また、FlexGrid上で行われた変更箇所はCollectionViewを使って追跡します。

[更新]押下時に実行される「update」関数では登録、更新、削除の処理を行っており、FlexGrid上で行われた変更内容が格納される「customer.itemsEdited」「customer.itemsAdded」「customer.itemsRemoved」の3つの配列を参照してリクエストをそれぞれ送信します。また、reviver関数の中では日付項目が格納されたstring型の項目に対し、Date型に変換する処理を行っています。
※ ライセンスキーを設定しない場合トライアル版を示すメッセージが表示されます。ライセンスキーの入手や設定方法についてはこちらをご覧ください。

<script setup>
import { onMounted } from "vue";
//Wijmoのコンポーネントをインポートします。
import * as wjCore from "@mescius/wijmo";
import { WjFlexGrid, WjFlexGridColumn } from '@mescius/wijmo.vue2.grid';
//日本語化カルチャをインポートします。
import '@mescius/wijmo.cultures/wijmo.culture.ja';

//wjCore.setLicenseKey('ここにライセンスキーの文字列を設定します');
const url = "http://127.0.0.1:8000/orders";

const order = new wjCore.CollectionView([], { trackChanges: true });

// データ取得 (GET)
onMounted(() => {
  wjCore.httpRequest(url, {
    success: (xhr) => {
      const data = JSON.parse(xhr.response, reviver);
      order.sourceCollection = data;
    },
  });
});

// 更新処理 (PATCH, POST, DELETE)
const update = () => {
  // データ更新(PATCH)
  order.itemsEdited.forEach((item) => {
    wjCore.httpRequest(`${url}/${item.id}`, {
      method: "PUT",
      data: item,
    });
  });

  // データ登録(POST)
  order.itemsAdded.forEach((item) => {
    wjCore.httpRequest(url, {
      method: "POST",
      data: item,
    });
  });

  // データ削除(DELETE)
  order.itemsRemoved.forEach((item) => {
    wjCore.httpRequest(`${url}/${item.id}`, {
      method: "DELETE",
    });
  });
};

const reviver = (key, val) => {
  // 先頭から"yyyy-mm-dd"の文字列を日付データと判断 
  if (typeof (val) == "string" &&
    val.match(/^\d{4}-\d{2}-\d{2}.*/)) {
    return new Date(Date.parse(val)); // Date型に変換
  } return val;
};
</script>

さらに<template>で更新処理を実行するボタンやFlexGridの表示部分の定義を行います。WjFlexGridColumnコンポーネントでは各カラムのプロパティの値を設定しています。

<template>
  <button @click="update" class="button">更新</button>
  <wj-flex-grid :autoGenerateColumns=false :itemsSource="order" :allowAddNew="true" :allowDelete="true">
    <wj-flex-grid-column header="ID" binding="id" :width="60"></wj-flex-grid-column>
    <wj-flex-grid-column header="商品名" binding="productName" :width="200"></wj-flex-grid-column>
    <wj-flex-grid-column header="受注日" binding="orderDate" :width="120" format='yyyy/M/d'></wj-flex-grid-column>
    <wj-flex-grid-column header="金額" binding="amount" :width="100" format="c"></wj-flex-grid-column>
  </wj-flex-grid>
</template>

続けて<style>でWijmoのCSSファイルのインポートや各種スタイルの定義を行います。

<style>
/* Wijmoのスタイルファイルをインポートします */
@import '@mescius/wijmo.styles/wijmo.css';

body {
  margin: 10px 0px 0px 10px;
}

.button {
  font-size: 12px;
  margin-bottom: 10px;
}

.wj-flexgrid {
    width: 530px;
}
</style>

「src/App.vue」ファイル全体の内容は以下のようになります。

<script setup>
import { onMounted } from "vue";
//WjFlexGridコンポーネントをインポートします。
import * as wjCore from "@mescius/wijmo";
import { WjFlexGrid, WjFlexGridColumn } from '@mescius/wijmo.vue2.grid';
//日本語化カルチャをインポートします。
import '@mescius/wijmo.cultures/wijmo.culture.ja';

//wjCore.setLicenseKey('ここにライセンスキーの文字列を設定します');
const url = "http://127.0.0.1:8000/orders";

const order = new wjCore.CollectionView([], { trackChanges: true });

// データ取得 (GET)
onMounted(() => {
  wjCore.httpRequest(url, {
    success: (xhr) => {
      const data = JSON.parse(xhr.response, reviver);
      order.sourceCollection = data;
    },
  });
});

// 更新処理 (PATCH, POST, DELETE)
const update = () => {
  // データ更新(PATCH)
  order.itemsEdited.forEach((item) => {
    wjCore.httpRequest(`${url}/${item.id}`, {
      method: "PUT",
      data: item,
    });
  });

  // データ登録(POST)
  order.itemsAdded.forEach((item) => {
    wjCore.httpRequest(url, {
      method: "POST",
      data: item,
    });
  });

  // データ削除(DELETE)
  order.itemsRemoved.forEach((item) => {
    wjCore.httpRequest(`${url}/${item.id}`, {
      method: "DELETE",
    });
  });
};

const reviver = (key, val) => {
  // 先頭から"yyyy-mm-dd"の文字列を日付データと判断 
  if (typeof (val) == "string" &&
    val.match(/^\d{4}-\d{2}-\d{2}.*/)) {
    return new Date(Date.parse(val)); // Date型に変換
  } return val;
};
</script>

<template>
  <button @click="update" class="button">更新</button><br/>
  <wj-flex-grid :autoGenerateColumns=false :itemsSource="order" :allowAddNew="true" :allowDelete="true">
    <wj-flex-grid-column header="ID" binding="id" :width="60"></wj-flex-grid-column>
    <wj-flex-grid-column header="商品名" binding="productName" :width="200"></wj-flex-grid-column>
    <wj-flex-grid-column header="受注日" binding="orderDate" :width="120" format='yyyy/M/d'></wj-flex-grid-column>
    <wj-flex-grid-column header="金額" binding="amount" :width="100" format="c"></wj-flex-grid-column>
  </wj-flex-grid>
</template>


<style>
/* Wijmoのスタイルファイルをインポートします */
@import '@mescius/wijmo.styles/wijmo.css';

body {
  margin: 10px 0px 0px 10px;
}

.button {
  font-size: 12px;
  margin-bottom: 10px;
}

.wj-flexgrid {
    width: 530px;
}
</style>

最後に「src/assets/main.css」に記載されている既存のスタイルを削除します。

@import './base.css';

データの取得(READ)

以上の手順で、Wijmoの組み込みは完了です。再び「npm run dev」コマンドを実行して「http://localhost:5173/」にアクセスすると、FlexGrid上にAPIから取得したデータが表示されていることを確認できます。
※ 事前に冒頭で作成したFastAPIのWeb APIを起動しておいてください。

APIからデータの取得

データの登録(CREATE)

FlexGridの一番下の行にデータを入力することで新規データの登録が可能です。データ入力後[更新]ボタンを押下するとAPIに登録のリクエストを送信できます。

データの更新(UPDATE)

FlexGrid上で任意のデータを更新し、[更新]ボタンを押下するとAPIに更新のリクエストを送信できます。一度に複数のレコードを更新することも可能です。

データの削除(DELETE)

FlexGrid上で削除したい行を選択しDeleteキーを押下すると対象の行を削除できます。その後、[更新]ボタンを押下するとAPIに削除のリクエストを送信できます。

さいごに

以上がFastAPIを使用してWeb APIを作成し、Vue.jsとWijmoを使ったフロントエンドアプリと連携する方法でした。

WebサイトではWijmoの機能を手軽に体験できるデモアプリケーションやトライアル版も公開しておりますので、こちらもご確認ください。

また、ご導入前の製品に関するご相談、ご導入後の各種サービスに関するご質問など、お気軽にお問合せください。

\  この記事をシェアする  /