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 App</h1>
<form onSubmit={handleCreateTodo}>
<input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="Add a new todo"
/>
<button type="submit">Add Todo</button>
</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)}>Delete</button>
</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.
Top comments (0)