Debugging a Full-Stack Application Deployment: My Journey from Local to Railway Cloud

Debugging a Full-Stack Application Deployment: My Journey from Local to Railway Cloud

The Starting Point

I’m deploying an enterprise document Q&A system — a FastAPI backend with a React frontend — containerized with Docker and prepped for Railway. Everything looked correct, but the app still wouldn’t deploy.

Discovery: The API Routes Were Unreachable

My first indication something was wrong came when testing the deployed application:

$ curl https://enterprise-document-qa-production.thanhphongle.net/api/health
<!doctype html><html lang="en"><head><meta charset="utf-8"/>...

Instead of JSON, I was getting HTML - the React app. My API endpoints were being swallowed by something.

Investigation Phase: Understanding the Route Structure

I needed to understand what was happening in my backend routing. Starting with the basics:

$ cat backend/api.py | head -50
# backend/api.py
from fastapi import FastAPI, HTTPException
...
@app.post("/query", response_model=QueryResponse)

This showed me my routes were defined without the /api prefix. Next, I searched for all route definitions:

$ grep -n "@app\." backend/api.py
40:@app.on_event("startup")
50:@app.post("/query", response_model=QueryResponse)
67:@app.get("/health")
82:    @app.get("/{path:path}")

Root Cause #1: The Catch-All Route Problem

Line 82 caught my attention - a catch-all route with unusual indentation. I needed context:

$ cat backend/api.py
...
if os.path.exists(frontend_build):
    app.mount("/static", StaticFiles(directory=f"{frontend_build}/static"), name="static")
    @app.get("/{path:path}")
    async def serve_react_app(path: str):
        file_path = os.path.join(frontend_build, path)
        if os.path.exists(file_path) and os.path.isfile(file_path):
            return FileResponse(file_path)
        return FileResponse(os.path.join(frontend_build, "index.html"))

The issue was clear: my catch-all route /{path:path} was intercepting ALL requests, including my API endpoints. It was only registered when the frontend build existed, but when active, it caught everything.

The First Fix: Adding API Route Prefixes

I realized I needed to separate my API routes from the frontend routes. I modified the backend to use /api prefixes:

# Before
@app.post("/query", response_model=QueryResponse)
@app.get("/health")

# After
@app.post("/api/query", response_model=QueryResponse)
@app.get("/api/health")

And updated the catch-all route to exclude API paths:

@app.get("/{path:path}")
async def serve_react_app(path: str):
    # Don't catch API routes
    if path.startswith("api/"):
        raise HTTPException(status_code=404, detail="Not Found")
    file_path = os.path.join(frontend_build, path)
    if os.path.exists(file_path) and os.path.isfile(file_path):
        return FileResponse(file_path)
    return FileResponse(os.path.join(frontend_build, "index.html"))

Deployment and Verification

After making the fix, I deployed to Railway:

$ railway up --detach
Indexed
Compressed [====================] 100%
Uploaded
Build Logs: https://railway.com/project/a9f1f9d6-d4f9-46fa-b8fc-4834410e812b/...

Checking the logs to ensure successful startup:

$ railway logs --tail 10
Starting Container
INFO:     Started server process [2]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)

The Moment of Truth: Testing the Fixed Routes

Testing the health endpoint:

$ curl https://enterprise-document-qa-production.thanhphongle.net/api/health
{"status":"healthy","qa_system_initialized":true}

Success! The API was responding with JSON instead of HTML. I verified the frontend still worked:

$ curl https://enterprise-document-qa-production.thanhphongle.net/ | grep -o '<title>.*</title>'
<title>React App</title>

And finally, testing the main query endpoint:

$ curl -X POST https://enterprise-document-qa-production.thanhphongle.net/api/query \
  -H "Content-Type: application/json" \
  -d '{"question": "What is machine learning?"}'
{"question":"What is machine learning?","answer":"I don't know the answer...","sources":4}

Reflection: Lessons Learned

This debugging journey taught me several valuable lessons:

  1. Route Order Matters: In FastAPI, as in many web frameworks, route registration order is crucial. Catch-all routes must come last, or they'll intercept everything.

  2. Systematic Investigation: Rather than making assumptions, I used targeted CLI commands to understand the actual state of my application.

  3. Minimal Changes: Instead of rewriting large portions of code, I made surgical fixes - adding the /api prefix and a simple path check.

  4. Verification at Each Step: After each change, I verified both that my fix worked AND that I hadn't broken anything else.

The entire process reinforced my belief in methodical debugging. By understanding the system before making changes, I avoided introducing new bugs while fixing the original issue.

My Debugging CLI Toolkit

Throughout this experience, I built a collection of commands that proved invaluable:

File Inspection Commands

# View specific lines in a file
cat filename | head -50

# Search for patterns with line numbers
grep -n "pattern" filename

# View specific line range
sed -n '80,90p' filename

# Check file existence and structure
ls -la

Docker and Deployment Commands

# Deploy to Railway
railway up --detach

# Check deployment logs
railway logs --tail 20

# Test endpoints
curl -I https://your-url.com
curl https://your-url.com/api/endpoint
curl -X POST https://your-url.com/api/endpoint \
  -H "Content-Type: application/json" \
  -d '{"key": "value"}'

# Extract specific HTML elements
curl https://your-url.com | grep -o '<title>.*</title>'

Process Management

# Check port usage
lsof -i :8000

# Start FastAPI server locally
python -m uvicorn api:app --host 0.0.0.0 --port 8000 --reload

These commands became my surgical instruments, allowing me to diagnose and fix issues with precision rather than guesswork.

Conclusion

What began as a frustrating deployment failure became a valuable lesson. By staying systematic and using CLI tools deliberately, I resolved the issue and gained a deeper grasp of routing priorities in modern web frameworks. The app now runs smoothly on Railway with cleanly separated API and frontend routes.


This debugging journey took place while deploying an enterprise document Q&A system built with FastAPI, React, and Azure OpenAI, containerized with Docker and deployed on Railway cloud.


If you enjoyed this article, you can also find it published on LinkedIn and Medium.