【備忘録】ハッカソンに向けて環境構築してみた

## はじめに
スクールのハッカソンに向けて環境構築したときの備忘録です。

## 構成
フロント用とバックエンドでリポジトリを分けました。
フロントはNext.js,バックエンドはFastAPIを使います。

完成形はこちら

```ruby:フロントリポジトリのディレクトリ構成
frontend/
├── docker-compose.yml       # Docker設定（開発環境）
├── Dockerfile               # アプリ用Dockerイメージ定義
├── eslint.config.mjs        # ESLint 設定
├── Makefile                 # よく使うコマンドの簡略化
├── next.config.ts           # Next.js 設定
├── package.json             # 依存ライブラリ定義
├── postcss.config.mjs       # Tailwind CSS等のPostCSS設定
├── public/                  # 公開アセット（画像等）
│   ├── *.svg
├── README.md                # プロジェクト概要（このファイル）
├── src/
│   └── app/
│       ├── favicon.ico      # ブラウザアイコン
│       ├── globals.css      # 全体スタイル
│       ├── layout.tsx       # レイアウトコンポーネント
│       └── page.tsx         # トップページ
└── tsconfig.json            # TypeScript 設定
```



```ruby:
backend/
├── app
│   ├── __pycache__             # Python のキャッシュファイル（無視可）
│   ├── main.py                 # アプリケーションのメインスクリプト
│   └── requirements.txt        # Python の依存ライブラリ定義
├── docker-compose.yml          # Docker Compose 設定ファイル
├── Dockerfile                  # Docker イメージビルド定義
└── Makefile                    # 開発用コマンドまとめ
```


## Next.js + Tailwind CSS の開発環境構築

:::note info
npxを実行するためNode.jsのインストールが必要です。
[公式サイト](https://nodejs.org/ja)からLTS（推奨版）をダウンロードし、インストールしてください。

インストールしたら下記コマンドでバージョンが表示されるか確認します
```
node -v
npm -v
```
:::

準備が整ったらlet's環境構築です

```ruby:コマンド実行：フォルダを作成
mkdir frontend
cd frontend
```

```ruby:ファイル作成：フロント用 Dockerfile
# Dockerfile
FROM node:18-alpine

# 作業ディレクトリを作成
WORKDIR /app

# 依存ファイルを先にコピーしてインストール
COPY package.json package-lock.json ./
RUN npm install

# 残りのファイルをコピー
COPY . .

# Next.js のデフォルトポートを開放
EXPOSE 3000

# devサーバー起動（ホットリロード対応）
CMD ["npm", "run", "dev"]
```

```ruby:ファイル作成：フロント用 docker-compose.yml
# docker-compose.yml
version: '3.8'
services:
  next-app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - CHOKIDAR_USEPOLLING=true # ホットリロード
```

```ruby:ファイル作成：.dockerignore
node_modules
npm-debug.log
```

```ruby:コマンド実行：
docker compose run --rm nextjs npx create-next-app@latest . --typescript
```
:::note info
**コマンドの意味**
Docker Compose の nextjs サービスを使って、
npx create-next-app@latest . --typescript を実行し、現在のディレクトリに TypeScript対応の Next.js アプリ が作る。
実行後は使ったコンテナが消える（--rm のおかげ）。
:::


:::note warn
「Dockerfileに[COPY package.json package-lock.json ./]があるのにコピー元のpackage.jsonがないよ」とエラー出ました
> ERROR [nextjs 3/5] COPY package.json package-lock.json  0.0s
 > [nextjs 3/5] COPY package.json package-lock.json ./:
failed to solve: failed to compute cache key: failed to calculate checksum of ref n006mml8u3hi4nnbxtk19mjga::mphnroaykwtth4rgglf02sv66: "/package.json": not found


**対応**
一旦作成したファイルを一掃したあと下記のコマンドを実行
```ruby:ローカルにNext.jsプロジェクトを作成
npx create-next-app@latest next-app
cd next-app
```

**もしUnexpected token '??=' のエラーが出た場合**
Node.js のバージョンを新しくしてください

```ruby:nvm をインストール（初めてなら）
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

```

```nvmの読み込み（または .bashrc / .zshrc に追記）
export NVM_DIR="$HOME/.nvm"
source "$NVM_DIR/nvm.sh"
```
```ruby:Node.js の新しいバージョンをインストール（例：v22.7.0）
nvm install 22.7.0
```
```ruby:再度Next.jsプロジェクトを作成
npx create-next-app@latest next-app
```

もう一度、Dockerifleとdocker-compose.ymlファイルを作成
:::

```ruby:コンテナをビルドして起動
docker compose up --build
```
ブラウザで[http://localhost:3000]にアクセス

### ついでにMakefile(コマンドマイ辞書みたいなやつ)も作る
```ruby:Makefile
up:
    docker compose up --build

up-d:
    docker compose up -d

ps:
    docker compose ps

logs:
    docker compose logs

exec:
    docker compose exec -it next-app bash

down:
    docker compose down

```

## FastAPI + MySQL の開発環境
```ruby:requirements.txt
fastapi
uvicorn[standard]
mysql-connector-python
```
```ruby:.env
MYSQL_ROOT_PASSWORD=ルートパスワード
MYSQL_DATABASE=データベース名
MYSQL_USER=DBユーザー名
MYSQL_PASSWORD=DBログインパスワード
```
```ruby:main.py
from fastapi import FastAPI
import mysql.connector
import os

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello from FastAPI + MySQL in Docker!"}

@app.get("/db-status")
def db_status():
    try:
        connection = mysql.connector.connect(
            host="db",  # コンテナ名
            user=os.getenv("MYSQL_USER"),
            password=os.getenv("MYSQL_PASSWORD"),
            database=os.getenv("MYSQL_DATABASE")
        )
        if connection.is_connected():
            return {"db_status": "connected"}
    except Exception as e:
        return {"db_status": "error", "details": str(e)}
```

```ruby:Dockerfile
FROM python:3.11-slim

WORKDIR /app

COPY app/requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY app/ .

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
```

```ruby:docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    container_name: fastapi-app
    ports:
      - "8000:8000"
    volumes:
      - ./app:/app
    env_file:
      - .env
    depends_on:
      - db
    command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload

  db:
    image: mysql:8.0
    container_name: mysql-db
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    ports:
      - "3306:3306"
    volumes:
      - db-data:/var/lib/mysql

volumes:
  db-data:
```

```ruby:コンテナをビルドして起動
docker compose up --build
```
- アプリ：
http://localhost:8000/ → {"message": "Hello from FastAPI + MySQL in Docker!"}

- DB接続確認：
http://localhost:8000/db-status → {"db_status": "connected"}（成功時）



## Next.jsとFastAPIの接続
```ruby:frontend/src/app/page.tsx
"use client";

import { useEffect, useState } from "react";
import Image from "next/image";

export default function Home() {
  const [message, setMessage] = useState("Loading...");

  useEffect(() => {
    fetch("http://localhost:8000/") // FastAPI の URL に合わせて変更
      .then((res) => res.json())
      .then((data) => setMessage(data.message))
      .catch(() => setMessage("Error fetching message from FastAPI."));
  }, []);

  return (
      <>
        {/* 🔽 FastAPI のメッセージ表示部分 🔽 */}
        <div className="text-lg text-center sm:text-left font-bold text-blue-600">
          {message}
        </div>
    </>
  );
}

```


```backend/app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import mysql.connector
import os

app = FastAPI()

# ✅ CORS設定（Next.jsからアクセス可能にする）
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 必要なら "http://localhost:3000" などに限定
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get("/")
~略~
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3999989/a6908f8c-868e-418c-bedd-6c38d4ff557a.png)