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