Practice and reinforce the concepts from Lesson 11
In this activity, you'll:
Total time: 45-60 minutes
What you'll do: Access the API builder template Time: 5 minutes
Access the template here: Quest Tracker API Template
git clone
and navigate to the template foldernpm install
in the template foldernpm start
to launch your APIserver.js
- Your API server code goes herepackage.json
- Dependencies and scriptsroutes/
- API route definitionsdata/
- Sample quest data filestest.html
- API testing interface💡 Tip: After starting the server, open
test.html
in your browser to test your API!
What you'll do: Set up your API server foundation Time: 15 minutes
In server.js
, add this code:
const express = require('express');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware setup
app.use(cors());
app.use(express.json());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: {
error: 'Too many requests from this IP',
retryAfter: '15 minutes'
}
});
app.use('/api/', limiter);
// API Routes
app.use('/api/quests', require('./routes/quests'));
app.use('/api/players', require('./routes/players'));
app.use('/api/categories', require('./routes/categories'));
// Root endpoint
app.get('/', (req, res) => {
res.json({
message: 'Quest Tracker API v1.0',
documentation: '/api/docs',
endpoints: {
quests: '/api/quests',
players: '/api/players/:username',
categories: '/api/categories'
},
version: '1.0.0',
status: 'active'
});
});
// API documentation endpoint
app.get('/api/docs', (req, res) => {
res.json({
title: 'Quest Tracker API Documentation',
version: '1.0.0',
description: 'A gamified todo/quest management API',
endpoints: [
{
method: 'GET',
path: '/api/quests',
description: 'Get all quests with optional filtering',
parameters: {
status: 'Quest status (optional): pending, in_progress, completed',
priority: 'Priority level (optional): low, medium, high, critical'
},
example: '/api/quests?status=pending&priority=high'
},
{
method: 'GET',
path: '/api/quests/:id',
description: 'Get specific quest by ID',
parameters: {
id: 'Quest ID (required)'
},
example: '/api/quests/1'
},
{
method: 'POST',
path: '/api/quests/:id/complete',
description: 'Mark a quest as completed',
parameters: {
id: 'Quest ID (required)'
},
example: 'POST /api/quests/1/complete'
},
{
method: 'GET',
path: '/api/players/:username',
description: 'Get player profile and quest statistics',
parameters: {
username: 'Player username (required)'
},
example: '/api/players/alex'
}
],
authentication: {
type: 'API Key',
header: 'X-API-Key',
note: 'Contact support for API key'
},
rate_limits: {
requests_per_15_minutes: 100,
burst_limit: 10
}
});
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: 'Internal server error',
message: 'Something went wrong on our end'
});
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({
error: 'Endpoint not found',
message: `Cannot ${req.method} ${req.originalUrl}`,
available_endpoints: [
'/api/quests',
'/api/quests/:id',
'/api/players/:username',
'/api/categories'
]
});
});
// Start server
app.listen(PORT, () => {
console.log(`⚡ Quest Tracker API server running on port ${PORT}`);
console.log(`📖 Documentation: http://localhost:${PORT}/api/docs`);
});
module.exports = app;
Click "Open in New Tab" in StackBlitz to see your API running. You should see the welcome message with API information.
What you'll do: Build actual quest data endpoints Time: 25 minutes
Create routes/quests.js
:
const express = require('express');
const router = express.Router();
// Sample Quest data - in real app, this would come from a database
const questsData = [
{
id: 1,
title: 'Complete Morning Workout',
description: 'Do 30 minutes of exercise to start the day strong',
status: 'pending',
priority: 'medium',
category: 'health',
xp_reward: 50,
deadline: '2024-01-15T09:00:00Z',
estimated_time: 30,
created_at: '2024-01-14T06:00:00Z'
},
{
id: 2,
title: 'Finish Project Report',
description: 'Write the final report for the quarterly project review',
status: 'in_progress',
priority: 'high',
category: 'work',
xp_reward: 100,
deadline: '2024-01-16T17:00:00Z',
estimated_time: 120,
created_at: '2024-01-10T09:00:00Z'
},
{
id: 3,
title: 'Learn New Recipe',
description: 'Try cooking a new dish from the cookbook',
status: 'completed',
priority: 'low',
category: 'personal',
xp_reward: 25,
deadline: '2024-01-14T19:00:00Z',
estimated_time: 60,
created_at: '2024-01-13T10:00:00Z',
completed_at: '2024-01-14T18:30:00Z'
},
{
id: 4,
title: 'Call Mom',
description: 'Weekly check-in call with family',
status: 'pending',
priority: 'medium',
category: 'personal',
xp_reward: 30,
deadline: '2024-01-17T20:00:00Z',
estimated_time: 20,
created_at: '2024-01-14T08:00:00Z'
},
{
id: 5,
title: 'Debug Login Issue',
description: 'Fix the authentication bug reported by users',
status: 'pending',
priority: 'critical',
category: 'work',
xp_reward: 150,
deadline: '2024-01-15T12:00:00Z',
estimated_time: 90,
created_at: '2024-01-14T14:00:00Z'
}
];
// GET /api/quests/:id
router.get('/:id', (req, res) => {
try {
const questId = parseInt(req.params.id);
// Check if quest exists
const quest = questsData.find(q => q.id === questId);
if (!quest) {
return res.status(404).json({
error: 'Quest not found',
message: `Quest with ID ${req.params.id} not found`,
available_quests: questsData.map(q => ({ id: q.id, title: q.title })),
suggestion: 'Check the quest ID or browse all quests'
});
}
// Add calculated fields
const questData = { ...quest };
// Calculate days until deadline
if (quest.deadline) {
const daysLeft = Math.ceil((new Date(quest.deadline) - new Date()) / (1000 * 60 * 60 * 24));
questData.days_until_deadline = daysLeft;
questData.is_urgent = daysLeft <= 1 && quest.status !== 'completed';
}
// Add API metadata
questData.api_info = {
endpoint: req.originalUrl,
response_time: new Date().toISOString(),
data_source: 'Quest Tracker API v1.0'
};
res.json(questData);
} catch (error) {
console.error('Quest endpoint error:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Failed to fetch quest data'
});
}
});
// GET /api/quests (list all quests with filtering)
router.get('/', (req, res) => {
try {
const status = req.query.status;
const priority = req.query.priority;
const category = req.query.category;
let filteredQuests = questsData;
// Filter by status
if (status) {
filteredQuests = filteredQuests.filter(quest =>
quest.status.toLowerCase() === status.toLowerCase()
);
}
// Filter by priority
if (priority) {
filteredQuests = filteredQuests.filter(quest =>
quest.priority.toLowerCase() === priority.toLowerCase()
);
}
// Filter by category
if (category) {
filteredQuests = filteredQuests.filter(quest =>
quest.category.toLowerCase() === category.toLowerCase()
);
}
// Calculate summary stats
const totalXP = filteredQuests.reduce((sum, quest) => sum + quest.xp_reward, 0);
const completedQuests = filteredQuests.filter(q => q.status === 'completed').length;
res.json({
total_quests: filteredQuests.length,
total_xp_available: totalXP,
completed_count: completedQuests,
filters_applied: {
status: status || null,
priority: priority || null,
category: category || null
},
quests: filteredQuests.map(quest => ({
id: quest.id,
title: quest.title,
status: quest.status,
priority: quest.priority,
category: quest.category,
xp_reward: quest.xp_reward,
endpoint: `/api/quests/${quest.id}`
})),
usage: 'GET /api/quests/:id for detailed quest data'
});
} catch (error) {
console.error('Quests list endpoint error:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Failed to fetch quests list'
});
}
});
// POST /api/quests/:id/complete
router.post('/:id/complete', (req, res) => {
try {
const questId = parseInt(req.params.id);
// Find quest
const questIndex = questsData.findIndex(q => q.id === questId);
if (questIndex === -1) {
return res.status(404).json({
error: 'Quest not found',
message: `Quest with ID ${req.params.id} not found`
});
}
const quest = questsData[questIndex];
// Check if already completed
if (quest.status === 'completed') {
return res.status(400).json({
error: 'Quest already completed',
message: `Quest "${quest.title}" was already completed`,
completed_at: quest.completed_at
});
}
// Mark as completed
questsData[questIndex] = {
...quest,
status: 'completed',
completed_at: new Date().toISOString()
};
res.json({
message: 'Quest completed successfully!',
quest: questsData[questIndex],
xp_earned: quest.xp_reward,
api_info: {
endpoint: req.originalUrl,
response_time: new Date().toISOString()
}
});
} catch (error) {
console.error('Complete quest endpoint error:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Failed to complete quest'
});
}
});
module.exports = router;
Create routes/players.js
:
const express = require('express');
const router = express.Router();
// Sample player data - in real app, this would come from a database
const playersData = {
'alex': {
username: 'alex',
display_name: 'Alex Chen',
level: 15,
total_xp: 2450,
xp_to_next_level: 550,
current_streak: 7,
longest_streak: 21,
join_date: '2024-01-01T00:00:00Z',
last_active: '2024-01-14T18:30:00Z',
active_quests: [1, 2, 4, 5],
completed_quests: [3],
achievements: ['First Week', 'Early Bird', 'Streak Master'],
preferences: {
categories: ['work', 'health'],
difficulty: 'medium',
notifications: true
}
},
'jordan': {
username: 'jordan',
display_name: 'Jordan Smith',
level: 8,
total_xp: 890,
xp_to_next_level: 110,
current_streak: 3,
longest_streak: 12,
join_date: '2024-01-05T00:00:00Z',
last_active: '2024-01-14T16:45:00Z',
active_quests: [2, 4],
completed_quests: [],
achievements: ['Getting Started'],
preferences: {
categories: ['personal', 'learning'],
difficulty: 'easy',
notifications: false
}
},
'sam': {
username: 'sam',
display_name: 'Sam Taylor',
level: 22,
total_xp: 4200,
xp_to_next_level: 800,
current_streak: 14,
longest_streak: 35,
join_date: '2023-12-15T00:00:00Z',
last_active: '2024-01-14T20:15:00Z',
active_quests: [1, 5],
completed_quests: [3],
achievements: ['First Week', 'Early Bird', 'Streak Master', 'Veteran', 'XP Hunter'],
preferences: {
categories: ['work', 'health', 'personal'],
difficulty: 'hard',
notifications: true
}
}
};
// Import quest data for calculating player stats
const questsData = require('./quests').questsData || []; // Normally would be shared or from database
// Calculate player statistics
function calculatePlayerStats(player) {
const completedQuests = player.completed_quests || [];
const activeQuests = player.active_quests || [];
return {
total_quests_completed: completedQuests.length,
active_quests_count: activeQuests.length,
completion_rate: activeQuests.length > 0 ? Math.round((completedQuests.length / (completedQuests.length + activeQuests.length)) * 100) : 100,
average_xp_per_quest: completedQuests.length > 0 ? Math.round(player.total_xp / completedQuests.length) : 0,
level_progress: Math.round(((3000 - player.xp_to_next_level) / 3000) * 100), // Assuming 3000 XP per level
streak_status: player.current_streak >= 7 ? 'Hot Streak!' : player.current_streak >= 3 ? 'Good' : 'Building'
};
}
// GET /api/players/:username
router.get('/:username', (req, res) => {
try {
const username = req.params.username.toLowerCase();
const includeQuests = req.query.include_quests === 'true';
// Check if player exists
if (!playersData[username]) {
return res.status(404).json({
error: 'Player not found',
message: `Player ${req.params.username} not found`,
available_players: Object.keys(playersData),
suggestion: 'Check the username spelling'
});
}
let playerData = { ...playersData[username] };
const stats = calculatePlayerStats(playerData);
// Add calculated stats
playerData.stats = stats;
// Include quest details if requested
if (includeQuests) {
playerData.quest_details = {
active: playerData.active_quests.map(id => ({
id: id,
endpoint: `/api/quests/${id}`
})),
completed: playerData.completed_quests.map(id => ({
id: id,
endpoint: `/api/quests/${id}`
}))
};
}
// Add API metadata
playerData.api_info = {
endpoint: req.originalUrl,
include_quests: includeQuests,
response_time: new Date().toISOString(),
data_source: 'Quest Tracker API v1.0'
};
res.json(playerData);
} catch (error) {
console.error('Player endpoint error:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Failed to fetch player data'
});
}
});
// GET /api/players (list all players)
router.get('/', (req, res) => {
try {
const sortBy = req.query.sort_by || 'level'; // level, xp, streak
const order = req.query.order || 'desc'; // asc, desc
const players = Object.keys(playersData).map(key => {
const data = playersData[key];
const stats = calculatePlayerStats(data);
return {
username: data.username,
display_name: data.display_name,
level: data.level,
total_xp: data.total_xp,
current_streak: data.current_streak,
completed_quests: stats.total_quests_completed,
active_quests: stats.active_quests_count,
endpoint: `/api/players/${key}`
};
});
// Sort players
players.sort((a, b) => {
let comparison = 0;
switch (sortBy) {
case 'xp':
comparison = a.total_xp - b.total_xp;
break;
case 'streak':
comparison = a.current_streak - b.current_streak;
break;
case 'level':
default:
comparison = a.level - b.level;
}
return order === 'desc' ? -comparison : comparison;
});
res.json({
total_players: players.length,
sort_applied: { by: sortBy, order: order },
players: players,
usage: 'GET /api/players/:username for detailed player data'
});
} catch (error) {
console.error('Players list endpoint error:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Failed to fetch players list'
});
}
});
module.exports = router;
Create routes/categories.js
:
const express = require('express');
const router = express.Router();
const categories = [
{
id: 'work',
name: 'Work & Career',
description: 'Professional development and work-related tasks',
color: '#3B82F6',
icon: '💼',
default_xp_multiplier: 1.2,
common_priorities: ['high', 'critical'],
suggested_time_blocks: [30, 60, 120],
popular_tags: ['meetings', 'projects', 'deadlines', 'learning']
},
{
id: 'health',
name: 'Health & Fitness',
description: 'Physical and mental wellness activities',
color: '#10B981',
icon: '🏃',
default_xp_multiplier: 1.1,
common_priorities: ['medium', 'high'],
suggested_time_blocks: [20, 30, 45, 60],
popular_tags: ['exercise', 'meditation', 'nutrition', 'sleep']
},
{
id: 'personal',
name: 'Personal Life',
description: 'Family, friends, hobbies, and personal growth',
color: '#8B5CF6',
icon: '🌟',
default_xp_multiplier: 1.0,
common_priorities: ['low', 'medium'],
suggested_time_blocks: [15, 30, 60],
popular_tags: ['family', 'friends', 'hobbies', 'self-care']
},
{
id: 'learning',
name: 'Learning & Skills',
description: 'Education, skill development, and knowledge acquisition',
color: '#F59E0B',
icon: '📚',
default_xp_multiplier: 1.3,
common_priorities: ['medium', 'high'],
suggested_time_blocks: [45, 60, 90],
popular_tags: ['courses', 'reading', 'practice', 'research']
},
{
id: 'creative',
name: 'Creative Projects',
description: 'Art, writing, music, and other creative endeavors',
color: '#EF4444',
icon: '🎨',
default_xp_multiplier: 1.1,
common_priorities: ['low', 'medium'],
suggested_time_blocks: [30, 60, 120],
popular_tags: ['art', 'writing', 'music', 'design']
}
];
// GET /api/categories
router.get('/', (req, res) => {
try {
const includeStats = req.query.include_stats === 'true';
const search = req.query.search?.toLowerCase();
let filteredCategories = categories;
// Filter by search term
if (search) {
filteredCategories = filteredCategories.filter(category =>
category.name.toLowerCase().includes(search) ||
category.description.toLowerCase().includes(search) ||
category.popular_tags.some(tag => tag.toLowerCase().includes(search))
);
}
// Add quest statistics if requested
if (includeStats) {
filteredCategories = filteredCategories.map(category => ({
...category,
stats: {
total_quests_available: `Check /api/quests?category=${category.id}`,
avg_completion_time: `${category.suggested_time_blocks[1] || 30} minutes`,
difficulty_level: category.default_xp_multiplier > 1.2 ? 'challenging' : 'moderate'
}
}));
}
res.json({
total_categories: filteredCategories.length,
categories: filteredCategories,
filters_applied: {
search: search || null,
include_stats: includeStats
},
api_info: {
endpoint: req.originalUrl,
response_time: new Date().toISOString()
}
});
} catch (error) {
console.error('Categories endpoint error:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Failed to fetch categories'
});
}
});
// GET /api/categories/:id
router.get('/:id', (req, res) => {
try {
const categoryId = req.params.id.toLowerCase();
const category = categories.find(c => c.id === categoryId);
if (!category) {
return res.status(404).json({
error: 'Category not found',
message: `Category with ID '${req.params.id}' not found`,
available_categories: categories.map(c => ({ id: c.id, name: c.name }))
});
}
res.json({
...category,
related_endpoints: {
quests: `/api/quests?category=${category.id}`,
pending_quests: `/api/quests?category=${category.id}&status=pending`,
completed_quests: `/api/quests?category=${category.id}&status=completed`
},
api_info: {
endpoint: req.originalUrl,
response_time: new Date().toISOString()
}
});
} catch (error) {
console.error('Category endpoint error:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Failed to fetch category data'
});
}
});
module.exports = router;
What you'll do: Implement API key authentication Time: 15 minutes
Add this to server.js
before your routes:
// API Key Authentication
const validApiKeys = new Set([
'demo_key_12345',
'test_key_67890',
'student_key_abcde'
]);
function authenticateApiKey(req, res, next) {
// Skip authentication for documentation and root endpoints
if (req.path === '/' || req.path === '/api/docs') {
return next();
}
const apiKey = req.headers['x-api-key'] || req.query.api_key;
if (!apiKey) {
return res.status(401).json({
error: 'Authentication required',
message: 'API key missing. Include X-API-Key header or api_key query parameter',
documentation: '/api/docs'
});
}
if (!validApiKeys.has(apiKey)) {
return res.status(401).json({
error: 'Invalid API key',
message: 'The provided API key is not valid',
hint: 'Use demo_key_12345 for testing'
});
}
// Add API key info to request for logging
req.apiKey = apiKey;
next();
}
// Apply authentication to all API routes
app.use('/api/', authenticateApiKey);
Try accessing these URLs in a new tab:
/api/quests/1
(should get 401 error)/api/quests/1?api_key=demo_key_12345
(should work)What you'll do: Build a web interface to test your API Time: 10 minutes
Replace the contents of test.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quest Tracker API Tester</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
.container {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.endpoint {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 15px;
margin: 10px 0;
}
.method {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
color: white;
font-weight: bold;
margin-right: 10px;
}
.get { background: #28a745; }
input, select, button {
padding: 8px;
margin: 5px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
background: #007bff;
color: white;
cursor: pointer;
}
button:hover {
background: #0056b3;
}
.response {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 15px;
margin: 10px 0;
white-space: pre-wrap;
font-family: monospace;
max-height: 400px;
overflow-y: auto;
}
.status-200 { border-left: 4px solid #28a745; }
.status-404 { border-left: 4px solid #ffc107; }
.status-401 { border-left: 4px solid #dc3545; }
.status-500 { border-left: 4px solid #dc3545; }
</style>
</head>
<body>
<div class="container">
<h1>🏆 Quest Tracker API Tester</h1>
<p>Test your Quest Tracker API endpoints directly from this interface.</p>
<div>
<label>API Key:</label>
<input type="text" id="apiKey" value="demo_key_12345" placeholder="Enter your API key">
<button onclick="testAuthentication()">Test Auth</button>
</div>
</div>
<div class="container">
<h2>📖 Available Endpoints</h2>
<div class="endpoint">
<span class="method get">GET</span>
<strong>/api/quests</strong>
<p>Get all quests with optional filtering</p>
<div>
<select id="questStatus">
<option value="">All Statuses</option>
<option value="pending">Pending</option>
<option value="in_progress">In Progress</option>
<option value="completed">Completed</option>
</select>
<select id="questPriority">
<option value="">All Priorities</option>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
<option value="critical">Critical</option>
</select>
<button onclick="testQuests()">Test Quests List</button>
</div>
</div>
<div class="endpoint">
<span class="method get">GET</span>
<strong>/api/quests/:id</strong>
<p>Get specific quest by ID</p>
<div>
<input type="number" id="questId" value="1" min="1" placeholder="Quest ID">
<button onclick="testQuestById()">Test Quest Details</button>
</div>
</div>
<div class="endpoint">
<span class="method post" style="background: #dc3545;">POST</span>
<strong>/api/quests/:id/complete</strong>
<p>Mark a quest as completed</p>
<div>
<input type="number" id="completeQuestId" value="1" min="1" placeholder="Quest ID">
<button onclick="completeQuest()">Complete Quest</button>
</div>
</div>
<div class="endpoint">
<span class="method get">GET</span>
<strong>/api/players/:username</strong>
<p>Get player profile and statistics</p>
<div>
<input type="text" id="playerUsername" value="alex" placeholder="Username">
<label>
<input type="checkbox" id="includeQuests"> Include quest details
</label>
<button onclick="testPlayer()">Test Player</button>
</div>
</div>
<div class="endpoint">
<span class="method get">GET</span>
<strong>/api/categories</strong>
<p>Get quest categories</p>
<div>
<input type="text" id="categorySearch" placeholder="Search categories (optional)">
<label>
<input type="checkbox" id="includeStats"> Include statistics
</label>
<button onclick="testCategories()">Test Categories</button>
</div>
</div>
</div>
<div class="container">
<h2>📡 API Response</h2>
<div id="response" class="response">Click a "Test" button to see API response here...</div>
</div>
<script>
const baseUrl = window.location.origin;
async function makeRequest(endpoint, params = {}, method = 'GET') {
const apiKey = document.getElementById('apiKey').value;
try {
let url, options;
if (method === 'GET') {
// Add API key to params for GET requests
params.api_key = apiKey;
// Build URL with query parameters
url = new URL(endpoint, baseUrl);
Object.keys(params).forEach(key => {
if (params[key] !== null && params[key] !== '') {
url.searchParams.append(key, params[key]);
}
});
options = { method: 'GET' };
} else {
// For POST requests, send API key in header
url = new URL(endpoint, baseUrl);
url.searchParams.append('api_key', apiKey);
options = {
method: method,
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey
}
};
if (Object.keys(params).length > 0) {
options.body = JSON.stringify(params);
}
}
const response = await fetch(url, options);
const data = await response.json();
displayResponse(response.status, data, url.toString());
} catch (error) {
displayResponse(0, { error: 'Network Error', message: error.message }, endpoint);
}
}
function displayResponse(status, data, url) {
const responseDiv = document.getElementById('response');
responseDiv.className = `response status-${status}`;
responseDiv.textContent = `Status: ${status}\nURL: ${url}\n\n${JSON.stringify(data, null, 2)}`;
}
async function testAuthentication() {
await makeRequest('/api/quests/1');
}
async function testQuests() {
const status = document.getElementById('questStatus').value;
const priority = document.getElementById('questPriority').value;
await makeRequest('/api/quests', { status, priority });
}
async function testQuestById() {
const id = document.getElementById('questId').value;
await makeRequest(`/api/quests/${id}`);
}
async function completeQuest() {
const id = document.getElementById('completeQuestId').value;
await makeRequest(`/api/quests/${id}/complete`, {}, 'POST');
}
async function testPlayer() {
const username = document.getElementById('playerUsername').value;
const includeQuests = document.getElementById('includeQuests').checked;
await makeRequest(`/api/players/${username}`, {
include_quests: includeQuests ? 'true' : 'false'
});
}
async function testCategories() {
const search = document.getElementById('categorySearch').value;
const includeStats = document.getElementById('includeStats').checked;
await makeRequest('/api/categories', {
search,
include_stats: includeStats ? 'true' : 'false'
});
}
// Test API on page load
window.onload = () => {
testQuests();
};
</script>
</body>
</html>
Try these extra challenges:
Create endpoints for quest creation, quest editing, or player achievements
Replace in-memory data with a real database like MongoDB or SQLite
Implement quest sharing, team challenges, or daily streak bonuses
In the final activity, you'll learn how to deploy your Quest API and create a dashboard to make it publicly accessible. You're now ready to build the Todo API for Project 3!
Server not starting?
Routes not working?
Authentication failing?
Quest data not loading?
Player endpoints failing?
POST requests not working?