タイトル : Next.js FastAPIと通信 その2 OpenApiGeneratorを使ってみる
更新日 : 2024-02-04
カテゴリ : プログラミング
タグ :
frontend   
nextjs   
zustand   
python   
fastapi   

画面

FastAPIの通信でフロントエンドのモデルファイル作成にOpenApiGeneratorを使ってみます。先に画面です。

画像1

FastAPIの方

@dataclass
class Field:
    name: str
    dtype: str
    size: int = None

@dataclass
class Table:
    name: str
    fields: List[Field]

class TableInfos(BaseModel):
    version: str
    tables: List[Table]


@app.get("/tables", response_model=TableInfos)
async def get_tables():

    tables = []
    for t_index in range(1, 4):
        t_name = f"TABLE{t_index}"
        fields = [
            Field(f"col1_t{t_index}", "int"), Field("col2", "text", 8),  Field("col3", "float")
        ]
        tables.append(
            Table(name=t_name, fields=fields)
        )

    return TableInfos(version="0.9.1", tables=tables)

OpenApiGeneratorを使う準備

OpenAPI Generatorを使います。

CLI Installationを参考にして、jarファイルをダウンロードします。

Javaをインストールします。上記のページに、Java 11 runtime at a minimumとあるので、とりあえずJava11で。

sudo apt install openjdk-11-jre-headless
(sudo apt install openjdk-11-jdk-headless でも)

OpenApiGeneratorを使ってtypescriptのモデルファイルを作成

FastAPIが作成してくれる openapi.json をダウンロードする。デフォルトは /openapi.json なので、そこにアクセスしてダウンロード。(今回だとhttp://192.168.11.6:8080/openapi.json)

-g typescript-fetch で今回はタイプスクリプト-fetch用のファイルを作成、-i openapi.jsonで上記でダウンロードしたファイルを指定し、 -o /src/openapi_generated で出力先を指定します。

java -jar openapi-generator-cli.jar generate 
  -g typescript-fetch -i openapi.json  -o front/src/openapi_generated
    --additional-properties=modelPropertyNaming=camelCase,supportsES6=true,withInterfaces=true,typescriptThreePlus=true

以下のファイルが作成されました。

$ pwd
xxx/src/openapi_generated
$ ls -R
.:
apis  index.ts  models  runtime.ts

./apis:
DefaultApi.ts  index.ts

./models:
Field.ts  ProjectInfo.ts  ProjectInfos.ts  Table.ts  TableInfos.ts  index.ts
$ 

上記の models/ に作成されたファイルをNext.jsで使っていきます。

Next.jsの方、Zustandのストアを書いて、その中でfetchする

import { TableInfos } from "@/openapi_generated"; で先に作成した TableInfos を使います。

import { create } from "zustand";

import { TableInfos } from "@/openapi_generated";
import { API_URL } from "@/store/settings";

type Tables = {
    tableinfo?: TableInfos;
    fetchTables: () => void;
};

export const useTableInfoStore = create<Tables>((set) => ({
    tableinfo: undefined,
    fetchTables: async () => {
        try {
            const response = await fetch(`${API_URL}/tables`);
            const res_json = await response.json();
            set({tableinfo: res_json})
        } catch (error) {
            console.error("Error fetching tables:", error);
        }
    },
}));

Next.jsの方、Zustandのストアを使う方

"use client"

import { Typography, Stack, Box } from "@mui/material";
import * as React from 'react';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';

import { useEffect } from "react";

import {useTableInfoStore} from "@/store/tableinfostore"; 

export default function Home() {

  const {tableinfo, fetchTables} = useTableInfoStore()

  useEffect(() => {
    fetchTables();
  }, []);

  return (
    <Box>
      <Stack spacing={2} padding={2}>
        <Typography>メニュー3の画面  version: {tableinfo?.version}</Typography>
        {
          tableinfo?.tables.map((tinfo) => {
            return (
              <Stack key={`s-${tinfo.name}`}>
                <Typography key={tinfo.name}>テーブル名:{tinfo.name}</Typography>
                <TableContainer component={Paper}>
                  <Table sx={{ minWidth: 450 }} size="small" aria-label="simple table">
                    <TableHead>
                      <TableRow>
                        <TableCell>カラム名</TableCell>
                        <TableCell align="center"></TableCell>
                        <TableCell align="center">サイズ</TableCell>
                      </TableRow>
                    </TableHead>
                    <TableBody>
                      {tinfo.fields.map((field) => (
                        <TableRow
                          key={`${tinfo.name}-${field.name}`}
                          sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
                        >
                          <TableCell component="th" scope="row">{field.name}</TableCell>
                          <TableCell align="center">{field.dtype}</TableCell>
                          <TableCell align="center">{field.size}</TableCell>
                        </TableRow>
                      ))}
                    </TableBody>
                  </Table>
                </TableContainer>
              </Stack>
            )
          })
        }
      </Stack>
    </Box>
  );

}