Todo Application Documentation
Table of Contents
- Introduction
- Architecture Overview
- Backend Setup
- Frontend Setup
- AWS Configuration
- Deployment
- Troubleshooting
- Maintenance and Updates
1. Introduction
This document provides a comprehensive guide for setting up, deploying, and maintaining a full-stack Todo application using Node.js, React, Express, and MongoDB. The application is deployed on AWS, utilizing services such as Lambda, API Gateway, S3, and CloudFront.
2. Architecture Overview
- Backend: Node.js with Express, deployed as an AWS Lambda function
- Frontend: React, built with Vite, hosted on S3 and served via CloudFront
- Database: MongoDB Atlas
- API: AWS API Gateway
- Authentication: (To be implemented)
3. Backend Setup
3.1 Lambda Function
Create a new file named index.js
:
const mongoose = require('mongoose');
let cachedDb = null;
async function connectToDatabase() {
if (cachedDb) {
return cachedDb;
}
const connection = await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
cachedDb = connection;
return connection;
}
const todoSchema = new mongoose.Schema({
title: { type: String, required: true },
completed: { type: Boolean, default: false },
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now },
});
const Todo = mongoose.model('Todo', todoSchema);
exports.handler = async (event) => {
const corsHeaders = {
'Access-Control-Allow-Origin': 'https://your-cloudfront-domain.cloudfront.net',
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
'Access-Control-Allow-Credentials': 'true'
};
if (event.httpMethod === 'OPTIONS') {
return {
statusCode: 200,
headers: corsHeaders,
body: JSON.stringify({ message: 'CORS preflight request successful' })
};
}
try {
await connectToDatabase();
const { httpMethod, resource, pathParameters, body } = event;
switch (`${httpMethod} ${resource}`) {
case 'GET /todos':
const todos = await Todo.find().sort({ createdAt: -1 });
return {
statusCode: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify(todos)
};
case 'POST /todos':
const newTodo = new Todo(JSON.parse(body));
const savedTodo = await newTodo.save();
return {
statusCode: 201,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify(savedTodo)
};
case 'PUT /todos/{id}':
const updatedTodo = await Todo.findByIdAndUpdate(
pathParameters.id,
{ ...JSON.parse(body), updatedAt: Date.now() },
{ new: true }
);
if (!updatedTodo) {
return {
statusCode: 404,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ message: 'Todo not found' })
};
}
return {
statusCode: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify(updatedTodo)
};
case 'DELETE /todos/{id}':
const deletedTodo = await Todo.findByIdAndDelete(pathParameters.id);
if (!deletedTodo) {
return {
statusCode: 404,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ message: 'Todo not found' })
};
}
return {
statusCode: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ message: 'Todo deleted successfully' })
};
default:
return {
statusCode: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ message: 'Invalid request' })
};
}
} catch (error) {
console.error('Error:', error);
return {
statusCode: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ message: 'Internal server error' })
};
}
};
3.2 Package Configuration
Create a package.json
file:
{
"name": "todo-api-lambda",
"version": "1.0.0",
"description": "Todo API Lambda Function",
"main": "index.js",
"dependencies": {
"mongoose": "^6.0.0"
}
}
3.3 Deployment Package
- Install dependencies:
npm install
- Create a ZIP file:
zip -r function.zip index.js node_modules
4. Frontend Setup
4.1 Create React App
- Create a new Vite project:
npm create vite@latest client -- --template react
- Navigate to the project directory:
cd client
- Install dependencies:
npm install
4.2 API Service
Create a file named src/services/todoService.js
:
import axios from 'axios';
const API_URL = 'https://your-api-gateway-url.execute-api.us-west-2.amazonaws.com/prod/todos';
const api = axios.create({
baseURL: API_URL,
withCredentials: true
});
export const getTodos = async () => {
const response = await api.get('');
return response.data;
};
export const createTodo = async (title) => {
const response = await api.post('', { title });
return response.data;
};
export const updateTodo = async (id, updates) => {
const response = await api.put(`/${id}`, updates);
return response.data;
};
export const deleteTodo = async (id) => {
await api.delete(`/${id}`);
};
4.3 Main App Component
Update src/App.jsx
:
import React, { useState, useEffect } from 'react';
import { getTodos, createTodo, updateTodo, deleteTodo } from './services/todoService';
import './App.css';
function App() {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState('');
useEffect(() => {
fetchTodos();
}, []);
const fetchTodos = async () => {
const fetchedTodos = await getTodos();
setTodos(fetchedTodos);
};
const handleCreateTodo = async (e) => {
e.preventDefault();
if (newTodo.trim()) {
const createdTodo = await createTodo(newTodo);
setTodos([createdTodo, ...todos]);
setNewTodo('');
}
};
const handleUpdateTodo = async (id, updates) => {
const updatedTodo = await updateTodo(id, updates);
setTodos(todos.map(todo => todo._id === id ? updatedTodo : todo));
};
const handleDeleteTodo = async (id) => {
await deleteTodo(id);
setTodos(todos.filter(todo => todo._id !== id));
};
return (
<div className="App">
<h1>Todo Apph1>
<form onSubmit={handleCreateTodo}>
<input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="Add a new todo"
/>
<button type="submit">Add Todobutton>
form>
<ul>
{todos.map(todo => (
<li key={todo._id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => handleUpdateTodo(todo._id, { completed: !todo.completed })}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.title}
span>
<button onClick={() => handleDeleteTodo(todo._id)}>Deletebutton>
li>
))}
ul>
div>
);
}
export default App;
5. AWS Configuration
5.1 Lambda Function
- Create a new Lambda function in the AWS Console.
- Upload the ZIP file created in step 3.3.
- Set the handler to
index.handler
. - Add environment variable:
MONGODB_URI
with your MongoDB connection string.
5.2 API Gateway
- Create a new API in API Gateway.
- Create resources and methods for /todos and /todos/{id}.
- Integrate each method with your Lambda function.
- Enable CORS for each method.
- Deploy the API to a new stage (e.g., “prod”).
5.3 S3 and CloudFront
- Create an S3 bucket for hosting the React app.
- Create a CloudFront distribution with the S3 bucket as the origin.
- Set up Origin Access Control (OAC) for secure access to the S3 bucket.
6. Deployment
6.1 Backend Deployment
Update the Lambda function code:
zip -r function.zip index.js node_modules
aws lambda update-function-code --function-name YourFunctionName --zip-file fileb://function.zip
6.2 Frontend Deployment
- Build the React app:
npm run build
- Upload to S3:
aws s3 sync build/ s3://your-bucket-name --delete
- Invalidate CloudFront cache:
aws cloudfront create-invalidation --distribution-id YourDistributionID --paths "/*"
7. Troubleshooting
7.1 CORS Issues
If encountering CORS errors:
- Ensure CORS is enabled in API Gateway for all methods.
- Verify CORS headers in the Lambda function response.
- Check that the
Access-Control-Allow-Origin
header matches your CloudFront domain.
7.2 API Gateway 5XX Errors
- Check Lambda function logs in CloudWatch.
- Verify that the Lambda function has the correct permissions to access other AWS services.
7.3 MongoDB Connection Issues
- Ensure the
MONGODB_URI
environment variable is set correctly in Lambda. - Verify that the Lambda function has network access to MongoDB Atlas (may require VPC configuration).
8. Maintenance and Updates
8.1 Updating the Backend
- Make changes to the Lambda function code.
- Redeploy using the steps in section 6.1.
8.2 Updating the Frontend
- Make changes to the React application.
- Rebuild and redeploy using the steps in section 6.2.
8.3 Monitoring
- Use CloudWatch to monitor Lambda function performance and errors.
- Set up CloudWatch Alarms for critical metrics.
8.4 Scaling
- Adjust Lambda function memory and timeout settings as needed.
- Consider implementing caching at the API Gateway level for frequently accessed data.
This documentation provides a comprehensive guide for setting up, deploying, and maintaining your Todo application. Remember to keep your dependencies updated and regularly review AWS best practices for potential improvements to your architecture.