DEV Community

Cover image for From Localhost to Production: Deploying an AI Blog Generator (FastAPI + React)
Lymah
Lymah Subscriber

Posted on

From Localhost to Production: Deploying an AI Blog Generator (FastAPI + React)

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.

Deployment success

frontent-vercel


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
  1. Live Demo
  2. GitHub

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:

  1. PostgreSQL Integration - One-click database setup
  2. GitHub Integration - Automatic deployments on push
  3. Developer Experience - Clean UI, clear logs, easy configuration
  4. Pricing - $5/month hobby plan is perfect for side projects
  5. Zero Config - Railway auto-detects Python and handles everything

Why Vercel for Frontend?

Vercel was the obvious choice for the React frontend:

  1. Built for React/Vite - Optimized for frontend frameworks
  2. Global CDN - Lightning-fast content delivery
  3. Automatic HTTPS - SSL certificates out of the box
  4. Preview Deployments - Every PR gets its own URL
  5. Free Tier - Perfect for personal projects

Architecture Overview

Here's what we're building:

┌─────────────┐     HTTPS      ┌─────────────┐
│             │◄───────────────►│             │
│   Vercel    │                 │   Railway   │
│  (Frontend) │                 │  (Backend)  │
│             │                 │             │
└─────────────┘                 └──────┬──────┘
                                       │
                                       │
                                ┌──────▼──────┐
                                │ PostgreSQL  │
                                │  Database   │
                                └─────────────┘
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

Key points:

  • NIXPACKS auto-detects Python and installs dependencies
  • --host 0.0.0.0 allows external connections
  • $PORT uses 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
Enter fullscreen mode Exit fullscreen mode

Step 2: Setting Up Railway

Creating the Project

The process was surprisingly smooth:

  1. Went to railway.app
  2. Clicked "Start a New Project"
  3. Selected "Deploy from GitHub repo"
  4. Chose my ai-blog-generator repository
  5. Important: Selected backend as the root directory

Railway immediately started building!

Adding PostgreSQL

This was the easiest database setup I've ever done:

Click "New" → "Database" → "PostgreSQL"
Enter fullscreen mode Exit fullscreen mode

Railway automatically:

  • Provisioned a PostgreSQL instance
  • Created the DATABASE_URL environment 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Railway detected the push and started building...

And it failed. 😅

Challenge #1: Import Errors

The Problem:

ModuleNotFoundError: No module named 'app.database'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Now tables are created automatically on every deployment!

Step 5: Generating a Domain

Railway provides a free subdomain:

Settings → Networking → Generate Domain
Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

Backend deployed successfully!

backend deployment logs

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
Enter fullscreen mode Exit fullscreen mode

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"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The rewrites section ensures React Router works properly in production.

Step 2: Deploying to Vercel

Import from GitHub

  1. Logged into vercel.com
  2. Clicked "New Project"
  3. Selected my repository
  4. 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
Enter fullscreen mode Exit fullscreen mode

Adding Environment Variable

Added the most important variable:

VITE_API_URL = https://ai-blog-generator-production-bd0c.up.railway.app
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Railway auto-redeployed the backend with new CORS settings.

Hard refreshed the frontend (Ctrl+Shift+R) and...
Connected! The status indicator turned green!

frontend deployment dashboard

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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

2. Database Tables Not Created

Error:

psycopg2.errors.UndefinedTable: relation "blog_posts" does not exist
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

3. CORS Policy Blocking Requests

Error:

Access to fetch has been blocked by CORS policy
Enter fullscreen mode Exit fullscreen mode

Solution:
Added frontend URL to CORS_ORIGINS in Railway:

CORS_ORIGINS=https://ai-blog-generator.vercel.app
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

After the first request warms up the model, subsequent requests are fast.

Swagger UI

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
)
Enter fullscreen mode Exit fullscreen mode

2. Request Timeout Handling

response = requests.post(
    self.api_url,
    headers=self.headers,
    json=payload,
    timeout=120  # 2 minute timeout
)
Enter fullscreen mode Exit fullscreen mode

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)}")
Enter fullscreen mode Exit fullscreen mode

Frontend Optimizations

1. Environment-based API URL

const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

3. Loading States

Clear feedback during long operations:

{isGenerating ? (
  <>

    Generating Content...
  </>
) : (
  <>

    Generate Blog Post
  </>
)}
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Vercel logs showed:

Building...
├─ Installing dependencies
├─ Running build
├─ Optimizing assets
└─ Uploading to CDN

✓ Build completed
✓ Deployment live
Enter fullscreen mode Exit fullscreen mode

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!

Dashboard

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!

Live Demo

GitHub Repository

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)