6 months ago
What I Built: A full-stack Flask application that tracks daily milk deliveries with automated billing calculations. Started as a simple tracker, evolved into a multi-tenant SaaS with user authentication, OAuth integration, and personalized settings.
Live: https://web-production-01a84.up.railway.app/
The Journey:
v1.0 - MVP (SQLite + Basic CRUD)
- Simple Flask app with local SQLite
- Deployed using Railway's GitHub integration
- Challenge: Database persistence across deployments
- Solution: Discovered Railway Volumes for persistent storage
v2.0 - Production Database Migration (SQLite → PostgreSQL)
- Hit SQLite's concurrency limits with multiple users
- Migrated to Railway's PostgreSQL in one click
- Challenge: Auto-increment sequences broke after data migration
- Solution: Created custom migration script to reset PostgreSQL sequences
v3.0 - Multi-User with Authentication
- Added OAuth (Google + GitHub) using Authlib
- Implemented email verification with custom token system
- Challenge: OAuth callbacks failing in production (http vs https)
- Solution: Dynamic redirect URI fixing:
v4.0 - User-Specific Data Isolation
- Added foreign keys linking records to users
- Challenge: Existing data had no user association
- Solution: Smart migration script that assigns orphaned records to primary user
v5.0 - Personalized Settings
- Per-user currency (10+ currencies) and milk pricing
- Auto-calculation with user-defined rates
- Optional bulk recalculation of historical records
- Challenge: Column additions without downtime
- Solution: Auto-migration on startup:
Railway Features I Love:
- Zero-Config PostgreSQL: One-click database provisioning with automatic DATABASE_URL injection
- Environment Variables: Seamless secret management across dev/prod
- Volume Persistence: Used for backup storage before PostgreSQL migration
- Automatic HTTPS: No SSL certificate headaches
- GitHub Integration: Push to deploy - Railway handles the rest
Technical Highlights:
Smart Database Detection:
Automatic Schema Migrations: Instead of complex migration tools, implemented auto-detection and migration on startup - perfect for Railway's ephemeral deployments.
Session-Based Multi-Currency: Each user sees their own currency symbol throughout the app - achieved with user settings stored in PostgreSQL.
Architecture:
- Backend: Flask + SQLAlchemy ORM
- Database: Railway PostgreSQL with connection pooling
- Auth: OAuth 2.0 (Google/GitHub) + custom email verification
- Frontend: Jinja2 templates with responsive CSS
- Deployment: Gunicorn WSGI server on Railway
What I Learned:
- Railway's DATABASE_URL uses postgres:// but SQLAlchemy expects postgresql:// - quick fix avoided hours of debugging
- PostgreSQL sequences need manual reset after bulk imports
- Railway's environment variable injection makes 12-factor apps trivial
- Connection pooling is essential for Railway's PostgreSQL (pool_pre_ping, pool_recycle)
- Auto-migrations on startup work better than separate migration scripts for small apps
Challenges Overcome:
- 404 errors post-deployment: Fixed with proper _external=True in url_for()
- OAuth state mismatch: Railway's HTTPS enforcement required callback URL fixes
- Data migration: Custom scripts handled SQLite boolean (0/1) to PostgreSQL boolean conversion
What's Next:
- CSV export functionality
- REST API for mobile apps
- Scheduled email reports using Railway Cron Jobs
Railway made deployment so smooth that I spent more time on features than DevOps. The PostgreSQL integration alone saved days of setup work. This project went from "weekend hack" to "production SaaS" thanks to Railway's simplicity!
Attachments
0 Replies