Content is user-generated and unverified.

🌈 今日の気分トラッカーAPI を作ろう!

📝 課題の概要

毎日の気分を記録できる楽しいWeb APIを作ってみましょう! 絵文字を使って、その日の気分を5段階で記録できるシンプルなAPIです。 さらに、Chrome開発者ツールを使ってネットワーク通信の仕組みも学びます!

完成イメージ

  • 😄 最高!(5点)
  • 😊 良い感じ(4点)
  • 😐 まあまあ(3点)
  • 😕 ちょっと疲れた(2点)
  • 😢 つらい...(1点)

🎯 学習目標

  1. DockerとDocker Composeの基本を理解する
  2. FlaskでシンプルなAPIを作る
  3. HTTPメソッド(GET/POST)を実践で学ぶ
  4. JSONでデータをやり取りする
  5. Chrome開発者ツールでネットワーク通信を確認する

📁 プロジェクト構造

mood-tracker/
├── docker-compose.yml
├── Dockerfile
├── app.py
├── requirements.txt
├── static/
│   └── index.html
└── templates/

🚀 ステップ1: ファイルを作成しよう

1. requirements.txt

txt
Flask==3.0.0
flask-cors==4.0.0

2. Dockerfile

dockerfile
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

CMD ["python", "app.py"]

3. docker-compose.yml

yaml
version: '3.8'

services:
  mood-api:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/app
    environment:
      - FLASK_ENV=development

4. app.py

python
from flask import Flask, jsonify, request, send_from_directory
from flask_cors import CORS
from datetime import datetime

app = Flask(__name__)
CORS(app)  # ブラウザからのアクセスを許可

# 気分のデータを保存するリスト(本来はデータベースを使います)
moods = []

# 絵文字と気分の対応
MOOD_EMOJIS = {
    5: "😄 最高!",
    4: "😊 良い感じ",
    3: "😐 まあまあ",
    2: "😕 ちょっと疲れた",
    1: "😢 つらい..."
}

# HTMLページを表示
@app.route('/')
def index():
    return send_from_directory('static', 'index.html')

# API情報
@app.route('/api')
def api_info():
    return jsonify({
        "message": "🌈 気分トラッカーAPIへようこそ!",
        "endpoints": {
            "GET /api/moods": "すべての気分記録を見る",
            "POST /api/moods": "新しい気分を記録する",
            "GET /api/today": "今日の気分を見る"
        }
    })

# すべての気分記録を取得
@app.route('/api/moods', methods=['GET'])
def get_all_moods():
    return jsonify({
        "moods": moods,
        "total": len(moods)
    })

# 新しい気分を記録
@app.route('/api/moods', methods=['POST'])
def add_mood():
    data = request.get_json()
    
    # スコアのチェック
    score = data.get('score')
    if not score or score not in range(1, 6):
        return jsonify({"error": "スコアは1〜5の数字で入力してください"}), 400
    
    # 新しい気分記録を作成
    new_mood = {
        "id": len(moods) + 1,
        "date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "score": score,
        "emoji": MOOD_EMOJIS[score],
        "note": data.get('note', '')  # メモ(オプション)
    }
    
    moods.append(new_mood)
    
    return jsonify({
        "message": "気分を記録しました!",
        "mood": new_mood
    }), 201

# 今日の気分を取得
@app.route('/api/today', methods=['GET'])
def get_today_mood():
    today = datetime.now().strftime("%Y-%m-%d")
    today_moods = [m for m in moods if m['date'].startswith(today)]
    
    if not today_moods:
        return jsonify({"message": "今日はまだ記録がありません"})
    
    # 今日の平均スコアを計算
    avg_score = sum(m['score'] for m in today_moods) / len(today_moods)
    
    return jsonify({
        "date": today,
        "records": today_moods,
        "average_score": round(avg_score, 1),
        "summary": MOOD_EMOJIS[round(avg_score)]
    })

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

5. static/index.html

html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>気分トラッカー</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f0f0f0;
        }
        .container {
            background: white;
            border-radius: 10px;
            padding: 30px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        h1 {
            color: #333;
            text-align: center;
        }
        .mood-selector {
            display: flex;
            justify-content: space-around;
            margin: 30px 0;
        }
        .mood-button {
            font-size: 2em;
            padding: 10px 20px;
            border: none;
            background: #f8f8f8;
            border-radius: 10px;
            cursor: pointer;
            transition: all 0.3s;
        }
        .mood-button:hover {
            background: #e0e0e0;
            transform: scale(1.1);
        }
        .mood-button.selected {
            background: #4CAF50;
            color: white;
        }
        textarea {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            resize: vertical;
        }
        button {
            background: #4CAF50;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
        }
        button:hover {
            background: #45a049;
        }
        .records {
            margin-top: 30px;
        }
        .record {
            background: #f8f8f8;
            padding: 10px;
            margin: 10px 0;
            border-radius: 5px;
        }
        .developer-tip {
            background: #fff3cd;
            border: 1px solid #ffeaa7;
            padding: 15px;
            border-radius: 5px;
            margin: 20px 0;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🌈 今日の気分はどう?</h1>
        
        <div class="developer-tip">
            💡 <strong>開発者ツールを開こう!</strong><br>
            F12キー(またはCtrl+Shift+I)を押して、「ネットワーク」タブを開いてみよう!
        </div>

        <div class="mood-selector">
            <button class="mood-button" data-score="1">😢</button>
            <button class="mood-button" data-score="2">😕</button>
            <button class="mood-button" data-score="3">😐</button>
            <button class="mood-button" data-score="4">😊</button>
            <button class="mood-button" data-score="5">😄</button>
        </div>

        <div>
            <h3>メモ(任意)</h3>
            <textarea id="note" rows="3" placeholder="今日はどんな日だった?"></textarea>
        </div>

        <div style="text-align: center; margin: 20px 0;">
            <button onclick="submitMood()">記録する</button>
            <button onclick="loadMoods()">記録を見る</button>
            <button onclick="loadToday()">今日の気分</button>
        </div>

        <div id="message" style="text-align: center; color: #4CAF50; margin: 10px 0;"></div>

        <div id="records" class="records"></div>
    </div>

    <script>
        let selectedScore = null;

        // 気分ボタンのクリックイベント
        document.querySelectorAll('.mood-button').forEach(button => {
            button.addEventListener('click', function() {
                document.querySelectorAll('.mood-button').forEach(b => b.classList.remove('selected'));
                this.classList.add('selected');
                selectedScore = parseInt(this.dataset.score);
                console.log('選択されたスコア:', selectedScore);
            });
        });

        // 気分を送信
        async function submitMood() {
            if (!selectedScore) {
                alert('気分を選んでください!');
                return;
            }

            const note = document.getElementById('note').value;
            
            console.log('送信データ:', { score: selectedScore, note: note });

            try {
                const response = await fetch('/api/moods', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        score: selectedScore,
                        note: note
                    })
                });

                const data = await response.json();
                console.log('レスポンス:', data);

                document.getElementById('message').textContent = data.message;
                document.getElementById('note').value = '';
                
                // 選択をリセット
                document.querySelectorAll('.mood-button').forEach(b => b.classList.remove('selected'));
                selectedScore = null;

            } catch (error) {
                console.error('エラー:', error);
                alert('エラーが発生しました');
            }
        }

        // すべての記録を読み込む
        async function loadMoods() {
            console.log('記録を取得中...');
            
            try {
                const response = await fetch('/api/moods');
                const data = await response.json();
                console.log('取得したデータ:', data);

                const recordsDiv = document.getElementById('records');
                recordsDiv.innerHTML = '<h3>📝 すべての記録</h3>';

                if (data.moods.length === 0) {
                    recordsDiv.innerHTML += '<p>まだ記録がありません</p>';
                } else {
                    data.moods.forEach(mood => {
                        recordsDiv.innerHTML += `
                            <div class="record">
                                <strong>${mood.emoji}</strong> - ${mood.date}
                                ${mood.note ? `<br>メモ: ${mood.note}` : ''}
                            </div>
                        `;
                    });
                }
            } catch (error) {
                console.error('エラー:', error);
            }
        }

        // 今日の気分を読み込む
        async function loadToday() {
            console.log('今日の気分を取得中...');
            
            try {
                const response = await fetch('/api/today');
                const data = await response.json();
                console.log('今日のデータ:', data);

                const recordsDiv = document.getElementById('records');
                
                if (data.records) {
                    recordsDiv.innerHTML = `
                        <h3>📊 今日の気分サマリー</h3>
                        <p>平均スコア: ${data.average_score} - ${data.summary}</p>
                        <p>記録数: ${data.records.length}件</p>
                    `;
                    
                    data.records.forEach(mood => {
                        recordsDiv.innerHTML += `
                            <div class="record">
                                ${mood.emoji} - ${mood.date}
                                ${mood.note ? `<br>メモ: ${mood.note}` : ''}
                            </div>
                        `;
                    });
                } else {
                    recordsDiv.innerHTML = '<p>' + data.message + '</p>';
                }
            } catch (error) {
                console.error('エラー:', error);
            }
        }
    </script>
</body>
</html>

🏃 ステップ2: 実行してみよう!

1. プロジェクトフォルダの作成

bash
mkdir mood-tracker
cd mood-tracker
mkdir static

2. ファイルを配置

上記のファイルをそれぞれ作成・配置します。

3. Docker Composeでアプリを起動

bash
docker-compose up --build

4. ブラウザでアクセス

http://localhost:5000 にアクセスしてみましょう!

🔍 ステップ3: Chrome開発者ツールでネットワークを確認しよう!

📖 開発者ツールの基本

1. 開発者ツールを開く

  • Windows/Linux: F12 または Ctrl + Shift + I
  • Mac: Cmd + Option + I
  • または、ページ上で右クリック → 「検証」

2. ネットワークタブを選択

上部のタブから「Network」(ネットワーク)をクリック

🎯 実践:APIリクエストを観察しよう

演習1: GETリクエストを見てみよう

  1. 開発者ツールの「ネットワーク」タブを開く
  2. 「記録を見る」ボタンをクリック
  3. ネットワークタブに表示される内容を確認:
    • Name: moods (リクエストしたエンドポイント)
    • Status: 200 (成功)
    • Type: fetch または xhr
    • Time: レスポンスまでの時間

演習2: POSTリクエストを詳しく見よう

  1. 気分の絵文字を選択
  2. メモを入力
  3. 「記録する」ボタンをクリック
  4. ネットワークタブで moods リクエストをクリック
  5. 以下のタブを確認:
Headers(ヘッダー)タブ
Request URL: http://localhost:5000/api/moods
Request Method: POST
Status Code: 201 Created
Content-Type: application/json
Payload(ペイロード)タブ

送信したデータを確認:

json
{
  "score": 5,
  "note": "プログラミングが楽しい!"
}
Response(レスポンス)タブ

サーバーからの返答:

json
{
  "message": "気分を記録しました!",
  "mood": {
    "id": 1,
    "date": "2024-01-15 14:30:00",
    "score": 5,
    "emoji": "😄 最高!",
    "note": "プログラミングが楽しい!"
  }
}

🔬 開発者ツールで確認すべきポイント

1. ステータスコード

  • 200: OK(成功)
  • 201: Created(作成成功)
  • 400: Bad Request(リクエストエラー)
  • 404: Not Found(見つからない)
  • 500: Internal Server Error(サーバーエラー)

2. リクエストメソッド

  • GET: データの取得
  • POST: データの作成
  • PUT: データの更新
  • DELETE: データの削除

3. レスポンスタイム

  • APIのパフォーマンスを確認
  • 遅い場合は最適化が必要かも

💡 デバッグのヒント

コンソールタブも活用しよう

  1. 「Console」タブを開く
  2. JavaScriptの console.log() の出力を確認
  3. エラーメッセージも表示される

ネットワークエラーの見方

  • 赤色で表示されるリクエストはエラー
  • クリックして詳細を確認
  • CORSエラーなどもここで分かる

🧪 ステップ4: curlコマンドでもテストしてみよう!

開発者ツールで見た内容をcurlでも確認:

1. 気分を記録する(POST)

bash
curl -X POST http://localhost:5000/api/moods \
  -H "Content-Type: application/json" \
  -d '{"score": 5, "note": "curlからテスト!"}' \
  -v

-v オプションでヘッダー情報も表示されます!

2. 記録を確認(GET)

bash
curl http://localhost:5000/api/moods -v

🎮 チャレンジ課題

レベル1: 開発者ツールマスター 🌟

  1. ネットワークタブで「Preserve log」をチェックして、ページ遷移後もログを保持してみよう
  2. フィルター機能を使って、XHR/Fetchリクエストだけを表示してみよう
  3. レスポンスヘッダーから Content-Length を見つけてみよう

レベル2: エラーハンドリング 🌟🌟

  1. わざと間違ったスコア(6や0)を送信して、400エラーを確認しよう
  2. 存在しないエンドポイント(/api/unknown)にアクセスして404を確認しよう
  3. エラー時の表示を改善してみよう

レベル3: パフォーマンス分析 🌟🌟🌟

  1. ネットワークタブの「Waterfall」を見て、どの処理に時間がかかっているか分析しよう
  2. 「Slow 3G」などのネットワークスロットリングを試してみよう
  3. キャッシュの動作を確認してみよう

📚 開発者ツールの追加学習

その他の便利な機能

  1. Elements: HTMLとCSSの確認・編集
  2. Sources: JavaScriptのデバッグ
  3. Application: LocalStorage、Cookie、Cache等の確認
  4. Performance: パフォーマンス分析
  5. Lighthouse: サイトの品質チェック

ショートカットキー

  • Ctrl+Shift+C: 要素選択モード
  • Ctrl+Shift+M: レスポンシブデザインモード
  • Ctrl+R: リロード(キャッシュクリア: Ctrl+Shift+R

🎉 完成おめでとう!

これで以下のスキルが身につきました:

  • ✅ REST APIの基本(GET/POST)
  • ✅ Dockerを使った開発環境構築
  • ✅ Chrome開発者ツールでのネットワーク分析
  • ✅ HTTPステータスコードの理解
  • ✅ リクエスト/レスポンスの仕組み

次のステップ:

  • WebSocketを使ったリアルタイム通信
  • 認証機能の実装
  • データベースの導入
  • CI/CDパイプラインの構築

楽しいプログラミングライフを! 🚀

Content is user-generated and unverified.
    🌈 今日の気分トラッカーAPI を作ろう! | Claude