This migration adds user authentication to your grocery list application, enabling multi-user support while preserving all existing data.
โ User authentication system (email/password) โ JWT token management with refresh tokens โ Multi-user support for grocery items โ Default admin account for existing data โ Safe, reversible migration process โ Comprehensive documentation
# Start PostgreSQL with Docker Compose
docker compose up -d postgres
# Verify it's running
docker compose pscd server
# Check what will happen
npm run migrate:status
# Run the migration
npm run migrate:upEmail: admin@grocery.local
Password: admin123
# Check migration status
npm run migrate:status
# Verify users table
npm run migrate verify # or use: ./migrations/migrate.sh verifyDone! Your application now has authentication. ๐
server/migrations/
โโโ 001_add_authentication.sql # UP migration (258 lines)
โโโ 001_add_authentication_rollback.sql # DOWN migration (174 lines)
โโโ migrate.ts # Migration runner (371 lines)
โโโ migrate.sh # Bash helper script (219 lines)
โโโ README.md # Full documentation (595 lines)
โโโ QUICKSTART.md # Quick reference (142 lines)
โโโ MIGRATION_SUMMARY.md # Technical summary (450 lines)
Total: 1,909 lines of migration code and documentation
users - User accounts
- UUID primary key
- Email (unique, validated, lowercase)
- Password hash (bcrypt)
- Name, timestamps, activity status
- Indexed for fast lookups
refresh_tokens - JWT token management
- Token storage and revocation
- Expiration tracking
- User association
schema_migrations - Migration tracking
- Automatic tracking table
- Prevents duplicate migrations
- Shows migration history
grocery_items - Gets user ownership
- Adds
user_idcolumn (UUID, NOT NULL) - Foreign key to users table
- All existing items assigned to admin
- Composite indexes for performance
- 2 Trigger functions (auto-update timestamps, normalize emails)
- 2 Triggers (on users table)
- 10 Indexes (optimized for user queries)
- Multiple constraints (email format, foreign keys, uniqueness)
โ All existing grocery items preserved โ All item data intact (name, quantity, category, notes, etc.) โ No data loss during migration โ Transaction-based (rolls back on error) โ Idempotent (safe to re-run)
A default admin user is created to own existing items:
- Email:
admin@grocery.local - Password:
admin123 - All existing grocery items assigned to this user
Security Note: This default password is intentionally weak to force you to change it immediately!
Read: /home/adam/grocery/server/migrations/QUICKSTART.md
- 5-minute setup guide
- Common commands
- Quick verification steps
Read: /home/adam/grocery/server/migrations/README.md
- Complete migration documentation
- All commands explained
- Troubleshooting guide
- Production deployment guide
Read: /home/adam/grocery/server/migrations/MIGRATION_SUMMARY.md
- Detailed technical summary
- Database schema changes
- Performance considerations
- Testing recommendations
Review:
/home/adam/grocery/server/migrations/001_add_authentication.sql/home/adam/grocery/server/migrations/001_add_authentication_rollback.sql
cd server
# View migration status
npm run migrate:status
# Run all pending migrations
npm run migrate:up
# Rollback last migration
npm run migrate:down
# Run specific migration
npm run migrate up 001
# Rollback specific migration
npm run migrate down 001cd server/migrations
# Make script executable (first time only)
chmod +x migrate.sh
# Show status
./migrate.sh status
# Verify database state
./migrate.sh verify
# Create backup
./migrate.sh backup
# Run migration
./migrate.sh up
# Rollback migration (with confirmation)
./migrate.sh down# Run migration manually
psql -h localhost -p 5432 -U grocery -d grocery_db \
-f server/migrations/001_add_authentication.sql
# Rollback migration manually
psql -h localhost -p 5432 -U grocery -d grocery_db \
-f server/migrations/001_add_authentication_rollback.sqlcd server
npm run migrate:statusExpected output:
โ Applied 001: add_authentication
psql -h localhost -p 5432 -U grocery -d grocery_db -c "
SELECT email, name, is_active, created_at FROM users;
"Expected output:
email | name | is_active | created_at
------------------------+---------------+-----------+---------------------------
admin@grocery.local | Default Admin | t | 2025-10-26 05:13:42.123...
psql -h localhost -p 5432 -U grocery -d grocery_db -c "
SELECT
COUNT(*) as total_items,
COUNT(user_id) as items_with_user,
COUNT(*) - COUNT(user_id) as orphaned_items
FROM grocery_items;
"Expected output:
total_items | items_with_user | orphaned_items
-------------+-----------------+----------------
15 | 15 | 0
# Test login endpoint
curl -X POST http://localhost:3001/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "admin@grocery.local",
"password": "admin123"
}'Expected: JSON response with access and refresh tokens.
Update server/.env (or create from .env.example):
# Database Configuration
DB_USER=grocery
DB_PASSWORD=grocery
DB_HOST=localhost
DB_PORT=5432
DB_NAME=grocery_db
# JWT Configuration (required for auth to work)
JWT_SECRET=your-super-secret-jwt-key-change-this
JWT_REFRESH_SECRET=your-super-secret-refresh-key-change-this
JWT_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d
# Server Configuration
PORT=3001
NODE_ENV=development
CORS_ORIGIN=http://localhost:3000Generate secure JWT secrets:
# Generate JWT_SECRET
openssl rand -base64 32
# Generate JWT_REFRESH_SECRET
openssl rand -base64 32If you need to remove authentication and revert to the original state:
Rollback will:
- Delete all user accounts
- Delete all refresh tokens
- Remove user_id from grocery items
- Grocery items will be preserved
# 1. Backup database first!
pg_dump -h localhost -p 5432 -U grocery grocery_db > backup_before_rollback.sql
# 2. Run rollback
cd server
npm run migrate:down
# 3. Verify rollback
npm run migrate:statusAfter rollback, your database will be in pre-authentication state with all grocery items intact.
Problem: PostgreSQL not running Solution:
docker compose up -d postgres
sleep 5
docker compose psProblem: Base schema not created Solution: Create the grocery_items table first, then run migration
Problem: Database user not created Solution: Check Docker Compose configuration or create user manually
Problem: Migration already applied
Solution: Check status with npm run migrate:status
Problem: Wrong connection credentials
Solution: Check server/.env file has correct DB credentials
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ users โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ id (UUID, PK) โ
โ email (VARCHAR, UNIQUE) โ
โ password_hash (VARCHAR) โ
โ name (VARCHAR) โ
โ created_at (TIMESTAMPTZ) โ
โ updated_at (TIMESTAMPTZ) โ
โ last_login (TIMESTAMPTZ) โ
โ is_active (BOOLEAN) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โฒ
โ (FK)
โ
โโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโ
โ grocery_items โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ id (TEXT, PK) โ
โ name (TEXT) โ
โ quantity (INTEGER) โ
โ gotten (BOOLEAN) โ
โ category (TEXT) โ
โ notes (TEXT) โ
โ created_at (INTEGER) โ
โ user_id (UUID, FK) โ NEW! โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ refresh_tokens โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ id (UUID, PK) โ
โ user_id (UUID, FK) โโโโโโโโโ โ
โ token_hash (VARCHAR) โ โ
โ expires_at (TIMESTAMPTZ) โ โ
โ created_at (TIMESTAMPTZ) โ โ
โ revoked (BOOLEAN) โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโ
โฒ
โ (FK)
โ
โโโโโโโโ users.id
- Migration completed successfully
- Admin user exists in database
- All grocery items have user_id assigned
- No orphaned items (user_id IS NULL)
- Can login with admin credentials
- Change default admin password
- Environment variables configured
- JWT secrets generated and set
- Authentication endpoints working
- Frontend can authenticate users
- Items filtered by user correctly
- Quick Start:
server/migrations/QUICKSTART.md - Full Guide:
server/migrations/README.md - Technical Summary:
server/migrations/MIGRATION_SUMMARY.md - This File:
MIGRATION_INSTRUCTIONS.md
# Show all tables
psql -h localhost -U grocery -d grocery_db -c "\dt"
# Describe users table
psql -h localhost -U grocery -d grocery_db -c "\d users"
# Count records
psql -h localhost -U grocery -d grocery_db -c "
SELECT
(SELECT COUNT(*) FROM users) as users,
(SELECT COUNT(*) FROM grocery_items) as items,
(SELECT COUNT(*) FROM refresh_tokens) as tokens;
"
# Show migration history
psql -h localhost -U grocery -d grocery_db -c "
SELECT * FROM schema_migrations ORDER BY applied_at;
"The migration uses IF NOT EXISTS / IF EXISTS clauses, making it safe to re-run without errors.
All changes run in a single transaction. If any step fails, everything rolls back automatically.
Existing data is never deleted. New columns are added, and data is migrated, but original data remains intact.
A schema_migrations table tracks which migrations have been applied, preventing duplicates.
Every migration has a corresponding rollback that can undo the changes safely.
- Always backup before migrating (especially in production)
- Test migrations on development first
- Review migration SQL before running
- Change default credentials immediately
- Use secure JWT secrets in production
- Monitor application after migration
- Keep migrations in version control
After successful migration:
-
Change admin password via application or SQL:
UPDATE users SET password_hash = '$2b$10$YOUR_NEW_HASH' WHERE email = 'admin@grocery.local';
-
Create new users via registration endpoint
-
Test application with authentication
-
Update API calls to include authentication tokens
-
Deploy frontend changes to use authentication
Created: 2025-10-26 Version: 1.0.0 Database: PostgreSQL 12+ Application: Grocery List with Authentication