山羊の午後

研究関係の備忘録。

バックエンドのRenderへのデプロイ

生成AIを使ってゼロからWebサービスをつくる - 山羊の午後

7日目。これでこのシリーズは最終日。
準備したDockerfileをもとにRenderにデプロイします。

バックエンドサービスはRenderのWeb Serviceをつかってデプロイします。
Web Services – Render Docs
基本的にはフロントと同じく、GitHubからのCI/CDになるので、GitHubのレポジトリに正しくDockerfileが設定できていれば問題ないかたち。

Dockerを使ったデプロイのマニュアルはこれ
Docker on Render – Render Docs

設定項目は以下。

  • Name : webアドレスになる appname-backend → https:// appname-backend.onrender.com
    • frontのaxios.postの設定URLと一致させる
  • Project : フロントとバックのアプリを同じプロジェクトに入れておくと管理が楽になる。(同じダッシュボードで管理できる)
  • Language: Docker デプロイにDockerを使う場合はここで指定する
  • Branch: main GitHubのレポジトリでデプロイに使うブランチを指定する
  • Region: Oregon (デフォルト)
  • Root Directory : 設定しない。*1
  • Instance Type : Free 無料枠のインスタンスを指定する。
  • Environment Variables : .envで設定している環境変数を入れる
    • uvicornで指定するportはデフォルトでは環境変数PORTで指定される10000を利用することになる
    • 準備したDockerfileを修正した

それから、デプロイしたフロントのURLをCORS設定に加えることを忘れずに。

全ての準備が整ったらデプロイ!

しばらく待つと問題なければ無事にサービスが立ち上がります。
とはいえ、最初はDockerfileに不備があったりしてエラーが出てはGPTに聞いて直して、を何回かやりました。
コードの修正はCodespaceでコードを編集して、リポジトリのmainにpushするだけ。
CI/CD連携があるので、修正がコミットされるたびに自動的にデプロイの作業がリスタートされます。
このあたり、とっても便利でサービスの発展を感じました。

無事にデプロイが完了したら、いよいよサービスの確認です。
フロント側にアクセスして、ボタンを押す→バックの処理が実行される、まで挙動を確認出来たら完成。
はじめてのWebサービスづくり、できました!

実際に作ってみることで、Webサービスってこんな仕組みで出来ているんだなあ、という理解がとても進みました。
とくにフロント側はほとんど知識のない状態からよくここまできた、、、
Reactアプリを作ってみて、JavaScript, CSS, HTMLとネットワーク接続のあれこれにちょっとだけ触れることができました。
それからバックエンドのAPIを一から作るのも、フロントと接続するのも勉強になりました。
Dockerをつかったデプロイ、GitHub連携をつかったCI/CDも学びがおおく、、、
ほぼ一週間でものすごく基礎知識の底上げができた感じがします。

つど分からないところをChatGPTに聞けるのはすごくよい学習体験でした。
ただ、対話型の学習では体系的な知識を学ぶのは難しい、とも感じました。
ある程度まとまった知識は解説ページなり、書籍なりを読むほうが入ってきやすい。
なので、教科書的なテキストを見ながら、手を動かし、分からないところは生成AIに質問する、という組み合わせが技術勉強には最適なのかなー、と思いました。

アプリとしてはまだまだ必要最低限の機能しかない状態なので、ここから機能追加をしつつ、より良いものにできればと思います!

*1:フロントでは/frontendをルートに指定したが、ルートをずらすとpythonモジュールのインポート参照がずれてしまうため変更しないことに。最初にコード書くときにこのあたりも考慮できたらよかった

バックエンドのデプロイ準備:Dockerファイルをつくる

生成AIを使ってゼロからWebサービスをつくる - 山羊の午後

6日目。今日はバックエンドのデプロイ~!と取り掛かりましたが、バックエンドのデプロイ方法をChatGPTに聞いた結果、Dockerを使おう!という結論になり、今日の作業はDockerの勉強とDockerファイルの作成になりました。

Docker、触ったことはあるし、使えるのですが、
アプリのデプロイに向けていちからDockerファイルを書いたことはない、というレベル感です。

今回、Pythonの環境構築をuvでやり、FastAPIのアプリを作成しました。
この条件でDockerファイルを作るぞ~と調べ始めたのですが、
uvの環境をdockerに作るのがやや手間、というか知見が少ない。。。
uv on Docker をやっている
手順通りにやればできるものの、これは初心者にはめんどうだな、という所感。

そこでこうしました。
uvでつくった仮想環境で pip freezeでrequirements.txtをつくり、pipインストールでpython環境が構築できるようにする。

cd backend # uv project フォルダ
source .venv/bin/activate
pip freeze > requirements.txt

これなら初心者にも安心、な感じです。
続いてDockerファイルを作ります。これも状況を伝えてChatGPTに書かせます。

# ベースイメージ
FROM python:3.12-slim

# 作業ディレクトリの設定
WORKDIR /app

# 必要ファイルのコピー
COPY requirements.txt .

# 依存関係のインストール
RUN pip install --no-cache-dir -r requirements.txt

# アプリ全体のコードをコピー
COPY . .

# ポートの公開
EXPOSE 8000

# 起動コマンド
CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"]

書いてもらったものを読みながら、それぞれ開設してもらう。
そういえばなんで起動はuvicornなんだ?とuvicornとFastAPIの関係性について教えてもらったり。
アプリケーションというのは何層にも処理が重ねられて動いているんだなあ、と感慨深く学びました。
そりゃ、いろいろエラー出るわ。。。と思いながらDockerファイルの書き方を学ぶ。

出来上がったものがローカルで動くことを確認する。

docker build -t app .
docker run -p 8000:8000 app

できましたー。
今日はここまで。書くとあっさりしていますが、なかなか大変でした。

Renderにフロントアプリをデプロイする

生成AIを使ってゼロからWebサービスをつくる - 山羊の午後

5日目!
ローカルでフロントとバックの繋ぎこみができたので、いよいよ本番環境にデプロイしていきます。

と、書きましたが、そもそもWebサービスを公開するってなに???というところから何もわかっていないので、まずは何をしたら良いのかChatGPTに聞きます。その結果、デプロイという作業が必要なことを把握しました。
デプロイ。かっこいいやん、よし、やってこー。

無料でWebサービスをホストしてくれるサービスについてWeb検索してもらいながらChatGPTに解説してもらいます。
その結果、Vercel、Render、Herokuなどのサービスを教えてもらう。
いろいろあるねー、ということでおすすめポイントをまとめて比較してもらう。
その結果、フロントとバック両方に無料枠があり、かつモノレポ対応していてデプロイが簡単そうなRenderを使ってみることに。

使い方のチュートリアルを読みながら、分からない点についてChatGPTに解説してもらう。
Static Sites – Render Docs

GitHubリポジトリからCI/CDができるので簡単らくらく。
CI/CDという概念もふんわりとした理解だったので解説してもらって理解を深める。
さくっとフロントがデプロイできたので、今日はここまで。明日はバックエンドをデプロイします。

フロントとバックの繋ぎこみ:CodeSpaceでのCORS設定

生成AIを使ってゼロからWebサービスをつくる - 山羊の午後

4日目。フロントとバックをつないで動かすぞ、という日。
これが、、、大変だった。

ローカルで二つアプリを実行して通信させるだけ、なのですが。
ですが、ネットワーク知識があまりないので、都度ChatGPTに聞きながら、現象を理解しつつエラーを解消していく、というのがなかなか手間でした。

そして、今回、開発環境としてGitHub Codespaceを利用しました。
これがなかなか罠だった。半クラウド環境というか、ローカルとは違って開発環境とリモートで繋がったWeb画面IDEで作業しているので、ネットワーク環境が複雑なんですな。。。

上手くいった手順は以下です。

フロントエンド:React

  • axios.postにバックエンドアプリのURLを設定する
    • CodespaceのVSCodeがポート転送してくれた先のURL
    • 公開範囲をPublicにする
cd frontend
npm run dev -- --host

バックエンド:FastAPI

  • CORSにフロントエンドのURLを設定する
    • CodespaceのVSCodeがポート転送してくれた先のURL
    • 公開範囲をPublicにする
uv run uvicorn backend.main:app --host 0.0.0.0 --port 8000 --reload

CodespaceのVSCodeでポートをPublicにするのは下記。
codespace でのポートの転送 - GitHub Docs
これを見つけるまでなんもわからん、、、の森をぐるぐるまわってしまった。

ChatGPTもこういうのは苦手ですな、新しくてかつ事例が少ないと、事態の正確な把握ができない。
でもWeb検索で↓を出してきてくれたので、だいぶ助かった。
angular - GitHub codespaces CORS issues - Stack Overflow

設定できたところでいよいよ、アプリとしては完成。
フロント画面で入力→生成ボタンを押すとバックエンドでOpenAI APIがコールされ、 生成された結果が返ってくる。
生成中はフロントでスピンがくるくる回ってくれる。そして生成結果が画面にドーン。
やったー。できました!

バックエンド実装と周辺知識のお勉強

https://yaginogogo.hatenablog.jp/entry/2025/07/08/061503

3日目はバックエンドの実装。
バックエンドは経験のあるPythonなので、書こうと思えば書けるけど、企画の趣旨に従って生成AIに出力させる。
作ろうとしているのは、2日目に作ったフロント側でユーザーが入力した情報を受け取り、その内容をOpenAI APIに送って小説を書かせる、という処理になる。
自分で書けるなーと思いながら、LangChainの最新ドキュメントを参照しながらOpenAI APIをコールする関数を書かせる。LangChainは仕様がころころ変わるので、Web検索機能をつかって最新のドキュメントを参照するように指示しないと古い書き方を引っ張ってきてしまい詰む。(これは人間もそう。。。)
MVPではバックエンドでやる処理はこれひとつなので、さくっと出力して完成。

from fastapi import FastAPI
from pydantic import BaseModel
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from backend.models import GenerateRequest
from backend.generate_service import generate_novel
import logging
logging.basicConfig(level=logging.INFO)
from dotenv import load_dotenv
import os

load_dotenv()  # .envファイル読み込み

allowed_origins = os.getenv("ALLOWED_ORIGINS", "")
origins = [origin.strip() for origin in allowed_origins.split(",") if origin.strip()]


app = FastAPI()

# CORS設定(Reactとの接続用)
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,  # フロントのReactのURL
    allow_credentials= False, #True, #
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.post("/generate")
async def generate(request: GenerateRequest):
    logging.info(f"リクエスト受信: {request}")
    try:
        result = await generate_novel(request)
        return {"result": result}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))



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


出してもらったコードの挙動を確認しながら、APIとは何か、HTTPリクエストとは何か、と言う様な基礎的なことを質問して教えてもらう。
そのままCORSの設定についても教えてもらう。
気になるところを順序関係なく深掘りして聞けるのは生成AIラーニングのいいところだなぁ。

さらにFastAPIの公式のドキュメントを読んでAPIについて概要をみる。
FastAPI
読みながらGETとPOSTって本質的になにが違うの、ということが解らなくなりGPTに聞いて整理する。
ChatGPTの説明は読めばわかるのだけれど、本質的な部分がふわふわしているのでさらにWebで適当な記事を探して読み込む。
なんとなく納得できたところで今日はここまで。

新しい知識をこういうこと?とChatGPT相手に壁打ちしながら理解を進めるのは思った以上に効率的だな、という実感。
ついでに思いついたことをその場でコードにつけたさせて試せるのも楽しい。

バックエンドの実装が終わったので、明日はフロントとバックを連動させて動かそう。

フロントエンド実装のスタート

前回 生成AIを使ってゼロからWebサービスをつくる - 山羊の午後

生成AIでゼロからWebサービスを作る、2日目。今日は実装を進めたい。
まずは初めてやるフロントエンドの実装からスタート。とりあえず、ChatGPTに何をしたらいいのか聞く。
UIについて検討しては、というので1日目に決めたMVPの機能リストから、UIのイメージを作ってもらう。
分からないので自分で考えたりはしない。

MVPの機能リストはこれ。(これは自分で決めた)
ちなみに作るものは「小説のワンシーンを出力する生成AIアプリ」である。

  • ユーザー入力を受け付けるフォームがある
  • 実行ボタンがある
  • 実行ボタンを押すとバックエンドで処理が実施される
  • 処理中はスピン画像がくるくるまわる
  • 処理結果が表示される
  • 実行ボタンが再実行の表示に変化する

そして生成された画像がこれ。

MVPの構成要素から生成されたUI

特に問題はないので、じゃあこれでMVPの実装をしてみて、とお願いする。
出てきたのがこちら。

import React, { useState } from 'react'

// スピナー表示コンポーネント
function Spinner() {
  return (
    <div className="flex justify-center mt-4">
      <div className="w-6 h-6 border-4 border-blue-500 border-dashed rounded-full animate-spin" />
    </div>
  )
}

type Inputs = {
  characterName: string
  perspective: 'first' | 'third'
  sceneType: '出会い' | '別れ' | 'クライマックス'
  instruction: string
}

export default function App() {
  const [inputs, setInputs] = useState<Inputs>({
    characterName: '',
    perspective: 'first',
    sceneType: 'クライマックス',
    instruction: ''
  })
  const [isLoading, setIsLoading] = useState(false)
  const [output, setOutput] = useState('')

  const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
    const { name, value } = e.target
    setInputs(prev => ({ ...prev, [name]: value }))
  }

  const handleGenerate = async () => {
    setIsLoading(true)
    setOutput('')
    await new Promise(r => setTimeout(r, 2000))
    setOutput(
      `${inputs.characterName} の「${inputs.sceneType}」シーン。` +
      `一人称? ${inputs.perspective === 'first' ? 'はい' : 'いいえ'}。` +
      `補足: ${inputs.instruction}`
    )
    setIsLoading(false)
  }

  return (
    <div className="min-h-screen bg-gray-100 flex items-center justify-center p-4">
      <div className="max-w-md w-full bg-white rounded-xl shadow p-6 space-y-6">
        <h1 className="text-2xl font-bold text-center">✨ 小説構想アプリ</h1>

        <div className="space-y-4">
          <div>
            <label className="block font-medium">主人公キャラクター名</label>
            <input
              type="text"
              name="characterName"
              value={inputs.characterName}
              onChange={handleChange}
              className="w-full p-2 border rounded focus:outline-blue-500"
            />
          </div>

          <div>
            <label className="block font-medium">視点</label>
            <div className="flex gap-4 mt-1">
              <label>
                <input
                  type="radio"
                  name="perspective"
                  value="first"
                  checked={inputs.perspective === 'first'}
                  onChange={handleChange}
                /> 一人称
              </label>
              <label>
                <input
                  type="radio"
                  name="perspective"
                  value="third"
                  checked={inputs.perspective === 'third'}
                  onChange={handleChange}
                /> 三人称
              </label>
            </div>
          </div>

          <div>
            <label className="block font-medium">展開</label>
            <select
              name="sceneType"
              value={inputs.sceneType}
              onChange={handleChange}
              className="w-full p-2 border rounded focus:outline-blue-500"
            >
              <option value="出会い">出会い</option>
              <option value="別れ">別れ</option>
              <option value="クライマックス">クライマックス</option>
            </select>
          </div>

          <div>
            <label className="block font-medium">補足指示(30字以内)</label>
            <input
              type="text"
              name="instruction"
              value={inputs.instruction}
              maxLength={30}
              onChange={handleChange}
              className="w-full p-2 border rounded focus:outline-blue-500"
            />
          </div>
        </div>

        <button
          onClick={handleGenerate}
          disabled={isLoading}
          className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 disabled:opacity-50"
        >
          {isLoading ? '生成中...' : output ? '再生成' : '生成開始'}
        </button>

        {isLoading && <Spinner />}

        {output && (
          <div className="mt-6 p-4 bg-gray-100 rounded shadow">
            <p className="whitespace-pre-wrap">{output}</p>
          </div>
        )}
      </div>
    </div>
  )
}

できた~。
割愛するが、一度の指示で完成したわけではなく、細かいところを修正しながら6回くらい生成させた。
しかし自分ではコードをまったく書いていない。
なのにちゃんと動くものになったのですばらしい。
つづいて、出来上がったコードの部品が何なのかを解説させて、Reactとはどんなものなのかを理解していく。
なんとなくわかった段階で、Reactの公式のチュートリアルを読む。
クイックスタート – React
UI の記述 – React
ふーん、と理解が進んだところで今日はここまで。

生成AIを使ってゼロからWebサービスをつくる

生成AIが何でも教えてくれる世界観がやってきた2025。
世間ではVibe Codingなる概念が誕生し、CodingとはAIがやるもの、という世界が近づいてきています。
でも本当に、全部AIがやってくれるのか? 実務でAIコーディングを使っている感覚ではだいぶ半信半疑なところもあり、検証もかねてWebサービスを作ってみました。

忙しい人のためのまとめ
  • React+FastAPIのWebサービスを企画~MVPのデプロイまで7日間で達成
  • 勉強になったこと:React, Python(FastAPI, uvicorn), APIとはなにか(HTTPリクエスト, REST, CORS), デプロイとはなにか(Docker, GitHub連携によるCI/CD)
  • 感想
    • AIすごい。けど結局最後のツメは自分が頑張らないとだめっぽい。
    • 生成AIと新技術学ぶのは楽しい。
前提条件
  • 実行者はR, MATLAB, Pythonなどを日々利用しており、プログラミングには慣れている
  • 実行者はWebサービス業界で働く知人がおり、Webサービスとはどんなものか聞きかじった知識がある
  • 生成AIとしてはChatGPT 4oを利用した(ChatGPT+課金勢)。
    • 専用のプロジェクトを作成し、話題ごとにchatを作成。
    • メモリを使って覚えていて欲しいことは記憶させつつ実行した。
  • 実行者は多忙な業務の中で自分でWebサービスを公開して一攫千金を狙う夢追いたかった

一日目:企画と技術選定をする

とりあえずどんなものを作るか、自分が現状どんなスキル感で何を実現したいかを生成AIに伝え、何をやれば良いのかスケジュールとマイルストーンを作ってもらう。
作業時間が、平日の業務後と土日の夜のみということも伝え、なるべく簡潔に目標に到達できるステップを出してもらう。

ChatGPTが作成したマイルストーン
スケジュール案

スケジュールではまずアプリのアイデアだしと技術選定をする、となっていたのでそのまま作りたいアプリについて相談する。
今回は自分の学習がメインなので、完成まで興味が持てそうなもの、にしようと思う。
自分の興味・関心・趣味を伝えつつWebアプリにするアイデアを相談する。雑に会話してもなんとなくそれっぽいアイデアにまとめてくれるので、AIはえらいな、と思いながらボールを投げ続ける。
なんとなく作ってみたいものが決まったので、サービスの名前も考えてもらう。名前があると途端にプロダクトっぽくなるのでイイ。
イデアだしをする中で、機能の優先順位を決めて、まずは小さめのMVPを作ってみるとよい、とChatGPTが言うのでMVP(実用最小限の製品 Minimum Viable Product)とは何かを教わる。

スケジュール案では次のステップは「Web技術の基礎を習得」になっていたが、作って学ぶほうが好きなので「先にMVPを作りたい」と持ち掛ける。あっさり了承されるので、そのままMVPの内容を決める。
シンプルに以下とした

  • ユーザー入力を受け付けるフォームがある
  • 実行ボタンがある
  • 実行ボタンを押すとバックエンドで処理が実施される
  • 処理中はスピン画像がくるくるまわる
  • 処理結果が表示される
  • 実行ボタンが再実行の表示に変化する

とりあえずフロントエンドとバックエンド、という二つの概念を理解することが目標だな、という感じ。

MVPが決まったので、これを作るなら、という前提でそのまま技術選定に入る。
Pythonを書けるので、バックエンドはPythonでいこう、ということは早々に決まった。フレームワークとして何を使うか相談してDjango、FastAPI、Flaskの比較をしてもらう。今回はそこまで大規模な処理を作らない、という前提でFastAPIに決まる。

フロントエンドはReactというのが有名らしい、と知っていたので、とりあえずReactで良いか聞く。ほかのフレームワークも説明してもらったが、なんとなくやってみたい、という理由でReactに挑戦することにした。

続いて開発環境について相談する。
ローカル環境は実務に使うこともあり、こういう遊びで汚したくない。何かクラウドサービスで使えるものがないか聞くと、GitHubのcodespaceが良さそうだった。
Codespaces | GitHub
いつでもどこでもVS Codeが利用できるGitHub Codespaces

さっそくアカウントを作って使う。いつも使っているVS Codeとほぼ一緒なので使いやすい。

そのままReactとPythonの環境構築についても聞く。
そもそも同じリポジトリ、開発環境でフロントとバックを管理できるのか?分けたほうがいいのか?というところから質問。
モノレポ(Monorepo)という概念を教えてもらう。
モノレポって結局どうなの?という話 #フロントエンド - Qiita

そのままリポジトリのフォルダ構成についても教えてくれたので、言われるがままに下記の様に構成していく。

/app/
├─ frontend/ ← React(create-react-app)
└─ backend/ ← FastAPI(main.py, requirements.txt)

つづいて環境構築に入る。
Reactは生成AIが出してくる環境構築方法が微妙に古い手続きで上手くいかず、結局検索した結果と公式サイトを見てやる。
試しにPythonをuvで構築したいと言うと*1微妙に古い手続きを出してきたので、「こいつ、枯れてる知識以外は微妙だな」ということに気づく。
そのため以降、できるだけ最新の知見を反映するようにWebサーチをONにして「Web検索をして最新情報を参照しながら答えて」と指示するようになる。
しかしWeb検索結果がノイズに汚染されているとこの手法でもうまくいかず、ChatGPTがだしてきた「こうできますよ!」の記述をたよりに自分でWeb検索して正解を見つける、という手続きが必要になってくる。
手間のかかる子だなあ、という感じだが、こっちは検索する手がかりもない状態なので、ないよりは全然いい。
そして調べた結果について「これのこと?」と貼り付けて聞くとどや顔で「それです!」的なことを言うの、あほっぽくてかわいい奴だな、と思う。
この辺でこいつに長時間付き合うには愛嬌が必要だなと思い、キャラクター的な名称をつける。そうすることでやり取りが多少ダメでも許せるようになった。

一日目は環境構築を終えて終わり。

React環境構築

【React入門】忙しい人のためのReactチュートリアル【2025年更新版】 #TypeScript - Qiita
ゼロからの React アプリ構築 – React

  • cd frontend
  • nodeが入っているか確認 node -v
  • npmが入っているか確認 npm -v
  • viteを入れる npm create vite@latest
    • Project nameを入力し、React, TypeScript + SWC を選択
    • プロジェクトのフォルダが出来上がる
  • npm install を実行
  • npm run dev を実行
    • サンプルページが起動したらOK
Python環境構築

Installation | uv
Using uv with FastAPI | uv


*1:uvは使い慣れているので手順がわかる