Part 2 of building an AI blog generator with FastAPI, React, Hugging Face, Railway, and Vercel.
This post is for developers who’ve built full-stack apps locally and want to confidently ship them to production without overengineering.
Welcome Back!
In Part 1, I built an AI-powered blog generator locally.
In this post, I’ll show how I deployed the full system to production using Railway and Vercel—covering backend deployment, database setup, frontend integration, and the real issues I ran into along the way.
In this post, I'll walk you through:
- Deploying the FastAPI backend to Railway
- Setting up PostgreSQL in production
- Deploying the React frontend to Vercel
- Connecting everything together
- The challenges I faced and how I solved them
Let's dive in!
Deployment Strategy
Before jumping into deployment, I needed to make some key decisions.
Why Railway for Backend?
I chose Railway over alternatives like Heroku, AWS, or DigitalOcean because:
- PostgreSQL Integration - One-click database setup
- GitHub Integration - Automatic deployments on push
- Developer Experience - Clean UI, clear logs, easy configuration
- Pricing - $5/month hobby plan is perfect for side projects
- Zero Config - Railway auto-detects Python and handles everything
Why Vercel for Frontend?
Vercel was the obvious choice for the React frontend:
- Built for React/Vite - Optimized for frontend frameworks
- Global CDN - Lightning-fast content delivery
- Automatic HTTPS - SSL certificates out of the box
- Preview Deployments - Every PR gets its own URL
- Free Tier - Perfect for personal projects
Architecture Overview
Here's what we're building:
┌─────────────┐ HTTPS ┌─────────────┐
│ │◄───────────────►│ │
│ Vercel │ │ Railway │
│ (Frontend) │ │ (Backend) │
│ │ │ │
└─────────────┘ └──────┬──────┘
│
│
┌──────▼──────┐
│ PostgreSQL │
│ Database │
└─────────────┘
Simple, scalable, and cost-effective.
Part 1: Deploying Backend to Railway
Step 1: Preparing the Backend
Before deployment, I needed to ensure the backend was production-ready.
Creating railway.json
Railway uses this file to understand how to deploy our app:
{
"$schema": "https://railway.app/railway.schema.json",
"build": {
"builder": "NIXPACKS"
},
"deploy": {
"startCommand": "uvicorn app.main:app --host 0.0.0.0 --port $PORT",
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 10
}
}
Key points:
-
NIXPACKSauto-detects Python and installs dependencies -
--host 0.0.0.0allows external connections -
$PORTuses Railway's dynamic port assignment - Restart policy handles crashes gracefully
Updating Production Dependencies
Made sure requirements.txt included production essentials:
fastapi==0.104.1
uvicorn[standard]==0.24.0
sqlalchemy==2.0.23
psycopg2-binary==2.9.9 # PostgreSQL driver
pydantic==2.5.0
pydantic-settings==2.1.0
python-dotenv==1.0.0
requests==2.31.0
gunicorn==21.2.0 # Production server
Step 2: Setting Up Railway
Creating the Project
The process was surprisingly smooth:
- Went to railway.app
- Clicked "Start a New Project"
- Selected "Deploy from GitHub repo"
- Chose my
ai-blog-generatorrepository -
Important: Selected
backendas the root directory
Railway immediately started building!
Adding PostgreSQL
This was the easiest database setup I've ever done:
Click "New" → "Database" → "PostgreSQL"
Railway automatically:
- Provisioned a PostgreSQL instance
- Created the
DATABASE_URLenvironment variable - Connected it to my backend service
No configuration files, no manual connection strings - just magic.
Step 3: Environment Variables
Railway makes environment management clean and secure.
I added these variables via the Railway dashboard:
HUGGINGFACE_API_KEY=hf_xxxxxxxxxxxxx
HUGGINGFACE_MODEL=meta-llama/Llama-3.3-70B-Instruct
ENVIRONMENT=production
APP_NAME=AI Blog Generator API
VERSION=1.0.0
Pro tip: The DATABASE_URL was automatically set when I added PostgreSQL. No manual configuration needed!
Step 4: First Deployment Attempt
I pushed my code and watched the build logs:
git add .
git commit -m "Prepare for Railway deployment"
git push origin main
Railway detected the push and started building...
And it failed. 😅
Challenge #1: Import Errors
The Problem:
ModuleNotFoundError: No module named 'app.database'
The Cause:
My local environment had different import paths than production.
The Solution:
I realized I needed to ensure all imports were relative to the backend directory. Updated app/main.py:
# Before (worked locally)
from database import engine, Base
# After (works everywhere)
from app.database import engine, Base
Lesson learned: Always test imports from the project root!
Challenge #2: Database Tables Not Created
The Problem:
API worked, but generating blogs failed with:
relation "blog_posts" does not exist
The Cause:
FastAPI wasn't creating tables on startup in production.
The Solution:
Updated the lifespan context manager in main.py:
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup: Create tables
print("Creating database tables...")
Base.metadata.create_all(bind=engine)
print("Tables created successfully!")
yield
# Shutdown
print("Application shutting down")
app = FastAPI(lifespan=lifespan)
Now tables are created automatically on every deployment!
Step 5: Generating a Domain
Railway provides a free subdomain:
Settings → Networking → Generate Domain
Got: https://ai-blog-generator-production-bd0c.up.railway.app
Tested it:
curl curl https://ai-blog-generator-production-bd0c.up.railway.app/health
# Response:
{"status":"healthy","environment":"production"}
Backend deployed successfully!
Part 2: Deploying Frontend to Vercel
Step 1: Preparing the Frontend
Environment Configuration
Created .env.production:
VITE_API_URL=https:/https://ai-blog-generator-production-bd0c.up.railway.app
This tells the frontend where to find the backend in production.
Vercel Configuration
Created vercel.json for optimal deployment:
{
"buildCommand": "npm run build",
"outputDirectory": "dist",
"framework": "vite",
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
]
}
The rewrites section ensures React Router works properly in production.
Step 2: Deploying to Vercel
Import from GitHub
- Logged into vercel.com
- Clicked "New Project"
- Selected my repository
-
Critical: Set root directory to
frontend
Build Configuration
Vercel auto-detected everything:
Framework: Vite
Build Command: npm run build
Output Directory: dist
Install Command: npm install
Adding Environment Variable
Added the most important variable:
VITE_API_URL = https://ai-blog-generator-production-bd0c.up.railway.app
Clicked "Deploy" and waited...
First deployment: Success!
Got URL: https://frontend-ochre-rho-71.vercel.app/
Challenge #3: CORS Errors
Opened my Vercel URL and... nothing worked.
The browser console showed:
Access to fetch at 'https://ai-blog-generator-production-bd0c.up.railway.app/api/v1/generate'
from origin 'https://frontend-ochre-rho-71.vercel.app' has been blocked by CORS policy
The Problem:
The backend wasn't configured to accept requests from the Vercel domain.
The Solution:
Added CORS_ORIGINS to Railway:
CORS_ORIGINS=https://frontend-ochre-rho-71.vercel.app
Railway auto-redeployed the backend with new CORS settings.
Hard refreshed the frontend (Ctrl+Shift+R) and...
Connected! The status indicator turned green!
Testing in Production
First Blog Generation
Held my breath and tried generating a blog:
Input:
Topic: The Future of Cloud Computing
Tone: Professional
Length: Medium
Keywords: cloud, scalability, innovation
Clicked "Generate Blog Post"...
20 seconds later...
Success!
Title: The Future of Cloud Computing: Embracing Innovation and Scalability
Word Count: 1,247 words
SEO Score: 82
[Full formatted blog content displayed]
Everything worked:
- ✅ AI generation
- ✅ Database storage
- ✅ SEO scoring
- ✅ Copy to clipboard
- ✅ Download as Markdown
- ✅ Blog history
Performance Testing
I generated several blogs to test:
Response Times:
- API health check: ~50ms
- Blog generation: 25-35 seconds (AI processing)
- Blog retrieval: ~100ms
- Blog list: ~150ms
Database:
- Tables created automatically ✅
- Blogs saving correctly ✅
- Queries performing well ✅
Challenges & Solutions Summary
Let me share all the issues I encountered and how I solved them:
1. Module Import Errors
Error:
ModuleNotFoundError: No module named 'app.database'
Solution:
Ensured all imports were absolute from the project root:
from app.database import engine
from app.models.blog import BlogPost
from app.services.ai_service import hf_service
2. Database Tables Not Created
Error:
psycopg2.errors.UndefinedTable: relation "blog_posts" does not exist
Solution:
Added lifespan context manager to create tables on startup:
@asynccontextmanager
async def lifespan(app: FastAPI):
Base.metadata.create_all(bind=engine)
yield
app = FastAPI(lifespan=lifespan)
3. CORS Policy Blocking Requests
Error:
Access to fetch has been blocked by CORS policy
Solution:
Added frontend URL to CORS_ORIGINS in Railway:
CORS_ORIGINS=https://ai-blog-generator.vercel.app
4. Environment Variables Not Loading
Error:
Frontend couldn't connect to backend (showing localhost URL)
Solution:
Ensured VITE_API_URL was set in Vercel's environment variables for all environments (Production, Preview, Development).
5. Hugging Face API 503 Errors
Error:
Model is currently loading
Solution:
This is expected! Hugging Face models go to sleep after inactivity. My retry logic handles it:
if response.status_code == 503:
print("Model loading... waiting 30 seconds")
time.sleep(30)
continue # Retry
After the first request warms up the model, subsequent requests are fast.
Production Optimizations
Backend Optimizations
1. Database Connection Pooling
engine = create_engine(
settings.DATABASE_URL,
pool_pre_ping=True, # Verify connections
pool_size=10, # Connection pool
max_overflow=20 # Max additional connections
)
2. Request Timeout Handling
response = requests.post(
self.api_url,
headers=self.headers,
json=payload,
timeout=120 # 2 minute timeout
)
3. Logging for Production
Added proper logging to track issues:
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# In routes
logger.info(f"Generating blog for topic: {request.topic}")
logger.error(f"Generation failed: {str(e)}")
Frontend Optimizations
1. Environment-based API URL
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
2. Error Boundaries
Added proper error handling:
try {
const result = await blogAPI.generateBlog(formData);
setCurrentBlog(result);
setSuccess(true);
} catch (err) {
const errorMessage = err.response?.data?.detail || 'Failed to generate blog';
setError(errorMessage);
}
3. Loading States
Clear feedback during long operations:
{isGenerating ? (
<>
Generating Content...
</>
) : (
<>
Generate Blog Post
</>
)}
Cost Breakdown
Let's talk money - one of the best parts!
Railway (Backend + Database)
Hobby Plan: $5/month
- Includes backend hosting
- PostgreSQL database (1GB storage)
- Automatic backups
- 99.9% uptime SLA
Free Trial:
- $5 credit/month for free accounts
- Perfect for side projects and portfolios
Vercel (Frontend)
Hobby Plan: FREE
- Unlimited personal projects
- 100GB bandwidth/month
- Global CDN
- Automatic HTTPS
- Custom domains
Hugging Face
Free Tier
- Inference API included
- Rate limits apply (but generous)
- Models sleep after inactivity
- Perfect for demos and low-traffic apps
Total Monthly Cost
$0 - $5/month depending on Railway usage!
For a production-ready, full-stack AI application, that's incredible value.
CI/CD Pipeline: Auto-Deployment
The best part? Both platforms auto-deploy when I push to GitHub!
How It Works
# Make changes
git add .
git commit -m "Add new feature"
git push origin main
# Automatically:
# 1. Railway detects push
# 2. Builds backend
# 3. Runs tests
# 4. Deploys new version
# 5. Zero downtime!
# Similarly:
# 1. Vercel detects push
# 2. Builds frontend
# 3. Deploys to CDN
# 4. Instantly live!
Deployment time: 2-3 minutes for both!
Deployment Logs
Railway and Vercel both provide detailed logs:
Railway logs showed:
Building...
├─ Installing dependencies
├─ Building application
└─ Starting server
✓ Deployment successful
✓ Health check passed
Vercel logs showed:
Building...
├─ Installing dependencies
├─ Running build
├─ Optimizing assets
└─ Uploading to CDN
✓ Build completed
✓ Deployment live
Monitoring & Maintenance
Railway Monitoring
Railway dashboard shows:
- CPU Usage: ~5-10% average
- Memory: ~150MB
- Response Times: 50-100ms (excluding AI)
- Uptime: 99.9%
Vercel Analytics
Enabled Vercel Analytics (free!) to track:
- Page views
- User locations
- Performance metrics
- Core Web Vitals
Database Monitoring
Checked PostgreSQL usage:
- Storage: ~5MB (100 blog posts)
- Connections: 2-3 active
- Queries: Fast (<100ms)
Everything running smoothly!
Lessons Learned
Here are the key takeaways from this deployment journey:
1. Start with Simple Deployment
Don't overcomplicate. Railway and Vercel handle 90% of DevOps for you.
2. Environment Variables Are Critical
Spent hours debugging before realizing a typo in VITE_API_URL. Triple-check these!
3. CORS Will Get You
Always configure CORS properly. Test with actual frontend URL, not localhost.
4. Database Initialization Matters
In production, tables won't magically appear. Handle database setup in application lifecycle.
5. Logs Are Your Best Friend
Both Railway and Vercel have excellent logging. Use them to debug issues.
6. Test Production Early
Deploy early, deploy often. Catching issues in production is better than finding them in interviews!
7. Free Tiers Are Powerful
You can build and deploy production apps for $0-5/month. No excuses!
Future Improvements
Now that we're in production, here's what I'm planning:
Immediate (This Week)
- [ ] Add rate limiting to prevent abuse
- [ ] Set up error monitoring (Sentry)
- [ ] Add database backup automation
- [ ] Create health check monitoring
Short-term (This Month)
- [ ] Add user authentication
- [ ] Implement blog favorites
- [ ] Add export to PDF
- [ ] Create API documentation site
Long-term (This Quarter)
- [ ] Multi-language support
- [ ] Image generation for blogs
- [ ] Content scheduling
- [ ] Analytics dashboard
- [ ] WordPress integration
Final Thoughts
Deploying this project taught me more than months of tutorials ever could.
What worked well:
- Railway's automatic PostgreSQL setup
- Vercel's instant deployments
- GitHub integration for CI/CD
- Simple, clean architecture
What I'd do differently:
- Set up monitoring from day one
- Add comprehensive error logging earlier
- Test with production environment variables locally
- Document deployment steps as I go
Most importantly: I now have a live, working application in my portfolio. Something I can show in interviews, share with friends, and actually use!
Resources
Tools and platforms used:
Documentation that helped:
Try It Yourself!
The complete code is open source! Feel free to:
- Star the repository
- Fork and customize
- Report issues
- Submit pull requests
This project reinforced that deployment is where architectural decisions are validated—and where many “working” projects quietly fail
🙏 Thank You!
Thanks for following along on this journey!
Part 1 covered the development process.
Part 2 (this post) covered deployment.
What did you learn? What would you do differently? Drop a comment below!
If you found this helpful:
- ❤️ Like this post
- 💬 Leave a comment
- 🔄 Share with others
- 👀 Follow me for more content
Connect With Me
I'd love to hear your thoughts and answer questions!
What's Next?
I'm already working on v2.0 with exciting features:
- 🔐 User authentication
- 🖼️ AI-generated blog images
- 📅 Content scheduling
- 📊 Analytics dashboard
Stay tuned! Follow me to get notified when the next update drops.


Top comments (0)