21 days 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 urlfor()
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