Introduction to the Weekend Project
This weekend project will guide you through implementing a basic REST API server in three popular backend technologies: Node.js, Python, and PHP. By building the same API endpoints across these different environments, you'll gain a deeper understanding of their similarities, differences, and unique characteristics.
Throughout this project, we'll apply George Polya's famous 4-step problem-solving procedure, which provides a systematic approach to tackling complex problems:
the Problem] A --> C[2. Devise
a Plan] A --> D[3. Execute
the Plan] A --> E[4. Review/Extend] B --> B1[Define project requirements] B --> B2[Clarify the API spec] C --> C1[Design the architecture] C --> C2[Choose tools and libraries] D --> D1[Implement Node.js server] D --> D2[Implement Python server] D --> D3[Implement PHP server] E --> E1[Test APIs] E --> E2[Evaluate differences] E --> E3[Extend functionality] style A fill:#f5f5f5,stroke:#333,stroke-width:2px style B fill:#dbeafe,stroke:#333,stroke-width:2px style C fill:#c6f6d5,stroke:#333,stroke-width:2px style D fill:#fef3c7,stroke:#333,stroke-width:2px style E fill:#fecdd3,stroke:#333,stroke-width:2px
Project Objectives
- Implement a RESTful API for managing a simple task list in three different server environments
- Apply structured problem-solving techniques to design and implement your solutions
- Compare the syntax, structure, and approaches of Node.js, Python, and PHP
- Understand the strengths and weaknesses of each technology
- Learn how to set up development environments for multiple backend technologies
Prerequisites
- Basic understanding of HTTP and RESTful API concepts
- Familiarity with the three programming languages (JavaScript, Python, and PHP)
- Development environments set up for each technology (Node.js, Python, and PHP)
- A tool for API testing (we'll use Postman, but any API testing tool will work)
Step 1: Understand the Problem
Following Polya's first step, we need to thoroughly understand what we're trying to accomplish. Let's define the problem in detail.
Project Requirements
We'll be building a simple "Task Manager" API with the following features:
- Create, read, update, and delete tasks (CRUD operations)
- Store tasks with attributes like id, title, description, status, and created_at
- Implement proper HTTP status codes and error handling
- Use an in-memory data store (for simplicity, no database required)
- Include basic validation for inputs
API Specification
| Endpoint | Method | Description | Request Body | Response |
|---|---|---|---|---|
| /api/tasks | GET | Get all tasks | None | Array of task objects |
| /api/tasks/:id | GET | Get a single task by ID | None | Single task object |
| /api/tasks | POST | Create a new task | { title, description } | Created task object |
| /api/tasks/:id | PUT | Update a task | { title, description, status } | Updated task object |
| /api/tasks/:id | DELETE | Delete a task | None | Success message |
Data Model
The Task object will have the following structure:
{
"id": "string", // Unique identifier (UUID or similar)
"title": "string", // Task title
"description": "string", // Task description
"status": "string", // "pending", "in-progress", or "completed"
"created_at": "string" // ISO date string
}
Understanding Each Technology
| Technology | Server Framework | Key Characteristics |
|---|---|---|
| Node.js | Express.js |
|
| Python | Flask |
|
| PHP | Plain PHP / Basic Router |
|
Key Questions to Address
- How does each technology handle routing and HTTP methods?
- What are the differences in request/response handling?
- How do the languages differ in data structure manipulation?
- What are the error handling approaches in each technology?
- How does code organization compare across these platforms?
Step 2: Devise a Plan
Now that we understand the problem, let's devise a plan for implementing our solution across the three technologies.
High-Level Implementation Plan
- Set up development environments for each technology
- Implement the Node.js server with Express.js
- Implement the Python server with Flask
- Implement the PHP server with a basic router
- Test each implementation with Postman
- Compare and contrast the implementations
Common Components
Despite the differences in languages and frameworks, each implementation will share these common components:
- Server initialization and configuration
- Route definitions for API endpoints
- Request parsing and validation
- In-memory data store for tasks
- Response formatting and status codes
- Error handling
Technology-Specific Plans
Node.js (Express.js) Implementation
- Initialize a Node.js project with npm
- Install Express.js and other dependencies (uuid, cors, etc.)
- Set up the Express application with middleware
- Create route handlers for each API endpoint
- Implement in-memory storage using a JavaScript array or object
- Add validation and error handling
- Test the implementation
Python (Flask) Implementation
- Create a virtual environment for Python
- Install Flask and other dependencies
- Initialize the Flask application
- Define route handlers using Flask's decorator syntax
- Implement in-memory storage using a Python list or dictionary
- Add validation and error handling
- Test the implementation
PHP Implementation
- Set up a basic PHP project structure
- Create a simple router to handle different HTTP methods and paths
- Implement controller functions for each API endpoint
- Use a PHP array for in-memory storage (serialized to a file for persistence)
- Add validation and error handling
- Configure proper JSON responses
- Test the implementation
Project Structure
For organization, we'll use the following directory structure:
weekend-project/
├── nodejs-server/
│ ├── package.json
│ ├── index.js
│ └── ...
├── python-server/
│ ├── app.py
│ ├── requirements.txt
│ └── ...
└── php-server/
├── index.php
├── router.php
└── ...
Step 3: Execute the Plan
Now we're ready to implement our servers in each technology. We'll start with Node.js, then move to Python, and finally PHP.
Node.js Implementation with Express.js
Setting Up the Project
# Create project directory
mkdir -p weekend-project/nodejs-server
cd weekend-project/nodejs-server
# Initialize npm project
npm init -y
# Install dependencies
npm install express uuid cors
# Create the main file
touch index.js
Implementing the Server
Here's the implementation of our Express.js server:
// index.js
const express = require('express');
const cors = require('cors');
const { v4: uuidv4 } = require('uuid');
// Initialize the app
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json());
// In-memory data store
let tasks = [];
// Helper function to find task by ID
function findTaskById(id) {
return tasks.find(task => task.id === id);
}
// GET all tasks
app.get('/api/tasks', (req, res) => {
res.json(tasks);
});
// GET a single task by ID
app.get('/api/tasks/:id', (req, res) => {
const task = findTaskById(req.params.id);
if (!task) {
return res.status(404).json({ error: 'Task not found' });
}
res.json(task);
});
// POST a new task
app.post('/api/tasks', (req, res) => {
const { title, description } = req.body;
// Validation
if (!title) {
return res.status(400).json({ error: 'Title is required' });
}
const newTask = {
id: uuidv4(),
title,
description: description || '',
status: 'pending',
created_at: new Date().toISOString()
};
tasks.push(newTask);
res.status(201).json(newTask);
});
// PUT (update) a task
app.put('/api/tasks/:id', (req, res) => {
const { title, description, status } = req.body;
const task = findTaskById(req.params.id);
if (!task) {
return res.status(404).json({ error: 'Task not found' });
}
// Validation
if (status && !['pending', 'in-progress', 'completed'].includes(status)) {
return res.status(400).json({ error: 'Invalid status value' });
}
// Update task
if (title) task.title = title;
if (description !== undefined) task.description = description;
if (status) task.status = status;
res.json(task);
});
// DELETE a task
app.delete('/api/tasks/:id', (req, res) => {
const taskIndex = tasks.findIndex(task => task.id === req.params.id);
if (taskIndex === -1) {
return res.status(404).json({ error: 'Task not found' });
}
tasks.splice(taskIndex, 1);
res.json({ message: 'Task deleted successfully' });
});
// Start the server
app.listen(PORT, () => {
console.log(`Node.js server running on port ${PORT}`);
});
Running the Node.js Server
# Start the server
node index.js
# You should see: "Node.js server running on port 3000"
Python Implementation with Flask
Setting Up the Project
# Create project directory
mkdir -p weekend-project/python-server
cd weekend-project/python-server
# Create a virtual environment
python -m venv venv
# Activate the virtual environment
# On Windows:
# venv\Scripts\activate
# On macOS/Linux:
source venv/bin/activate
# Install dependencies
pip install flask uuid flask-cors
# Create requirements.txt
pip freeze > requirements.txt
# Create the main file
touch app.py
Implementing the Server
Here's the implementation of our Flask server:
# app.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import uuid
from datetime import datetime
# Initialize the app
app = Flask(__name__)
CORS(app)
# In-memory data store
tasks = []
# Helper function to find task by ID
def find_task_by_id(task_id):
for task in tasks:
if task['id'] == task_id:
return task
return None
# GET all tasks
@app.route('/api/tasks', methods=['GET'])
def get_all_tasks():
return jsonify(tasks)
# GET a single task by ID
@app.route('/api/tasks/', methods=['GET'])
def get_task(task_id):
task = find_task_by_id(task_id)
if not task:
return jsonify({'error': 'Task not found'}), 404
return jsonify(task)
# POST a new task
@app.route('/api/tasks', methods=['POST'])
def create_task():
data = request.get_json()
# Validation
if not data or 'title' not in data:
return jsonify({'error': 'Title is required'}), 400
new_task = {
'id': str(uuid.uuid4()),
'title': data['title'],
'description': data.get('description', ''),
'status': 'pending',
'created_at': datetime.now().isoformat()
}
tasks.append(new_task)
return jsonify(new_task), 201
# PUT (update) a task
@app.route('/api/tasks/', methods=['PUT'])
def update_task(task_id):
task = find_task_by_id(task_id)
if not task:
return jsonify({'error': 'Task not found'}), 404
data = request.get_json()
# Validation
if 'status' in data and data['status'] not in ['pending', 'in-progress', 'completed']:
return jsonify({'error': 'Invalid status value'}), 400
# Update task
if 'title' in data:
task['title'] = data['title']
if 'description' in data:
task['description'] = data['description']
if 'status' in data:
task['status'] = data['status']
return jsonify(task)
# DELETE a task
@app.route('/api/tasks/', methods=['DELETE'])
def delete_task(task_id):
global tasks
task = find_task_by_id(task_id)
if not task:
return jsonify({'error': 'Task not found'}), 404
tasks = [t for t in tasks if t['id'] != task_id]
return jsonify({'message': 'Task deleted successfully'})
if __name__ == '__main__':
app.run(debug=True, port=5000)
Running the Python Server
# Start the server
python app.py
# You should see: "Running on http://127.0.0.1:5000/"
PHP Implementation
Setting Up the Project
# Create project directory
mkdir -p weekend-project/php-server
cd weekend-project/php-server
# Create necessary files
touch index.php
touch router.php
mkdir data
touch data/tasks.json
Implementing the Server
Here's the implementation of our PHP server:
<?php
// index.php
// Set headers for CORS and JSON response
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
header('Content-Type: application/json');
// Handle preflight OPTIONS request
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit(0);
}
// Include router
require_once 'router.php';
// Parse the URL and route the request
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$uri = explode('/', $uri);
// API routes should start with /api/tasks
if ($uri[1] !== 'api' || $uri[2] !== 'tasks') {
header("HTTP/1.1 404 Not Found");
echo json_encode(['error' => 'Not Found']);
exit();
}
// Get the task ID if it exists
$taskId = null;
if (isset($uri[3])) {
$taskId = $uri[3];
}
// Route the request based on the HTTP method
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
if ($taskId) {
getTask($taskId);
} else {
getAllTasks();
}
break;
case 'POST':
createTask();
break;
case 'PUT':
if ($taskId) {
updateTask($taskId);
} else {
header("HTTP/1.1 400 Bad Request");
echo json_encode(['error' => 'Task ID is required']);
}
break;
case 'DELETE':
if ($taskId) {
deleteTask($taskId);
} else {
header("HTTP/1.1 400 Bad Request");
echo json_encode(['error' => 'Task ID is required']);
}
break;
default:
header("HTTP/1.1 405 Method Not Allowed");
echo json_encode(['error' => 'Method not allowed']);
break;
}
<?php
// router.php
// Load tasks from JSON file
function getTasks() {
if (file_exists('data/tasks.json')) {
$json = file_get_contents('data/tasks.json');
return json_decode($json, true) ?: [];
}
return [];
}
// Save tasks to JSON file
function saveTasks($tasks) {
file_put_contents('data/tasks.json', json_encode($tasks, JSON_PRETTY_PRINT));
}
// Find task by ID
function findTaskById($id) {
$tasks = getTasks();
foreach ($tasks as $task) {
if ($task['id'] === $id) {
return $task;
}
}
return null;
}
// GET all tasks
function getAllTasks() {
echo json_encode(getTasks());
}
// GET a single task by ID
function getTask($id) {
$task = findTaskById($id);
if (!$task) {
header("HTTP/1.1 404 Not Found");
echo json_encode(['error' => 'Task not found']);
return;
}
echo json_encode($task);
}
// POST a new task
function createTask() {
$data = json_decode(file_get_contents('php://input'), true);
// Validation
if (!isset($data['title']) || empty($data['title'])) {
header("HTTP/1.1 400 Bad Request");
echo json_encode(['error' => 'Title is required']);
return;
}
$tasks = getTasks();
// Generate a unique ID
$id = uniqid();
$newTask = [
'id' => $id,
'title' => $data['title'],
'description' => $data['description'] ?? '',
'status' => 'pending',
'created_at' => date('c') // ISO 8601 date
];
$tasks[] = $newTask;
saveTasks($tasks);
header("HTTP/1.1 201 Created");
echo json_encode($newTask);
}
// PUT (update) a task
function updateTask($id) {
$task = findTaskById($id);
if (!$task) {
header("HTTP/1.1 404 Not Found");
echo json_encode(['error' => 'Task not found']);
return;
}
$data = json_decode(file_get_contents('php://input'), true);
// Validation
if (isset($data['status']) && !in_array($data['status'], ['pending', 'in-progress', 'completed'])) {
header("HTTP/1.1 400 Bad Request");
echo json_encode(['error' => 'Invalid status value']);
return;
}
// Update task
if (isset($data['title'])) {
$task['title'] = $data['title'];
}
if (array_key_exists('description', $data)) {
$task['description'] = $data['description'];
}
if (isset($data['status'])) {
$task['status'] = $data['status'];
}
// Update the task in the array
$tasks = getTasks();
foreach ($tasks as $key => $t) {
if ($t['id'] === $id) {
$tasks[$key] = $task;
break;
}
}
saveTasks($tasks);
echo json_encode($task);
}
// DELETE a task
function deleteTask($id) {
$tasks = getTasks();
$found = false;
foreach ($tasks as $key => $task) {
if ($task['id'] === $id) {
unset($tasks[$key]);
$found = true;
break;
}
}
if (!$found) {
header("HTTP/1.1 404 Not Found");
echo json_encode(['error' => 'Task not found']);
return;
}
// Reindex array
$tasks = array_values($tasks);
saveTasks($tasks);
echo json_encode(['message' => 'Task deleted successfully']);
}
Running the PHP Server
# Start the built-in PHP server
php -S localhost:8000
# You should see: "PHP 8.x.x Development Server started at [date and time]"
Creating Empty tasks.json File
# For the PHP implementation, create an empty JSON array
echo "[]" > data/tasks.json
Step 4: Review and Extend
Now that we've implemented our API in all three technologies, let's review what we've learned and look at how we can extend our solutions.
Testing the APIs
To test the APIs, we'll use Postman or any HTTP client. Here are the test cases to try:
-
GET all tasks:
- Node.js:
GET http://localhost:3000/api/tasks - Python:
GET http://localhost:5000/api/tasks - PHP:
GET http://localhost:8000/api/tasks
- Node.js:
-
Create a task:
- Node.js:
POST http://localhost:3000/api/tasks - Python:
POST http://localhost:5000/api/tasks - PHP:
POST http://localhost:8000/api/tasks - Request body:
{ "title": "Learn Node.js", "description": "Study Express.js framework" }
- Node.js:
-
Get a single task:
- Node.js:
GET http://localhost:3000/api/tasks/{id} - Python:
GET http://localhost:5000/api/tasks/{id} - PHP:
GET http://localhost:8000/api/tasks/{id}
- Node.js:
-
Update a task:
- Node.js:
PUT http://localhost:3000/api/tasks/{id} - Python:
PUT http://localhost:5000/api/tasks/{id} - PHP:
PUT http://localhost:8000/api/tasks/{id} - Request body:
{ "status": "in-progress" }
- Node.js:
-
Delete a task:
- Node.js:
DELETE http://localhost:3000/api/tasks/{id} - Python:
DELETE http://localhost:5000/api/tasks/{id} - PHP:
DELETE http://localhost:8000/api/tasks/{id}
- Node.js:
Comparing the Implementations
| Feature | Node.js (Express) | Python (Flask) | PHP |
|---|---|---|---|
| Routing | Express's app.METHOD(path, handler) | Flask's @app.route decorator | Manual routing with switch/case |
| Request Parsing | req.body (with express.json() middleware) | request.get_json() | file_get_contents('php://input') |
| Route Parameters | req.params.id | Function parameter from route | Manual URL parsing |
| Status Codes | res.status(code).json(data) | return jsonify(data), status_code | header("HTTP/1.1 XXX Status") |
| Data Storage | In-memory array (lost on restart) | In-memory list (lost on restart) | JSON file (persists between restarts) |
| Error Handling | return res.status(404).json({ error: 'msg' }) | return jsonify({'error': 'msg'}), 404 | header() + json_encode() |
| Code Organization | Single file (can be modularized) | Single file (can be modularized) | Split into index and router |
Language-Specific Observations
Node.js Observations
- Pros:
- Very concise routing syntax
- Excellent middleware system
- JSON handling is seamless
- Async/await makes asynchronous code readable
- Cons:
- Callback pattern can be confusing for beginners
- Error handling requires explicit checks
- In-memory data is lost on server restart
Python (Flask) Observations
- Pros:
- Clean, decorator-based routing
- Intuitive parameter passing from URLs
- Easy to understand for Python developers
- Good error handling with tuple returns
- Cons:
- Less mature ecosystem for web compared to Node.js
- Global variable for the task list (could be improved with a class)
- No built-in middleware system (extensions required)
PHP Observations
- Pros:
- Simple to deploy (works with any PHP server)
- Data persistence using the file system
- Direct access to HTTP headers and methods
- No dependencies required
- Cons:
- Manual routing is more verbose
- More boilerplate code for JSON handling
- Error handling is more manual
- Less elegant syntax for modern web APIs
Extensions and Improvements
Here are some ways you can extend these basic implementations:
Common Extensions
-
Database Integration: Replace in-memory storage with a database
- Node.js: MongoDB with Mongoose or SQL with Sequelize
- Python: SQLAlchemy or PyMongo
- PHP: PDO for SQL databases
-
Authentication: Add user authentication
- Node.js: Passport.js or JWT
- Python: Flask-Login or PyJWT
- PHP: JWT or session-based auth
-
Input Validation: Add more robust validation
- Node.js: Joi or express-validator
- Python: Marshmallow or Pydantic
- PHP: Respect\Validation or Laravel Validator
-
Logging: Add request logging
- Node.js: Morgan or Winston
- Python: Flask's built-in logger or Loguru
- PHP: Monolog
-
Testing: Add automated tests
- Node.js: Jest or Mocha
- Python: pytest
- PHP: PHPUnit
Node.js-Specific Extensions
- Add middleware for request logging
- Implement error-handling middleware
- Structure the app with separate route and controller files
- Add TypeScript for type safety
Python-Specific Extensions
- Use Flask Blueprints for better code organization
- Add Flask-RESTful for a more structured API
- Implement class-based views
- Add type hints
PHP-Specific Extensions
- Implement a proper router class
- Add autoloading with Composer
- Use a micro-framework like Slim or Lumen
- Implement middleware for cross-cutting concerns
Applying Polya's Problem-Solving Process
Throughout this project, we've applied George Polya's four-step problem-solving process. Let's reflect on how each step contributed to our solution:
the Problem] A --> C[2. Devise
a Plan] A --> D[3. Execute
the Plan] A --> E[4. Review/Extend] B --> B1[We defined our API structure,
endpoints, and data models] B --> B2[We clarified the requirements
for each technology] C --> C1[We designed the architecture
for each implementation] C --> C2[We identified common components
and technology-specific approaches] D --> D1[We implemented the
Node.js server with Express] D --> D2[We implemented the
Python server with Flask] D --> D3[We implemented the
PHP server with a custom router] E --> E1[We tested each API
implementation] E --> E2[We compared the approaches
and identified pros/cons] E --> E3[We proposed extensions
and improvements] style A fill:#f5f5f5,stroke:#333,stroke-width:2px style B fill:#dbeafe,stroke:#333,stroke-width:2px style C fill:#c6f6d5,stroke:#333,stroke-width:2px style D fill:#fef3c7,stroke:#333,stroke-width:2px style E fill:#fecdd3,stroke:#333,stroke-width:2px
-
Understand the Problem:
- We clearly defined what we needed to build (a task management API)
- We specified the data model and API endpoints
- We identified the technologies we would use and their characteristics
- We articulated key questions to address in our implementation
-
Devise a Plan:
- We created a high-level plan for implementing each server
- We broke down the common components across all implementations
- We outlined technology-specific approaches
- We organized the project structure
-
Execute the Plan:
- We set up the development environments for each technology
- We implemented the APIs following our planned approach
- We ensured each implementation met the requirements
- We kept the code organized and documented
-
Review/Extend:
- We tested each implementation to verify functionality
- We compared the different approaches and identified strengths and weaknesses
- We suggested improvements and extensions
- We reflected on what we learned from each technology
Additional Challenge: Building a Simple Frontend
As an additional challenge, consider building a simple HTML/CSS/JavaScript frontend that can interact with any of your API servers. This will give you a complete full-stack experience.
Here's a simple HTML file you can use as a starting point:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Task Manager</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1 {
color: #333;
}
.task-form {
margin-bottom: 20px;
padding: 15px;
background-color: #f5f5f5;
border-radius: 5px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
}
input, textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
.task-list {
list-style: none;
padding: 0;
}
.task-item {
background-color: #fff;
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 10px;
padding: 15px;
}
.task-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.task-title {
font-weight: bold;
font-size: 18px;
}
.task-status {
padding: 5px 10px;
border-radius: 15px;
font-size: 12px;
}
.status-pending {
background-color: #f8d7da;
color: #721c24;
}
.status-in-progress {
background-color: #fff3cd;
color: #856404;
}
.status-completed {
background-color: #d4edda;
color: #155724;
}
.task-actions {
margin-top: 10px;
}
.btn-delete {
background-color: #dc3545;
}
.btn-delete:hover {
background-color: #c82333;
}
</style>
</head>
<body>
<h1>Task Manager</h1>
<div class="task-form">
<h2>Add New Task</h2>
<form id="task-form">
<div class="form-group">
<label for="title">Title:</label>
<input type="text" id="title" required>
</div>
<div class="form-group">
<label for="description">Description:</label>
<textarea id="description" rows="3"></textarea>
</div>
<button type="submit">Add Task</button>
</form>
</div>
<h2>Tasks</h2>
<ul id="task-list" class="task-list">
<!-- Tasks will be inserted here -->
</ul>
<script>
// API URL - Change this to point to your server
const API_URL = 'http://localhost:3000/api/tasks'; // Change port as needed
// DOM Elements
const taskForm = document.getElementById('task-form');
const taskList = document.getElementById('task-list');
const titleInput = document.getElementById('title');
const descriptionInput = document.getElementById('description');
// Event Listeners
document.addEventListener('DOMContentLoaded', fetchTasks);
taskForm.addEventListener('submit', addTask);
// Fetch all tasks
async function fetchTasks() {
try {
const response = await fetch(API_URL);
const tasks = await response.json();
renderTasks(tasks);
} catch (error) {
console.error('Error fetching tasks:', error);
}
}
// Add a new task
async function addTask(e) {
e.preventDefault();
const task = {
title: titleInput.value,
description: descriptionInput.value
};
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(task)
});
const newTask = await response.json();
renderTask(newTask);
// Clear form
taskForm.reset();
} catch (error) {
console.error('Error adding task:', error);
}
}
// Update task status
async function updateTaskStatus(id, status) {
try {
const response = await fetch(`${API_URL}/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ status })
});
await response.json();
fetchTasks(); // Refresh the list
} catch (error) {
console.error('Error updating task:', error);
}
}
// Delete a task
async function deleteTask(id) {
try {
await fetch(`${API_URL}/${id}`, {
method: 'DELETE'
});
// Remove from DOM
const taskElement = document.getElementById(`task-${id}`);
if (taskElement) {
taskElement.remove();
}
} catch (error) {
console.error('Error deleting task:', error);
}
}
// Render all tasks
function renderTasks(tasks) {
taskList.innerHTML = '';
tasks.forEach(task => renderTask(task));
}
// Render a single task
function renderTask(task) {
const li = document.createElement('li');
li.id = `task-${task.id}`;
li.className = 'task-item';
const statusClass = `status-${task.status.replace(' ', '-')}`;
li.innerHTML = `
<div class="task-header">
<span class="task-title">${task.title}</span>
<span class="task-status ${statusClass}">${task.status}</span>
</div>
<div class="task-description">${task.description || ''}</div>
<div class="task-actions">
${task.status !== 'in-progress' ?
`<button onclick="updateTaskStatus('${task.id}', 'in-progress')">Mark In Progress</button>` : ''}
${task.status !== 'completed' ?
`<button onclick="updateTaskStatus('${task.id}', 'completed')">Mark Completed</button>` : ''}
${task.status !== 'pending' ?
`<button onclick="updateTaskStatus('${task.id}', 'pending')">Mark Pending</button>` : ''}
<button class="btn-delete" onclick="deleteTask('${task.id}')">Delete</button>
</div>
`;
taskList.appendChild(li);
}
</script>
</body>
</html>
Save this as index.html in a separate directory and open it in your browser. Make sure to adjust the API_URL
to point to whichever server you want to use.
Final Thoughts and Reflections
Key Learning Points
- Common Patterns: All three technologies follow the same patterns for API development: routing, request handling, data processing, and response formatting.
- Syntax Differences: Each language has its unique syntax and idioms, but the underlying concepts are similar.
- Framework Value: Frameworks like Express and Flask dramatically simplify backend development compared to vanilla implementations.
- Error Handling: Proper error handling is crucial across all platforms for building robust APIs.
- Structure Importance: Good structure becomes more important as your application grows, regardless of the technology.
Final Challenge
Based on what you've learned, consider which technology you prefer for backend development and why. Create a more complex application in that technology to deepen your skills.
Next Steps
- Add database integration to persist data
- Implement user authentication
- Deploy your servers to cloud platforms
- Add comprehensive error handling and logging
- Implement unit and integration tests
- Explore more advanced features of each framework
Resources and References
Node.js and Express
- Node.js Documentation
- Express.js Routing Guide
- Express Security Best Practices
- Introduction to Node.js