1
0

生成AI時代のためのVite+Expressアプリ構成を考えてみた

Posted at

概要

以前、生成AIを用いたアプリ開発ではNext.jsよりもViteの方が使いやすいと述べました。
参考:Claude 3.5 Sonnet Artifactsで爆速アプリ開発してみた
ですが、サーバサイド処理はNext.jsの方がお手軽にできるイメージでした。
そこで、今回は自分用のメモとして、Viteを用いたサーバサイド処理の実装方法をまとめます。
例によって、Claude 3.5に実装方法のベースを書かせています。

環境

  • マシン:Macbook Air (M1チップ、2020)
  • OS:Sonoma 14.6
  • Docker:v4.33.0
  • Node.js v18.16.0
  • MySQL Ver 8.0.36 for macos14.2 on arm64 (Homebrew)

アーキテクチャ

  • Viteプロジェクトの中でExpressサーバを立てて、データフェッチはサーバサイド、レンダリングはクライアントサイドで行います。
  • Server-side処理とClient-side処理が分かりやすいよう、docker composeを用います。
  • Expressサーバはコンテナ外からのアクセスは出来ないようにします。

アーキテクチャ改.png

アプリケーションを公開するならGoogle Cloud Platformのコンテナ系サービスを使いたい気持ちで書いています。

実装方法

  1. 新しいディレクトリを作成し、Viteプロジェクト用ディレクトリに移動します
    mkdir my-app
    cd my-app
    mkdir vite-project database
    cd vite-project
    
  2. Viteプロジェクトを作成します
    npm create vite@latest
    npm install
    
  3. 必要なパッケージをインストールします
    npm install express mysql2
    npm install -D cocurrently ts-node @types/express @types/node
    
  4. src/server.tsファイルを作成し、以下の内容を追加します。
    src/server.ts
    import express from 'express';
    import mysql from 'mysql2/promise';
    import cors from 'cors';
    
    const app = express();
    const port = 3000;
    
    app.use(cors());
    
    app.get('/api/hello', async (req, res) => {
      try {
        const connection = await mysql.createConnection({
          host: process.env.DB_HOST || 'database',
          user: process.env.DB_USER || 'root',
          password: process.env.DB_PASSWORD || 'password',
          database: process.env.DB_NAME || 'testdb',
          port: 3306
        });
    
        const [rows] = await connection.execute('SELECT * FROM hello_world');
        await connection.end();
    
        if (Array.isArray(rows) && rows.length > 0) {
          res.json(rows[0]);
        } else {
          res.status(404).json({ error: 'No data found' });
        }
      } catch (error) {
        console.error('Database error:', error);
        res.status(500).json({ error: 'Internal Server Error', details: error instanceof Error ? error.message : String(error) });
      }
    });
    
    app.listen(port, '127.0.0.1', () => {
      console.log(`Server running at http://localhost:${port}`);
    });
    
    • Expressサーバがコンテナ内でのみアクセス可能になるように、app.listenに127.0.0.1(=localhost)を指定します。
  5. src/App.tsxファイルを次のように更新します。
    src/App.tsx
    import { useState, useEffect } from 'react';
    
    function App() {
      const [message, setMessage] = useState('');
      const [error, setError] = useState('');
    
      useEffect(() => {
        fetch('/api/hello')
          .then(response => {
            if (!response.ok) {
              throw new Error(`HTTP error! status: ${response.status}`);
            }
            return response.json();
          })
          .then(data => {
            if (data.message) {
              setMessage(data.message);
            } else {
              setError('Unexpected data structure');
            }
          })
          .catch(error => {
            console.error('Error:', error);
            setError(`Failed to fetch data: ${error.message}`);
          });
      }, []);
    
      if (error) {
        return <div>Error: {error}</div>;
      }
    
      return (
        <div>
          <h1>Message from MySQL:</h1>
          <h1>{message}</h1>
        </div>
      );
    }
    
    export default App;
    
    • APIリクエストをプロキシする設定を行います。
  6. package.jsonファイルのscriptフィールドを以下のように更新します。
    package.json
    "scripts": {
        "dev": "concurrently \"vite\" \"NODE_OPTIONS='--experimental-specifier-resolution=node' node --loader ts-node/esm src/server.ts\"",
        "build": "tsc && vite build",
        "preview": "vite preview"
      }
    
    • concurrentlyを使うことで、フロントエンドの開発サーバーとバックエンドのサーバーを同時に立ち上げる
    • --experimental-specifier-resolution=node: Node.jsがESモジュールの解決方法を変更
    • --loader ts-node/esm: TypeScriptのコードを直接実行するためのローダー。ts-nodeはTypeScriptのコードを直接実行できるツールで、/esmはESモジュールをサポートするためのオプション。
  7. tsconfig.app.jsonを削除し、tsconfig.jsonを以下のように更新します。
    tsconfig.json
    {
      "compilerOptions": {
        "target": "ES2020",
        "module": "ESNext",
        "moduleResolution": "node",
        "esModuleInterop": true,
        "strict": true,
        "outDir": "./dist",
        "rootDir": "./src",
        "typeRoots": ["./node_modules/@types"],
        "types": ["node"],
        "allowJs": true,
        "resolveJsonModule": true,
        "allowSyntheticDefaultImports": true,
        "jsx": "react-jsx"
      },
      "ts-node": {
        "esm": true,
        "experimentalSpecifierResolution": "node"
      },
      "include": ["src/**/*"],
      "exclude": ["node_modules"]
    }
    
  8. vite.config.tsを以下のように更新します。
    vite.config.ts
    import { defineConfig } from 'vite'
    import react from '@vitejs/plugin-react'
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [react()],
      server: {
        host: '0.0.0.0',
        port: 5173,
        strictPort: true,
        proxy: {
          '/api': 'http://127.0.0.1:3000'
        }
      }
    })
    
  9. ViteプロジェクトのルートディレクトリにDockerfileを作成し、以下の内容を追加します。
    Dockerfile
    FROM node:18
    
    WORKDIR /app
    
    COPY package*.json ./
    
    RUN npm install
    
    COPY . .
    
    EXPOSE 5173
    
    CMD ["npm", "run", "dev"]
    
  10. databaseディレクトリ配下にinit.sqlファイルを作成し、以下の内容を追加します。
    init.sql
    USE testdb;
    CREATE TABLE IF NOT EXISTS hello_world (
      id INT AUTO_INCREMENT PRIMARY KEY,
      message VARCHAR(255) NOT NULL
    );
    INSERT INTO hello_world (message) VALUES ('Hello World');
    
  11. my-appディレクトリ配下にdocker-compose.yamlを作成し、以下の内容を追加します。
    docker-compose.yaml
    version: '3'
    services:
      app:
        build: ./vite-project
        ports:
          - "5173:5173"
        environment:
          - DB_HOST=database
          - DB_USER=root
          - DB_PASSWORD=mysql
          - DB_NAME=testdb
        depends_on:
          - database
      database:
        image: mysql:8
        environment:
          MYSQL_ROOT_PASSWORD: mysql
          MYSQL_DATABASE: testdb
        volumes:
          - ./mysql-data:/var/lib/mysql
          - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
    
  12. vite-projectディレクトリからmy-appディレクトリに移動し、コンテナを立ち上げます。
    cd ..
    docker compose up -d
    
  13. ブラウザを立ち上げ、http://localhost:5173に接続して、以下の画面が表示されていることを確認します。
    Vite.png
  14. ブラウザから、http://localhost:3000/api/helloに接続して、アクセスできないことを確認します。
    Express.png
1
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
1
0