A full-stack todo application built with React, Express, and SQLite. Designed to be developed on an M4 Mac Mini and deployed to an M1 Mac Mini with Cloudflare Tunnel for web access.
- Frontend: React 18 + TypeScript + Vite
- Backend: Node.js + Express + TypeScript
- Database: SQLite with better-sqlite3
- Process Manager: PM2
- Deployment: Git-based with automated scripts
- Remote Access: Cloudflare Tunnel
- Create, read, update, and delete todos
- Mark todos as complete/incomplete
- Separate views for active and completed todos
- Persistent storage with SQLite
- Automated deployment with database backups
- Health checks and monitoring
- Secure remote access via Cloudflare Tunnel
- Node.js (v18 or higher)
- npm or yarn
- Git
- Node.js (same version as M4)
- npm
- Git
- PM2 (
npm install -g pm2) - SSH server enabled
- Cloudflared (for tunnel):
brew install cloudflared
-
Clone the repository
git clone <your-repo-url> cd todo-app
-
Install backend dependencies
cd server npm install -
Install frontend dependencies
cd ../client npm install -
Configure environment variables
cd .. cp .env.example .env # Edit .env with your M1 Mac Mini details
-
Start the backend server (in one terminal)
cd server npm run devServer runs on http://localhost:3001
-
Start the frontend dev server (in another terminal)
cd client npm run devApp opens at http://localhost:5173
-
Access the application Open your browser to http://localhost:5173
-
SSH into your M1 Mac Mini
ssh user@m1-mac-mini.local
-
Install Node.js and PM2
# Install Node.js (use same version as M4) brew install node@20 # Install PM2 globally npm install -g pm2
-
Set up Git bare repository for deployment
# Create bare repository mkdir -p ~/todo-app.git cd ~/todo-app.git git init --bare # Create deployment directory mkdir -p ~/todo-app cd ~/todo-app git init
-
Configure post-receive hook (optional, for auto-deployment)
cd ~/todo-app.git/hooks cat > post-receive << 'EOF' #!/bin/bash git --work-tree=/Users/$(whoami)/todo-app --git-dir=/Users/$(whoami)/todo-app.git checkout -f EOF chmod +x post-receive
-
Set up SSH key authentication from M4 to M1
# On M4, generate SSH key if you don't have one ssh-keygen -t ed25519 -C "your_email@example.com" # Copy public key to M1 ssh-copy-id user@m1-mac-mini.local
-
Create logs directory
mkdir -p ~/todo-app/logs
# On your M4 Mac Mini
git remote add m1-production ssh://user@m1-mac-mini.local/Users/username/todo-app.git
# Update .env with your M1 details
nano .env# Commit your changes
git add .
git commit -m "Initial commit"
# Deploy to M1
./scripts/deploy.shThe deployment script will:
- Check for uncommitted changes
- Backup the SQLite database on M1
- Push code to M1 git repository
- Install dependencies
- Build the application
- Restart PM2
- Run health checks
# Check PM2 status
ssh user@m1-mac-mini.local 'pm2 status'
# View application logs
ssh user@m1-mac-mini.local 'pm2 logs todo-app'
# View all PM2 apps
ssh user@m1-mac-mini.local 'pm2 list'-
Install cloudflared
brew install cloudflared
-
Authenticate with Cloudflare
cloudflared tunnel login
-
Create a tunnel
cloudflared tunnel create todo-app
Note the tunnel ID that's generated.
-
Configure the tunnel
mkdir -p ~/.cloudflared nano ~/.cloudflared/config.yml
Add the following configuration:
tunnel: <your-tunnel-id> credentials-file: /Users/<username>/.cloudflared/<tunnel-id>.json ingress: - hostname: todo.yourdomain.com service: http://localhost:3001 - service: http_status:404
-
Create DNS record
cloudflared tunnel route dns todo-app todo.yourdomain.com
-
Start the tunnel with PM2
cd ~/todo-app pm2 start ecosystem.config.js --only cloudflare-tunnel pm2 save
-
Enable PM2 startup on boot
pm2 startup # Follow the instructions provided by the command pm2 save
Your todo app is now accessible at:
- Local network: http://m1-mac-mini.local:3001
- Via Cloudflare Tunnel: https://todo.yourdomain.com
todo-app/
├── client/ # React frontend
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── api.ts # API client
│ │ ├── App.tsx # Main app component
│ │ └── main.tsx # Entry point
│ ├── package.json
│ └── vite.config.ts
├── server/ # Express backend
│ ├── src/
│ │ ├── routes/ # API routes
│ │ ├── db.ts # Database layer
│ │ ├── types.ts # TypeScript types
│ │ └── index.ts # Server entry point
│ └── package.json
├── database/ # Database files
│ └── schema.sql # SQLite schema
├── scripts/ # Deployment scripts
│ ├── deploy.sh # Main deployment script
│ ├── backup-db.sh # Database backup
│ └── health-check.sh # Health verification
├── ecosystem.config.js # PM2 configuration
└── .env # Environment variables
GET /api/health- Health checkGET /api/todos- Get all todosGET /api/todos/:id- Get a specific todoPOST /api/todos- Create a new todoPUT /api/todos/:id- Update a todoPATCH /api/todos/:id/toggle- Toggle todo completionDELETE /api/todos/:id- Delete a todo
Check that you've set up the git remote correctly:
git remote -v
# Should show m1-production pointing to your M1-
Check if the app is running on M1:
ssh user@m1-mac-mini.local 'pm2 status' -
View logs:
ssh user@m1-mac-mini.local 'pm2 logs todo-app --lines 50' -
Check if port 3001 is accessible:
ssh user@m1-mac-mini.local 'lsof -i :3001'
View database backups:
ssh user@m1-mac-mini.local 'ls -lh ~/todo-app-backups'Restore from backup:
ssh user@m1-mac-mini.local 'cp ~/todo-app-backups/todos-backup-YYYY-MM-DD-HH-MM-SS.db ~/todo-app/server/database/todos.db'Check tunnel status:
ssh user@m1-mac-mini.local 'pm2 logs cloudflare-tunnel'Restart tunnel:
ssh user@m1-mac-mini.local 'pm2 restart cloudflare-tunnel'- Make changes on M4 Mac Mini
- Test locally (
npm run devin both server and client) - Commit changes to git
- Deploy to M1:
./scripts/deploy.sh - Test via Cloudflare Tunnel URL
- Monitor with:
ssh user@m1-mac-mini.local 'pm2 monit'
MIT