毎日の気分を記録できる楽しいWeb APIを作ってみましょう! 絵文字を使って、その日の気分を5段階で記録できるシンプルなAPIです。 さらに、Chrome開発者ツールを使ってネットワーク通信の仕組みも学びます!
mood-tracker/
├── docker-compose.yml
├── Dockerfile
├── app.py
├── requirements.txt
├── static/
│ └── index.html
└── templates/requirements.txtFlask==3.0.0
flask-cors==4.0.0DockerfileFROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]docker-compose.ymlversion: '3.8'
services:
mood-api:
build: .
ports:
- "5000:5000"
volumes:
- .:/app
environment:
- FLASK_ENV=developmentapp.pyfrom 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)static/index.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>mkdir mood-tracker
cd mood-tracker
mkdir static上記のファイルをそれぞれ作成・配置します。
docker-compose up --buildhttp://localhost:5000 にアクセスしてみましょう!
F12 または Ctrl + Shift + ICmd + Option + I上部のタブから「Network」(ネットワーク)をクリック
moods (リクエストしたエンドポイント)200 (成功)fetch または xhrmoods リクエストをクリックRequest URL: http://localhost:5000/api/moods
Request Method: POST
Status Code: 201 Created
Content-Type: application/json送信したデータを確認:
{
"score": 5,
"note": "プログラミングが楽しい!"
}サーバーからの返答:
{
"message": "気分を記録しました!",
"mood": {
"id": 1,
"date": "2024-01-15 14:30:00",
"score": 5,
"emoji": "😄 最高!",
"note": "プログラミングが楽しい!"
}
}console.log() の出力を確認開発者ツールで見た内容をcurlでも確認:
curl -X POST http://localhost:5000/api/moods \
-H "Content-Type: application/json" \
-d '{"score": 5, "note": "curlからテスト!"}' \
-v-v オプションでヘッダー情報も表示されます!
curl http://localhost:5000/api/moods -vContent-Length を見つけてみようCtrl+Shift+C: 要素選択モードCtrl+Shift+M: レスポンシブデザインモードCtrl+R: リロード(キャッシュクリア: Ctrl+Shift+R)これで以下のスキルが身につきました:
次のステップ:
楽しいプログラミングライフを! 🚀