A modern, full-stack task management application with an AI-supported frontend design that helps you organize and track your tasks in a beautiful calendar interface.
- Calendar-Based Task Planning: View and manage your tasks in a monthly calendar layout
- Quick Task Addition: Add tasks with description and due date directly from the top bar
- Task Management: Create, read, update, and delete tasks with ease
- Task Details Drawer: View and edit detailed task information in a side panel
- Task Status Tracking: Mark tasks as completed or pending
- Responsive Design: Clean, intuitive UI with AI-optimized layout
- Real-time API Integration: Seamless frontend-backend communication
Backend:
- Java 21
- Spring Boot 4.0.5
- Spring Data JPA & Hibernate
- MySQL Database
- Maven Build Tool
- JWT Authentication
- Spring Security
Frontend:
- HTML5
- CSS3 (AI-optimized design)
- Vanilla JavaScript
- REST API client
To-Do-List/
βββ Backend/ # Spring Boot backend
β βββ src/main/java/
β β βββ com/tp/todolist/
β β βββ TodolistApplication.java
β β βββ config/ # CORS & Security configuration
β β βββ controller/ # REST API endpoints
β β βββ entity/ # JPA entities
β β βββ service/ # Business logic
β β βββ repository/ # Data access layer
β β βββ dto/ # Data Transfer Objects
β β βββ security/ # JWT utilities
β βββ src/main/resources/
β β βββ application.yaml # Configuration
β β βββ static/ # Static assets
β βββ .env # Environment variables (DO NOT COMMIT)
β βββ pom.xml # Maven dependencies
β βββ db/ # Database scripts
β
βββ Frontend/ # Web frontend
βββ index.html # Welcome page
βββ login.html # Login page
βββ register.html # Registration page
βββ main.html # Main app page
βββ app-config.js # Centralized configuration
βββ app-main.js # Main app logic
βββ app-login.js # Login logic
βββ app-register.js # Registration logic
βββ app-welcome.js # Welcome page logic
βββ style.css # Styling
No setup required! Simply visit: https://todo-list-thanh.netlify.app/index.html
- Create an account
- Start managing tasks immediately
- Everything is hosted and ready to use
- Java 21 or later
- MySQL 8.0 or later
- Node.js (optional, for local frontend server)
- Maven 3.6+
-
Navigate to the backend directory:
cd Backend -
Create or modify the
.envfile:The
.envfile contains all sensitive configuration. Create a new file named.envin theBackend/directory: -
Configure environment variables in
.envfor LOCAL DEVELOPMENT:# ========== DATABASE CONFIGURATION (LOCAL) ========== SPRING_DATASOURCE_URL=jdbc:mysql://localhost:3306/to_do_list_db SPRING_DATASOURCE_USERNAME=your_user_name_here SPRING_DATASOURCE_PASSWORD=your_password_here # ========== JWT CONFIGURATION ========== # Jwt key will be auto-generated if: # APP_JWT_SECRET=GENERATE_A_RANDOM_SECRET #or manually set: # Generate a secure random 32+ character key # Linux/Mac: openssl rand -base64 32 # Windows: [Convert]::ToBase64String((New-Object Byte[] 32 | ForEach-Object { Get-Random -Maximum 256 })) APP_JWT_SECRET=your-secure-random-secret-key-at-least-32-chars!!! # ========== CORS CONFIGURATION ========== CORS_ALLOWED_ORIGINS=http://localhost:5500, http://127.0.0.1:5500, http://localhost, http://127.0.0.1
Or for PRODUCTION:
# ========== DATABASE CONFIGURATION ========== SPRING_DATASOURCE_URL=jdbc:mysql:... SPRING_DATASOURCE_USERNAME=root SPRING_DATASOURCE_PASSWORD=your_password_here # ========== JWT CONFIGURATION ========== #Same as local host configuration # ========== CORS CONFIGURATION ========== CORS_ALLOWED_ORIGINS=your_back_end_deploy_links
-
Database Setup (Local Development Only):
If using local MySQL, create the database and user:
mysql -u root -p
Then run in MySQL:
CREATE DATABASE to_do_list_db; CREATE USER '<your_username_here>'@'localhost' IDENTIFIED BY '<your password_here>'; GRANT ALL PRIVILEGES ON to_do_list_db.* TO '<your_username_here>'@'localhost'; FLUSH PRIVILEGES; EXIT;
The application will automatically create tables via Hibernate ORM.
-
Generate a Secure JWT Secret (Optional):
If you want to generate your own instead of using the placeholder:
Linux/macOS:
openssl rand -base64 32
Windows (PowerShell):
[Convert]::ToBase64String((New-Object Byte[] 32 | ForEach-Object { Get-Random -Maximum 256 }))
Copy the generated value and paste it into
APP_JWT_SECRETin your.envfile. -
Build the project:
mvn clean install
-
Run the application:
mvn spring-boot:run
The backend will start on
http://localhost:8080How it works:
- Spring Boot automatically imports environment variables from
.envthroughapplication.yaml:spring: config: import: optional:file:.env[.properties]
- All variables are then available as
${VARIABLE_NAME}in the configuration
- Spring Boot automatically imports environment variables from
-
Navigate to the frontend directory:
cd Frontend -
Configure the server (optional):
Edit Frontend/app-config.js to switch between servers:
// For LOCAL backend: const ACTIVE_SERVER = "localhost"; // For RENDER (production): const ACTIVE_SERVER = "render";
-
Serve the frontend:
Option A: Using Python (simplest):
python -m http.server 5500
Option B: Using Node.js (if installed):
npx http-server -p 5500
Option C: Open directly in browser:
- Simply open
index.htmlin your web browser
- Simply open
-
Access the application:
- If using a server:
http://localhost:5500/index.html - If opening directly:
file:///path/to/Frontend/index.html
- If using a server:
Security Best Practices:
-
β DO:
- Keep
.envin.gitignore(already configured) - Use strong, random JWT secrets
- Use different credentials for dev vs. production
- Rotate secrets periodically
- Use HTTPS in production
- Keep
-
β DON'T:
- Commit
.envto git - Use weak or predictable secrets
- Reuse credentials across environments
- Share
.envfiles insecurely
- Commit
All endpoints are prefixed with /api and require JWT authentication (except login/register)
| Method | Endpoint | Description | Body |
|---|---|---|---|
POST |
/api/auth/register |
Create new account | {userName, userEmail, userPassword} |
POST |
/api/auth/login |
Login and get JWT | {userName, userPassword} |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
GET |
/api/todos/?year=2024&month=5 |
Get tasks by month | Yes |
POST |
/api/todos/add |
Create new task | Yes |
PATCH |
/api/todos/update/ |
Update existing task | Yes |
DELETE |
/api/todos/delete/{id} |
Delete task | Yes |
GET |
/api/todos/upcoming |
Get upcoming tasks | Yes |
GET |
/api/todos/overdue |
Get overdue tasks | Yes |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
GET |
/api/todos/tags |
Get all tags | Yes |
POST |
/api/todos/tags/add |
Create tag | Yes (Admin Role) |
PATCH |
/api/todos/tags/update/ |
Update tag | Yes (Admin Role) |
DELETE |
/api/todos/tags/delete/{id} |
Delete tag | Yes (Admin Role) |
Login:
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"userName":"john_doe","userPassword":"securepass123"}'
# Response: JWT token string
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Create a Task:
curl -X POST http://localhost:8080/api/todos/add \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"acDueDate": "2026-05-15",
"acDescription": "Complete project report",
"acCompleted": false,
"acTagId": 1
}'Get Monthly Tasks:
curl -X GET "http://localhost:8080/api/todos/?year=2026&month=5" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"The application is fully deployed and accessible online:
| Component | Platform | URL |
|---|---|---|
| Frontend | Netlify | https://todo-list-thanh.netlify.app/index.html |
| Backend API | Render | https://to-do-list-eki9.onrender.com |
| Database | Railway | Cloud-hosted MySQL |
- Type: MySQL 8.0
- Region: Global
- Status: Active and running
- Backups: Automatic daily backups
- Connection: Secured with SSL
- Type: Java Spring Boot
- Runtime: Java 21
- Memory: 512MB
- Startup: Automatic from git push
- Environment: Production variables configured in Render dashboard
- Type: Static Site (HTML/CSS/JavaScript)
- Deployment: Upload Frontend folder
- HTTPS: Automatic with Let's Encrypt
-
Open the application:
https://todo-list-thanh.netlify.app/index.html -
Create an account:
- Click "Login to use" button
- Click "Register here"
- Fill in your username, email, and password
- Your account is stored in the Railway database
-
Start managing tasks:
- Add new tasks with descriptions and due dates
- Organize tasks by tags
- View all tasks in the calendar interface
- Update, complete, or delete tasks as needed
Backend won't start - "url must start with 'jdbc'"
- Check
.envfile exists inBackend/directory - Verify
SPRING_DATASOURCE_URLis set correctly - Ensure database is running (for local setup)
Backend won't start - "Connection refused"
- Is MySQL running? Start it:
mysql.server start(Mac) or MySQL Service (Windows) - Verify database credentials in
.env - Check database is accessible:
mysql -u pro -p
Backend crashes with "Key size too small for HS256"
- JWT secret must be β₯32 characters
- Regenerate using:
openssl rand -base64 32 - Update
APP_JWT_SECRETin.env
"Sign in first" after logging in
- Clear browser storage: Press F12 β Application β Clear All
- Hard refresh:
Ctrl+Shift+R(Windows) orCmd+Shift+R(Mac) - Check DevTools Console for errors
Can't connect to backend
- Verify backend is running:
http://localhost:8080/actuator/health - Check
ACTIVE_SERVERis set correctly inapp-config.js - Check browser DevTools β Network tab for CORS errors
Tasks not loading
- Check if logged in (look for user chip in top-right)
- Open DevTools Console (F12) and check for errors
- Verify JWT token is being sent in Authorization header
Frontend shows errors on Netlify
- Clear browser cache and cookies
- Check that
ACTIVE_SERVERis set to"render" - Verify Render backend is running: Check health endpoint
Backend timeouts on Render
- Railway database might be in sleep mode on free tier
- Access any API endpoint to wake it up
- Wait 30-60 seconds for cold start
Database connection fails
- Check Render environment variables match Railway credentials
- Verify Railway password doesn't contain special characters that need escaping
- Check Railway dashboard for connection issues
- Backend API: http://localhost:8080 (local) or https://to-do-list-eki9.onrender.com (production)
- Frontend: http://localhost:5500 (local) or https://todo-list-thanh.netlify.app (production)
- The
.envfile is never committed to git (configured in.gitignore) - JWT tokens expire after 1 hour (
expiration-ms: 3600000) - All passwords are hashed using Spring Security's BCrypt encoder
- Calendar automatically handles month navigation and displays 42-day grid
Backend:
- spring-boot-starter-web
- spring-boot-starter-data-jpa
- spring-boot-starter-security
- spring-boot-devtools
- mysql-connector-j
- io.jsonwebtoken (JJWT)
- lombok
- maven-compiler-plugin
Frontend:
- No external dependencies (vanilla JavaScript)
- All communication via REST API with JWT authentication
- User registers or logs in with credentials
- Backend validates and returns JWT token
- Frontend stores token in localStorage
- All subsequent requests include token in
Authorization: Bearer <token>header - Backend validates token and returns user-specific data
- Displays full month in 6Γ7 grid (42 days)
- Tasks color-coded and truncated for readability
- Click any day to view/add tasks for that date
- Navigate between months with arrows
- Create: Add new task with description, date, and tag
- Read: View tasks in calendar or list drawers
- Update: Modify task details in detail drawer
- Delete: Remove tasks with confirmation
This project is open source.
Nguyen Hoang Thanh PHAN
Last Updated: May 2026 Version: 1.0.0
This project is fully containerized with Docker. You can run the entire application (Backend, Frontend, and Database) using containers or Docker Compose.
- +Backend only (build & run):
- +
powershell +cd Backend +docker build -t todolist-backend:1.0 . +# Use the root .env (one level up) so the container receives database creds +docker run --env-file ../.env -p 8080:8080 todolist-backend:1.0 + - +Frontend only (build & run):
- +
powershell +cd Frontend +docker build -t todolist-frontend:1.0 . +docker run -p 1010:1010 todolist-frontend:1.0 + - +Full stack (root): use Docker Compose to run Backend + Frontend + MySQL together
- +
powershell +cd "$(git rev-parse --show-toplevel 2>$null || pwd)" # or the project root +docker compose up --build + - +To stop and remove containers, networks and volumes:
- +
powershell +docker compose down -v + - +### Docker Prerequisites
- Docker Desktop installed and running (Download here)
- Docker Compose (included with Docker Desktop)
- Windows: Ensure WSL 2 backend is enabled
- Port availability:
8080(Backend),1010(Frontend),3306(MySQL)
-
Navigate to Backend directory:
cd Backend -
Build the Backend image:
docker build -t todolist-backend:1.0 . -
Run the Backend container (with local .env):
docker run --env-file .env -p 8080:8080 todolist-backend:1.0
If your MySQL is on the Windows host, use:
docker run --env-file .env -e SPRING_DATASOURCE_URL="jdbc:mysql://host.docker.internal:3306/to_do_list_db" -p 8080:8080 todolist-backend:1.0Backend will be available at:
http://localhost:8080
-
Navigate to Frontend directory:
cd Frontend -
Build the Frontend image:
docker build -t todolist-frontend:1.0 . -
Run the Frontend container:
docker run -p 1010:1010 todolist-frontend:1.0
Frontend will be available at:
http://localhost:1010
Docker Compose runs Backend, Frontend, and MySQL together with one command.
-
Create
docker-compose.ymlin project root:version: "3.8" services: mysql: image: mysql:8.0 container_name: todolist-mysql environment: MYSQL_ROOT_PASSWORD: rootpass123 MYSQL_DATABASE: to_do_list_db MYSQL_USER: pro MYSQL_PASSWORD: helloworld123 ports: - "3306:3306" volumes: - mysql-data:/var/lib/mysql - ./Backend/db/db.sql:/docker-entrypoint-initdb.d/init.sql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] timeout: 20s retries: 10 backend: build: ./Backend container_name: todolist-backend ports: - "8080:8080" environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/to_do_list_db SPRING_DATASOURCE_USERNAME: pro SPRING_DATASOURCE_PASSWORD: helloworld123 APP_JWT_SECRET: GENERATE_A_RANDOM_SECRET CORS_ALLOWED_ORIGINS: http://localhost:1010, http://127.0.0.1:1010 depends_on: mysql: condition: service_healthy restart: unless-stopped frontend: build: ./Frontend container_name: todolist-frontend ports: - "1010:1010" depends_on: - backend restart: unless-stopped volumes: mysql-data:
-
Start all services:
docker-compose up
This will start MySQL, Backend, and Frontend in order.
-
Access the application:
- Frontend:
http://localhost:1010 - Backend API:
http://localhost:8080 - Database:
localhost:3306(MySQL)
- Frontend:
-
Stop all services:
docker-compose down
-
View logs (optional):
docker-compose logs -f backend docker-compose logs -f frontend docker-compose logs -f mysql
The Backend uses an optimized multi-stage build:
- Stage 1 (Builder): Uses JDK 21 to compile the Spring Boot application
- Stage 2 (Runtime): Uses smaller JRE 21 image for final container
- Result: ~50% smaller image size compared to single-stage builds
- Base: Nginx Alpine (lightweight)
- Port: 1010 (configured in nginx.conf)
- Content: HTML, CSS, and JavaScript files served statically
Backend Environment Variables (in docker-compose.yml):
| Variable | Default | Purpose |
|---|---|---|
SPRING_DATASOURCE_URL |
jdbc:mysql://mysql:3306/to_do_list_db |
MySQL connection string |
SPRING_DATASOURCE_USERNAME |
pro |
Database user |
SPRING_DATASOURCE_PASSWORD |
helloworld123 |
Database password |
APP_JWT_SECRET |
GENERATE_A_RANDOM_SECRET |
JWT signing key |
CORS_ALLOWED_ORIGINS |
http://localhost:1010 |
Allowed frontend URLs |
# List running containers
docker ps
# List all containers (including stopped)
docker ps -a
# View logs of a container
docker logs container-name
# Follow logs in real-time
docker logs -f container-name
# Stop a container
docker stop container-name
# Remove a container
docker rm container-name
# Remove an image
docker rmi image-name:tag
# Enter a container shell
docker exec -it container-name bash
# Check container resource usage
docker stats
# Build image without cache
docker build --no-cache -t todolist-backend:1.0 .Container won't start - "Address already in use"
- Check if ports are already in use:
netstat -ano | findstr :8080(Windows) - Stop the container:
docker stop container-name - Or use different ports in
docker-compose.yml
Database connection errors
- Ensure MySQL container is running first (check with
docker ps) - Verify environment variables match in
docker-compose.yml - Check logs:
docker logs todolist-mysql
Frontend can't reach Backend
- Verify Backend is running:
docker ps - Check Backend URL in Frontend environment: should be
http://localhost:8080(nothost.docker.internal) - Ensure CORS is configured correctly in Backend
Images not rebuilding after code changes
- Rebuild without cache:
docker build --no-cache -t todolist-backend:1.0 . - Or use:
docker-compose up --build
Out of disk space
- Clean up unused images/containers:
docker system prune - Clean up everything (WARNING):
docker system prune -a
For deploying to production:
-
Use environment-specific docker-compose files:
docker-compose.dev.ymlfor developmentdocker-compose.prod.ymlfor production
-
Use Docker Registry:
- Push images to Docker Hub or Azure Container Registry
- Pull in production environment
-
Use Kubernetes (optional):
- Deploy to Azure Kubernetes Service (AKS)
- Use Helm charts for configuration management
-
Set secure environment variables:
- Use
.env.prodfile (never committed to git) - Or use secrets management (Azure Key Vault, AWS Secrets Manager)
- Use