A modern, full-stack note-taking application built with Django REST Framework and Next.js. Features AWS Cognito authentication, category-based organization, and production deployment infrastructure using Terraform.
Live Demo: https://diic9tcw9xmrs.cloudfront.net/notes/
- Sign up screen with email and password inputs
- Password visibility toggle on signup
- Link to navigate to login if user already has an account
- Login screen with email and password inputs
- Sign out functionality
- Empty state when user has no notes (illustration + message)
- Auto-created categories visible: Random Thoughts, School, Personal
- Option to create a new note from the initial state
- Create new note by clicking "New note" icon/button
- Automatic note creation (no manual save action required)
- Note includes: Title, Content, Category, Last edited timestamp
- Last edited timestamp updates automatically as user writes/edits
- User can change a note's category
- Note background color changes when category changes
- User can close the note editor and return to notes list
- Notes list screen with left sidebar showing categories
- Each category displays: color, title, number of notes
- Clicking a category filters the list to show only notes in that category
- "All Categories" option shows all notes
- Notes displayed as preview cards in the list
- Each card includes: last edited date, category name, title, content preview
- Content truncation when content doesn't fit in the card
- "Today" displayed if note was edited today
- "Yesterday" displayed if edited yesterday
- Month + day format (e.g., "Jan 26") for older notes
- Clicking an existing note opens it for editing
- Can edit title by clicking into text
- Can edit content by clicking into text
- Can change category via dropdown
- Live Production Deployment on AWS (Free Tier)
- AWS ECR + ECS + ALB for backend containerized deployment
- AWS RDS PostgreSQL for managed database with Secrets Manager
- AWS S3 + CloudFront for frontend static hosting with CDN
- AWS Cognito + Lambda for authentication (auto-confirm without email verification)
- Terraform Infrastructure as Code - All resources managed via Terraform
- AWS Cognito integration with JWT validation
- Auto sign-in after successful signup
- Lambda trigger for instant user confirmation (no email verification delay)
- Secure token refresh via AWS Amplify
- Pagination - 18 notes per page with navigation
- Note Deletion - Delete notes from card hover or within editor
- Confirmation Modal - Confirmation dialog before permanent deletion
- Mobile-Responsive UI - Full responsive design with hamburger menu
- Debounced Autosave - 600ms debounce for title/content, instant save on category change
- Loading States - Spinners and skeleton states during data fetching
- Backend Test Coverage: ~98% (81 comprehensive tests)
- Frontend Test Coverage: ~85% (115 comprehensive tests)
- Pre-commit Hooks - Automated linting on commit
- CI/CD Pipeline - GitHub Actions for automated testing
- Type Safety - TypeScript (frontend) + Mypy type checking (backend)
- Code Formatting - Ruff + Prettier with ESLint
- Architecture
- Tech Stack
- Quick Start
- Development Setup
- Testing
- Deployment
- Database Design
- Security Considerations
- API Documentation
- Project Structure
- Process Summary & AI Usage Workflow
┌─────────────────────────────────────────────────────────────────────────────┐
│ PRODUCTION (AWS) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌───────────────────────────────────────────┐ │
│ │ CloudFront │ │ VPC (Default) │ │
│ │ (CDN + SSL) │ │ │ │
│ │ │ │ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ Static Files │ │ │ ALB │ │ ECS Fargate │ │ │
│ │ (Next.js) │ │ │ (HTTP) │───▶│ (Backend) │ │ │
│ │ │ │ └─────────────┘ └─────────────────┘ │ │
│ └────────┬────────┘ │ │ │ │
│ │ │ ▼ │ │
│ ▼ │ ┌─────────────────┐ │ │
│ ┌─────────────────┐ │ │ RDS Postgres │ │ │
│ │ S3 Bucket │ │ │ (db.t3.micro) │ │ │
│ │ (Frontend) │ │ └─────────────────┘ │ │
│ └─────────────────┘ │ │ │
│ └───────────────────────────────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Cognito │ │ Lambda │ │ Secrets Manager │ │
│ │ (User Pool) │────▶│ (Auto-Confirm) │ │ (DB Creds) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ ECR │ │
│ │ (Docker Images) │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ LOCAL DEVELOPMENT (Docker) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Frontend │────▶│ Backend │────▶│ PostgreSQL │ │
│ │ (Next.js) │ │ (Django DRF) │ │ Database │ │
│ │ Port 3000 │ │ Port 8000 │ │ Port 5432 │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ AWS Cognito │ │
│ │ (DEV Pool) │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
-
Authentication Flow:
- User signs up/logs in via frontend (Next.js + AWS Amplify)
- Cognito validates credentials and returns JWT tokens
- Lambda trigger auto-confirms users (no email verification)
- Frontend stores tokens and includes them in API requests
-
API Request Flow:
- Frontend makes authenticated request with JWT in Authorization header
- Backend validates JWT against Cognito JWKS (cached 24h)
- On first auth, backend creates local user and default categories
- API returns data filtered by authenticated user
-
Data Isolation:
- All notes and categories belong to a specific user
- Backend filters all queries by
user_id - Users cannot access other users' data
| Technology | Version | Purpose |
|---|---|---|
| Python | 3.13 | Runtime |
| Django | 5.1.5 | Web framework |
| Django REST Framework | 3.15.2 | API framework |
| PostgreSQL | 16 | Database |
| Gunicorn | 23.0.0 | WSGI server |
| PyJWT | 2.10.1 | JWT validation |
| uv | Latest | Package manager |
| Ruff | Latest | Linter & formatter |
| Mypy | Latest | Type checking |
| Pytest | 8.3.5 | Testing |
| Technology | Version | Purpose |
|---|---|---|
| Next.js | 15.1.6 | React framework |
| React | 19.0.0 | UI library |
| TypeScript | 5 | Type safety |
| Tailwind CSS | 3.4.17 | Styling |
| AWS Amplify | 6.13.2 | Cognito integration |
| Jest | 29.7.0 | Testing |
| React Testing Library | 16.2.0 | Component testing |
| ESLint | 9.18.0 | Linting |
| Prettier | 3.4.2 | Formatting |
| Technology | Version | Purpose |
|---|---|---|
| Docker | 20.10+ | Containerization |
| Docker Compose | 2.0+ | Local orchestration |
| Terraform | 1.0+ | Infrastructure as Code |
| AWS ECS Fargate | - | Container orchestration |
| AWS RDS | PostgreSQL 16 | Managed database |
| AWS S3 | - | Static file hosting |
| AWS CloudFront | - | CDN |
| AWS Cognito | - | Authentication |
| AWS Lambda | Python 3.13 | Serverless functions |
| AWS Secrets Manager | - | Secrets storage |
| AWS ECR | - | Container registry |
Get the application running locally with one command:
make upThe application will be available at:
| Service | URL |
|---|---|
| Frontend | http://localhost:3000 |
| Backend API | http://localhost:8000/api/ |
| Health Check | http://localhost:8000/api/health/ |
| Admin Panel | http://localhost:8000/admin/ |
To verify everything is working:
./verify-setup.shTo stop the application:
make down- Docker (v20.10+)
- Docker Compose (v2.0+)
- Make (optional but recommended)
Verify installations:
docker --version
docker compose --version
make --version # optional-
Clone the repository
git clone https://github.com/YOUR_USERNAME/notes-taking.git cd notes-taking -
Configure environment (optional for local dev)
# Copy example env file cp backend/env.example backend/.env # Edit with your Cognito credentials if available # Default values work for local development
-
Build and start services
make build make up
-
Run migrations (automatic on container start)
make migrate
-
Create admin user (optional)
make createsuperuser
| Command | Description |
|---|---|
make help |
Show all available commands |
make build |
Build all Docker containers |
make up |
Start all services |
make down |
Stop all services |
make restart |
Restart all services |
make logs |
View logs from all services |
make clean |
Stop services and remove volumes |
make test |
Run all tests |
make test-backend |
Run backend tests only |
make test-frontend |
Run frontend tests only |
make test-backend-coverage |
Backend tests with coverage report |
make test-frontend-coverage |
Frontend tests with coverage report |
make lint |
Run all linters |
make lint-backend |
Run backend linters (Ruff + Mypy) |
make lint-frontend |
Run frontend linters (ESLint + Prettier) |
make lint-backend-fix |
Auto-fix backend lint issues |
make lint-frontend-fix |
Auto-fix frontend lint issues |
make migrate |
Run database migrations |
make createsuperuser |
Create Django admin user |
make shell |
Open Django shell |
make shell-db |
Open PostgreSQL shell |
make status |
Show status of all services |
make pre-commit-install |
Install pre-commit hooks |
make pre-commit-run |
Run pre-commit on all files |
The project uses industry-standard linters:
Backend:
- Ruff: Fast Python linter and formatter
- Mypy: Static type checking with Django stubs
Frontend:
- ESLint: Next.js configuration
- Prettier: Code formatting with Tailwind plugin
# Run all linters
make lint
# Auto-fix issues
make lint-backend-fix
make lint-frontend-fix# Install hooks (one time)
make pre-commit-install
# Hooks run automatically on git commit
git commit -m "feat: your changes"
# Run manually on all files
make pre-commit-run| Component | Tests | Coverage |
|---|---|---|
| Backend | 81 | ~98% |
| Frontend | 115 | ~85% |
# All tests
make test
# Backend only
make test-backend
# Frontend only
make test-frontend
# With coverage reports
make test-backend-coverage
make test-frontend-coveragebackend/api/tests/
├── test_api_categories.py # Category CRUD tests
├── test_api_notes.py # Note CRUD tests
├── test_auth.py # Authentication tests
├── test_category_filtering.py # Category filtering tests
├── test_delete_note.py # Note deletion tests
├── test_health.py # Health endpoint tests
├── test_models.py # Model tests
├── test_pagination.py # Pagination tests
└── test_serializers.py # Serializer validation tests
frontend/src/
├── __tests__/ # Integration tests
├── app/__tests__/ # Page tests
├── components/__tests__/ # Component tests
├── hooks/__tests__/ # Hook tests
├── lib/__tests__/ # Utility tests
└── utils/__tests__/ # Helper function tests
# Backend (after running test-backend-coverage)
xdg-open backend/htmlcov/index.html # Linux
open backend/htmlcov/index.html # macOS
# Frontend (after running test-frontend-coverage)
xdg-open frontend/coverage/lcov-report/index.html # Linux
open frontend/coverage/lcov-report/index.html # macOSFor detailed testing documentation, see:
The application is deployed to AWS using Terraform. All resources are configured to stay within AWS Free Tier limits.
Deployed Services:
- ECS Fargate: Containerized backend (256 CPU, 512 MB memory)
- RDS PostgreSQL: db.t3.micro with 20GB storage
- S3 + CloudFront: Static frontend with CDN
- Cognito: User authentication
- Secrets Manager: Database credentials
- ECR: Container registry
# Deploy backend to ECS
./scripts/deploy.sh
# Deploy frontend to S3/CloudFront
./scripts/deploy-frontend.sh
# Run database migrations
./scripts/run-migrations.sh
# Update task definition
./scripts/update-task-definition.sh# 1. Initialize and apply Terraform
cd terraform/aws
terraform init
terraform apply -var-file="vars/prod.tfvars"
# 2. Build and push backend image
./scripts/deploy.sh
# 3. Build and deploy frontend
./scripts/deploy-frontend.sh
# 4. Run migrations
./scripts/run-migrations.sh# Get outputs from Terraform
cd terraform/aws
terraform output frontend_url # CloudFront URL
terraform output alb_dns_name # Backend API URL
terraform output deployment_summary # Full deployment infoWith low traffic (~5 users), monthly cost is approximately $0.40-1.00 (mostly Secrets Manager). All other services stay within Free Tier limits.
For complete deployment documentation, see terraform/README.md.
┌─────────────────────────────────────────────────────────────────────────────┐
│ DATABASE SCHEMA │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────┐ │
│ │ users │ │
│ ├─────────────────────────────┤ │
│ │ id (PK) SERIAL │ │
│ │ cognito_sub VARCHAR(255)│ ◄── Unique identifier from Cognito │
│ │ email VARCHAR(255)│ │
│ │ is_active BOOLEAN │ │
│ │ created_at TIMESTAMP │ │
│ │ updated_at TIMESTAMP │ │
│ └──────────────┬──────────────┘ │
│ │ │
│ │ 1:N │
│ ▼ │
│ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │
│ │ categories │ │ notes │ │
│ ├─────────────────────────────┤ ├─────────────────────────────┤ │
│ │ id (PK) SERIAL │ │ id (PK) SERIAL │ │
│ │ user_id (FK) INTEGER │◄───────│ user_id (FK) INTEGER │ │
│ │ name VARCHAR(100)│ 1:N │ category_id(FK) INTEGER │──┐│
│ │ color VARCHAR(7) │◄───────│ title VARCHAR(255)│ ││
│ │ created_at TIMESTAMP │ │ content TEXT │ ││
│ │ updated_at TIMESTAMP │ │ created_at TIMESTAMP │ ││
│ └─────────────────────────────┘ │ updated_at TIMESTAMP │ ││
│ └─────────────────────────────┘ ││
│ │ ││
│ └───────────────┘│
│ │
└─────────────────────────────────────────────────────────────────────────────┘
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | Auto-increment ID |
| cognito_sub | VARCHAR(255) | UNIQUE, NOT NULL | Cognito user identifier |
| VARCHAR(255) | UNIQUE, NOT NULL | User email address | |
| is_active | BOOLEAN | DEFAULT TRUE | Account active status |
| created_at | TIMESTAMP | AUTO | Creation timestamp |
| updated_at | TIMESTAMP | AUTO | Last update timestamp |
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | Auto-increment ID |
| user_id | INTEGER | FOREIGN KEY → users.id | Owner user |
| name | VARCHAR(100) | NOT NULL | Category name |
| color | VARCHAR(7) | NOT NULL | Hex color code (#RRGGBB) |
| created_at | TIMESTAMP | AUTO | Creation timestamp |
| updated_at | TIMESTAMP | AUTO | Last update timestamp |
Unique constraint: (user_id, name)
Default categories (created on first login):
- Random Thoughts (#EF9C66)
- School (#FCDC94)
- Personal (#78ABA8)
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | Auto-increment ID |
| user_id | INTEGER | FOREIGN KEY → users.id | Owner user |
| category_id | INTEGER | FOREIGN KEY → categories.id | Note category |
| title | VARCHAR(255) | BLANK OK | Note title |
| content | TEXT | BLANK OK, MAX 50000 | Note content |
| created_at | TIMESTAMP | AUTO | Creation timestamp |
| updated_at | TIMESTAMP | AUTO | Last update timestamp |
Business Rules:
- Every note MUST have a category (required)
- Category must belong to the same user as the note
- Notes ordered by
updated_at DESC(newest first)
| Table | Index | Columns | Purpose |
|---|---|---|---|
| users | idx_cognito_sub | cognito_sub | Fast lookup by Cognito ID |
| users | idx_email | Fast lookup by email | |
| categories | idx_user_name | user_id, name | User's categories lookup |
| notes | idx_user_updated | user_id, -updated_at | User's notes ordered by date |
| notes | idx_category | category_id | Filter notes by category |
| Risk | Mitigation |
|---|---|
| A01: Broken Access Control | User-based data isolation; all queries filtered by user_id |
| A02: Cryptographic Failures | Passwords handled by Cognito; JWT tokens for auth |
| A03: Injection | Django ORM prevents SQL injection; input validation via serializers |
| A04: Insecure Design | Least privilege principle; defense in depth |
| A05: Security Misconfiguration | Environment-specific settings; secrets in AWS Secrets Manager |
| A06: Vulnerable Components | Actively maintained dependencies; regular updates |
| A07: Auth Failures | AWS Cognito handles authentication; JWT validation |
| A08: Data Integrity Failures | Category ownership validation; CSRF protection |
| A09: Security Logging | CloudWatch logging enabled |
| A10: SSRF | No user-controlled URL fetching |
- JWT Validation: Tokens validated against Cognito JWKS
- JWKS Caching: Keys cached for 24 hours to reduce API calls
- Token Claims Validated: Issuer, audience, expiry, token_use
- Secure Password Policy: Min 8 chars, requires uppercase, lowercase, numbers, symbols
- CORS Configuration: Restricted to allowed origins
- Input Validation: All inputs validated via DRF serializers
- Rate Limiting: Recommended for production (Django Ratelimit)
- Security Headers: X-Frame-Options, X-Content-Type-Options, HSTS
- HTTPS via CloudFront
- Database credentials in Secrets Manager
- Environment-specific configurations
- User data isolation
- WAF (Web Application Firewall) - Recommended
- Rate limiting - Recommended
- Security scanning (SAST/DAST) - Recommended
| Risk | Mitigation |
|---|---|
| Default credentials in development | Use environment variables; never use defaults in production |
| Debug mode exposure | DEBUG=False in production |
| CORS misconfiguration | Restrict ALLOWED_HOSTS and CORS_ALLOWED_ORIGINS |
| Missing rate limiting | Implement Django Ratelimit or AWS WAF |
For security issues or vulnerabilities:
- Create a private security advisory on GitHub
- Do not disclose security issues publicly until addressed
All endpoints except /health/ require a valid JWT token in the Authorization header:
Authorization: Bearer <token>
GET /api/health/Response (200):
{
"status": "healthy",
"service": "notes-taking-api",
"database": "connected"
}| Method | Endpoint | Description |
|---|---|---|
| GET | /api/categories/ |
List user's categories |
| POST | /api/categories/ |
Create category |
| GET | /api/categories/{id}/ |
Get category details |
| PATCH | /api/categories/{id}/ |
Update category |
| DELETE | /api/categories/{id}/ |
Delete category |
Category Object:
{
"id": 1,
"name": "Personal",
"color": "#78ABA8",
"note_count": 5,
"created_at": "2026-01-26T10:00:00Z",
"updated_at": "2026-01-26T10:00:00Z"
}| Method | Endpoint | Description |
|---|---|---|
| GET | /api/notes/ |
List user's notes (paginated) |
| GET | /api/notes/?category=1 |
Filter notes by category |
| GET | /api/notes/?page=2 |
Get specific page |
| POST | /api/notes/ |
Create note |
| GET | /api/notes/{id}/ |
Get note details |
| PATCH | /api/notes/{id}/ |
Update note |
| DELETE | /api/notes/{id}/ |
Delete note |
| GET | /api/notes/summary/ |
Get notes summary |
Note Object:
{
"id": 1,
"title": "My Note",
"content": "Note content here...",
"category": 1,
"category_name": "Personal",
"category_color": "#78ABA8",
"created_at": "2026-01-26T10:00:00Z",
"updated_at": "2026-01-26T10:30:00Z"
}Paginated Response:
{
"count": 42,
"next": "http://api/notes/?page=3",
"previous": "http://api/notes/?page=1",
"results": [ /* Note objects */ ]
}For detailed API documentation, see backend/README.md.
notes-taking/
├── backend/ # Django REST Framework API
│ ├── api/ # Main API app
│ │ ├── tests/ # Test files
│ │ ├── authentication.py # Cognito JWT auth
│ │ ├── models.py # User, Category, Note models
│ │ ├── serializers.py # DRF serializers
│ │ ├── viewsets.py # API viewsets
│ │ ├── views.py # Health check view
│ │ └── urls.py # API routes
│ ├── config/ # Django project config
│ │ └── settings.py # Django settings
│ ├── Dockerfile # Production container
│ ├── pyproject.toml # Python dependencies
│ └── README.md # Backend documentation
│
├── frontend/ # Next.js application
│ ├── src/
│ │ ├── app/ # Next.js pages
│ │ │ ├── login/ # Login page
│ │ │ ├── signup/ # Signup page
│ │ │ └── notes/ # Notes page (protected)
│ │ ├── components/ # React components
│ │ │ ├── NoteEditor.tsx # Note editor modal
│ │ │ ├── Pagination.tsx # Pagination controls
│ │ │ └── ...
│ │ ├── hooks/ # Custom hooks
│ │ │ ├── useAuth.ts # Authentication hook
│ │ │ └── useDebounce.ts # Debounce hook
│ │ ├── lib/ # Utilities
│ │ │ ├── api-client.ts # API client with auth
│ │ │ └── amplify-config.ts # AWS Amplify config
│ │ └── utils/ # Helper functions
│ ├── Dockerfile # Production container
│ ├── Dockerfile.dev # Development container
│ └── README.md # Frontend documentation
│
├── terraform/ # Infrastructure as Code
│ └── aws/ # AWS resources
│ ├── main.tf # Provider configuration
│ ├── cognito.tf # Cognito user pools
│ ├── lambda.tf # Lambda functions
│ ├── ecs.tf # ECS cluster & service
│ ├── rds.tf # RDS database
│ ├── s3.tf # S3 bucket
│ ├── cloudfront.tf # CloudFront distribution
│ ├── alb.tf # Application Load Balancer
│ └── README.md # Infrastructure docs
│
├── scripts/ # Deployment scripts
│ ├── deploy.sh # Backend deployment
│ ├── deploy-frontend.sh # Frontend deployment
│ ├── run-migrations.sh # Database migrations
│ └── update-task-definition.sh # ECS task definition
│
├── infra/ # Infrastructure configs
│ └── task-definition-prod.json # ECS task definition
│
├── docs/ # Documentation
│ └── workflow-process.md # AI workflow & planning
│
├── docker-compose.yml # Local development
├── Makefile # Development commands
├── CODEOWNERS # Code ownership
└── README.md # This file
For a detailed explanation of the development process, planning methodology, and AI tools used throughout this project, see:
This document covers:
- How the project roadmap was created
- Tools used to manage tasks and deliverables
- AI tools and integrations used (Cursor IDE, Claude Sonnet 4.5, Figma MCP)
- The iterative development approach
- Infrastructure planning and deployment strategy
- All changes require review from code owners (see
CODEOWNERS) - PRs must pass all CI checks (lint, test, smoke test)
- Follow existing code style and patterns
# Create feature branch
git checkout -b feature/your-feature-name
# Make changes and test
make lint
make test
# Commit (pre-commit hooks run automatically)
git commit -m "feat: your feature description"
# Push and create PR
git push origin feature/your-feature-nameUse conventional commits:
feat:New featurefix:Bug fixdocs:Documentationstyle:Formattingrefactor:Code restructuringtest:Testschore:Maintenance
This project is licensed under the MIT License - see the LICENSE file for details.
For questions or issues:
- Check documentation (this README, backend/frontend/terraform READMEs)
- Review API Documentation
- Open an issue on GitHub
- Contact the development team
Built with ❤️ using Django REST Framework, Next.js, and AWS