Skip to main content

Overview

This guide covers deploying Tresa Contafy API to production environments, with specific instructions for Railway and general deployment workflows.

Prerequisites

  • PostgreSQL database configured with SSL
  • All environment variables configured
  • Domain name and SSL certificate (recommended)
  • CI/CD pipeline setup (recommended)

Pre-Deployment Checklist

1

Environment Variables

Verify all required variables are set:
 NODE_ENV=production
 DATABASE_URL (with SSL)
 JWT_SECRET (strong random value)
 JWT_REFRESH_SECRET (strong random value)
 BREVO_API_KEY
 BREVO_FROM_EMAIL
 APP_URL
 FRONTEND_URL (for CORS)
2

Generate Secure Secrets

Generate production JWT secrets:
# Generate JWT_SECRET
openssl rand -base64 32

# Generate JWT_REFRESH_SECRET
openssl rand -base64 32
Never reuse development secrets in production!
3

Database Ready

Ensure PostgreSQL is accessible and migrations are ready:
# Test database connection
psql $DATABASE_URL -c "SELECT version();"
4

Build Application

Verify the application builds without errors:
pnpm install --production=false
pnpm build

Deployment Methods

1

Create Project

  1. Go to railway.app
  2. Click “New Project”
  3. Select “Deploy from GitHub repo”
  4. Connect your repository
2

Add PostgreSQL Database

  1. Click “New” → “Database” → “Add PostgreSQL”
  2. Railway provisions the database automatically
  3. Note the DATABASE_URL is available as ${{Postgres.DATABASE_URL}}
3

Configure Environment Variables

Add all required variables in Railway dashboard:
NODE_ENV=production
DATABASE_URL=${{Postgres.DATABASE_URL}}
JWT_SECRET=<your-generated-secret>
JWT_REFRESH_SECRET=<your-generated-secret>
BREVO_API_KEY=<your-brevo-key>
BREVO_FROM_EMAIL=[email protected]
BREVO_FROM_NAME=Tresa Contafy
APP_URL=https://yourdomain.com
FRONTEND_URL=https://yourdomain.com
API_RATE_LIMIT_MAX=1000
LOG_LEVEL=info
4

Configure Build Settings

Railway auto-detects settings, but verify:
  • Build Command: pnpm install && pnpm build
  • Start Command: pnpm start
  • Root Directory: / (or path to API if monorepo)
5

Run Migrations

After first deployment, run migrations:
  1. Go to your service in Railway
  2. Click “Deployments” → Latest deployment → “View Logs”
  3. In “Settings” → “Deploy”, add a deploy command:
pnpm db:migrate && pnpm start
Or run manually via Railway CLI:
railway run pnpm db:migrate
6

Setup Custom Domain

  1. Go to “Settings” → “Domains”
  2. Click “Custom Domain”
  3. Add your domain (e.g., api.tresacontafy.com)
  4. Configure DNS records as shown by Railway

Manual Deployment

For VPS or custom hosting:
1

Install Dependencies

SSH into your server and install Node.js and pnpm:
# Install Node.js 18+
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs

# Install pnpm
npm install -g pnpm
2

Clone Repository

git clone <your-repo-url>
cd tresa-contafy
3

Install & Build

pnpm install --production=false
pnpm build
4

Set Environment Variables

Create .env file with production values:
nano .env
# Add all environment variables
5

Run Migrations

pnpm db:migrate
6

Start with Process Manager

Use PM2 to keep the application running:
# Install PM2
npm install -g pm2

# Start application
NODE_ENV=production pm2 start dist/index.js --name tresa-api

# Save PM2 configuration
pm2 save
pm2 startup

Deployment Script

For manual deployments, create a deploy.sh script:
deploy.sh
#!/bin/bash

set -e

echo "🚀 Starting deployment..."

# 1. Install dependencies
echo "📦 Installing dependencies..."
pnpm install --production=false

# 2. Build TypeScript
echo "🔨 Building application..."
pnpm build

# 3. Run migrations
echo "🗄️  Running database migrations..."
pnpm db:migrate

# 4. Start application
echo "✅ Starting application..."
NODE_ENV=production pnpm start
Make it executable:
chmod +x deploy.sh
./deploy.sh

CI/CD Pipeline

GitHub Actions Example

Create .github/workflows/deploy.yml:
name: Deploy to Production

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      
      - name: Install pnpm
        run: npm install -g pnpm
      
      - name: Install dependencies
        run: pnpm install
      
      - name: Run tests
        run: pnpm test
      
      - name: Build application
        run: pnpm build
      
      - name: Deploy to Railway
        run: |
          npm install -g @railway/cli
          railway up --service tresa-api
        env:
          RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}

Post-Deployment Verification

1

Health Check

Verify the health endpoint responds:
curl https://api.yourdomain.com/health
Expected response:
{
  "status": "ok",
  "timestamp": "2026-03-07T12:00:00.000Z",
  "uptime": 123.456,
  "environment": "production"
}
2

Test Authentication

Test user registration endpoint:
curl -X POST https://api.yourdomain.com/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "Test123!",
    "name": "Test User"
  }'
3

Check Logs

Monitor application logs for errors:Railway:
  • Go to your service → “Deployments” → “View Logs”
PM2:
pm2 logs tresa-api
4

Verify Database

Check migrations ran successfully:
# On Railway
railway run pnpm db:migrate:status

# On VPS
pnpm db:migrate:status

Production Configuration

Trust Proxy (Railway)

Railway uses a reverse proxy. The application automatically enables proxy trust:
src/server.ts:33
if (process.env.NODE_ENV === 'production') {
  app.set('trust proxy', 1);
}
This ensures:
  • Correct client IP addresses in logs
  • Proper rate limiting per client
  • Accurate request headers

Rate Limiting

Production rate limits:
  • General API: 1000 requests / 15 minutes per IP
  • Auth endpoints: 5 requests / 15 minutes per IP
src/server.ts:67
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: process.env.NODE_ENV === 'production' ? 1000 : 2000,
});

CORS Configuration

CORS is configured based on FRONTEND_URL or APP_URL:
src/server.ts:41
const corsOptions = {
  origin: process.env.FRONTEND_URL || process.env.APP_URL,
  credentials: true,
};
If neither FRONTEND_URL nor APP_URL is set in production, CORS is disabled for security.

Monitoring & Logging

Health Check Endpoint

GET /health
Returns:
{
  "status": "ok",
  "timestamp": "2026-03-07T12:00:00.000Z",
  "uptime": 3600.5,
  "environment": "production"
}
Use this for:
  • Load balancer health checks
  • Uptime monitoring (UptimeRobot, Pingdom)
  • Container orchestration health probes

Logging

Production uses structured JSON logging with Pino:
src/utils/logger.util.ts:7
const isProduction = process.env.NODE_ENV === 'production';

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  formatters: {
    level: (label) => ({ level: label }),
  },
  timestamp: pino.stdTimeFunctions.isoTime,
});
Log levels:
  • fatal - Application crash
  • error - Error conditions
  • warn - Warning conditions
  • info - Informational messages (default)
  • debug - Debug messages
  • trace - Very detailed tracing

Rollback Procedure

1

Identify Issue

Check logs and metrics to confirm deployment issue
2

Railway Rollback

  1. Go to “Deployments”
  2. Find the last working deployment
  3. Click ”⋯” → “Redeploy”
3

Manual Rollback

# Checkout previous version
git checkout <previous-commit-hash>

# Rebuild and restart
pnpm build
pm2 restart tresa-api
4

Database Rollback (if needed)

# Undo last migration
pnpm db:migrate:undo
Only rollback migrations if the new migration is causing issues.

Scaling Considerations

Horizontal Scaling

For high traffic, run multiple instances:
  • Railway: Increase “Replicas” in Settings
  • Load Balancer: Use Nginx or cloud load balancer
  • Session Management: Use Redis for session storage

Database Connection Pool

Adjust based on instance count:
pool: {
  max: 5, // Max per instance
  min: 1,
}
Formula: total_connections = instances × pool.max Ensure PostgreSQL max_connections > total connections

Troubleshooting

Check:
  • Verify DATABASE_URL is accessible
  • Ensure all required env vars are set
  • Check build completed successfully
  • Review logs for errors
Possible causes:
  • Application crashed (check logs)
  • Port mismatch (ensure PORT env var matches)
  • Health check failing
Solutions:
  • Verify SSL is enabled for production DB
  • Check database is running and accessible
  • Verify connection string is correct
  • Check firewall rules
Solutions:
  • Increase API_RATE_LIMIT_MAX
  • Ensure trust proxy is enabled
  • Check if IP detection is working correctly

Next Steps

Security

Configure security features and best practices

Monitoring

Set up monitoring and alerting

Build docs developers (and LLMs) love