Skip to main content

Overview

This guide covers deploying the Portal Self-Service Backend API to production environments, including configuration, build steps, and best practices.

Prerequisites

Node.js

Node.js 18.x or later installed on the server

MSSQL Database

SQL Server instance accessible from deployment server

Azure AD

Azure AD app registration configured for authentication

Environment Variables

All required environment variables configured

Pre-Deployment Checklist

1

Database Setup

  • Ensure production database is created
  • Verify database user has appropriate permissions
  • Run any required schema migrations
  • Test database connectivity from deployment server
2

Environment Configuration

  • Prepare production .env file with all required variables
  • Use strong, unique passwords for production
  • Configure Azure AD with production URLs
  • Set FRONTEND_URL and BASE_URL to production domains
3

Azure AD Configuration

  • Update Redirect URIs in Azure AD app registration
  • Configure API permissions
  • Add production frontend URL to authorized origins
  • Note down tenant ID and application ID
4

Security Review

  • Review CORS configuration for production domain
  • Ensure SSL/TLS certificates are valid
  • Consider setting trustServerCertificate: false for database
  • Review and minimize exposed ports

Application Structure

The Portal Self-Service Backend is an ES6 module-based Node.js application:
package.json
{
  "name": "portal-selfservice-backend",
  "version": "1.0.0",
  "type": "module",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
  }
}
The "type": "module" setting enables ES6 imports. All imports use .js extensions.

Deployment Methods

Method 1: Traditional Server Deployment

1

Clone Repository

git clone https://github.com/your-org/portal-selfservice-backend.git
cd portal-selfservice-backend
2

Install Dependencies

npm install --production
The --production flag skips development dependencies like nodemon.
3

Configure Environment

Create a .env file with production values:
nano .env
See Environment Configuration for all required variables.
4

Start Application

npm start
For production, use a process manager (see below).

Method 2: Docker Deployment

Docker Support

Create a Dockerfile for containerized deployment:
FROM node:18-alpine

# Set working directory
WORKDIR /app

# Copy package files
COPY package*.json ./

# Install production dependencies
RUN npm ci --production

# Copy application source
COPY . .

# Create uploads directory
RUN mkdir -p public/uploads

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \
  CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1);})"

# Start application
CMD ["npm", "start"]

Method 3: Azure App Service

1

Prepare for Azure

Ensure your code is in a Git repository accessible to Azure.
2

Create App Service

# Using Azure CLI
az webapp create \
  --resource-group portal-rg \
  --plan portal-plan \
  --name portal-selfservice-api \
  --runtime "NODE:18-lts"
3

Configure Environment Variables

az webapp config appsettings set \
  --resource-group portal-rg \
  --name portal-selfservice-api \
  --settings \
    DB_USER="prod_user" \
    DB_PASSWORD="***" \
    DB_SERVER="sql-server.database.windows.net" \
    DB_DATABASE="PortalSelfService" \
    DB_PORT="1433" \
    AZURE_ISSUER_BASE_URL="https://login.microsoftonline.com/tenant-id/v2.0" \
    AZURE_AUDIENCE="api://app-id" \
    FRONTEND_URL="https://portal.yourcompany.com" \
    BASE_URL="https://api.yourcompany.com"
4

Deploy Code

# Deploy from Git
az webapp deployment source config \
  --resource-group portal-rg \
  --name portal-selfservice-api \
  --repo-url https://github.com/your-org/portal-selfservice-backend \
  --branch main

Process Management

PM2 Process Manager

PM2 keeps your application running, restarts on crashes, and provides monitoring.
1

Install PM2

npm install -g pm2
2

Create PM2 Configuration

ecosystem.config.cjs
module.exports = {
  apps: [{
    name: 'portal-selfservice-api',
    script: './server.js',
    instances: 2,
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'production',
      PORT: 3000
    },
    error_file: './logs/err.log',
    out_file: './logs/out.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
    merge_logs: true,
    autorestart: true,
    watch: false,
    max_memory_restart: '1G'
  }]
};
3

Start with PM2

# Start application
pm2 start ecosystem.config.cjs

# Save process list
pm2 save

# Setup startup script
pm2 startup
4

Monitor Application

# View status
pm2 status

# View logs
pm2 logs portal-selfservice-api

# Monitor resources
pm2 monit

Using systemd

[Unit]
Description=Portal Self-Service Backend API
After=network.target

[Service]
Type=simple
User=nodeapp
WorkingDirectory=/opt/portal-selfservice-backend
Environment="NODE_ENV=production"
EnvironmentFile=/opt/portal-selfservice-backend/.env
ExecStart=/usr/bin/node /opt/portal-selfservice-backend/server.js
Restart=on-failure
RestartSec=10
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=portal-api

[Install]
WantedBy=multi-user.target

Server Startup Flow

The application follows this startup sequence:
server.js
import 'dotenv/config';
import app from './src/app.js';
import { poolPromise } from './src/config/db.config.js';
import { initSocket } from './src/config/socket.js';
import http from 'http';

const PORT = process.env.PORT || 3000;

// Health check endpoint
app.get('/health', (req, res) => {
  res.status(200).send({ status: 'UP', message: 'Express Server Running' });
});

const server = http.createServer(app);
initSocket(server); // Initialize WebSocket

async function startServer() {
    try {
        await poolPromise; // Wait for DB connection
        
        server.listen(PORT, () => {
            console.log(`Servidor escuchando en http://localhost:${PORT}`);
        });
    } catch (error) {
        console.error('Error fatal: La aplicación no pudo iniciar.', error.message);
        process.exit(1); 
    }
}

startServer();
The application will not start if the database connection fails. This ensures data integrity and prevents requests to an unhealthy service.

Health Checks

Health Endpoint

The application provides a health check endpoint at /health:
curl http://your-server:3000/health
Response:
{
  "status": "UP",
  "message": "Express Server Running"
}

Monitoring Checklist

HTTP Health

  • Monitor /health endpoint (should return 200)
  • Set up uptime monitoring (e.g., Pingdom, UptimeRobot)

Database Connection

  • Monitor SQL Server connection pool
  • Track query performance
  • Set alerts for connection failures

Application Logs

  • Collect and analyze application logs
  • Monitor for authentication failures
  • Track error rates

System Resources

  • Monitor CPU and memory usage
  • Track disk space (especially for uploads)
  • Monitor network connectivity

Reverse Proxy Setup

Nginx Configuration

Production Recommendation

Use Nginx as a reverse proxy for SSL termination, load balancing, and security.
/etc/nginx/sites-available/portal-api
upstream portal_backend {
    server localhost:3000;
    keepalive 64;
}

server {
    listen 80;
    server_name api.yourcompany.com;
    
    # Redirect HTTP to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.yourcompany.com;
    
    # SSL Configuration
    ssl_certificate /etc/ssl/certs/portal-api.crt;
    ssl_certificate_key /etc/ssl/private/portal-api.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    
    # Security Headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    
    # Proxy Settings
    location / {
        proxy_pass http://portal_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
    
    # WebSocket Support
    location /socket.io/ {
        proxy_pass http://portal_backend/socket.io/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
    # Static Files
    location /uploads/ {
        alias /opt/portal-selfservice-backend/public/uploads/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}
1

Enable Configuration

sudo ln -s /etc/nginx/sites-available/portal-api /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Production Environment Variables

Critical: Use production-specific values for all environment variables. Never use development credentials in production.
.env.production
# Server
PORT=3000
NODE_ENV=production

# Database - Production
DB_USER=prod_portal_user
DB_PASSWORD=ComplexPr0d_P@ssw0rd_2024!
DB_SERVER=sql-prod.database.windows.net
DB_DATABASE=PortalSelfServiceProd
DB_PORT=1433

# Azure AD - Production
AZURE_ISSUER_BASE_URL=https://login.microsoftonline.com/your-prod-tenant-id/v2.0
AZURE_AUDIENCE=api://prod-backend-api-id

# URLs - Production
FRONTEND_URL=https://portal.yourcompany.com
BASE_URL=https://api.yourcompany.com

Post-Deployment Verification

1

Health Check

curl https://api.yourcompany.com/health
Expected: {"status":"UP","message":"Express Server Running"}
2

API Documentation

Visit Swagger UI:
https://api.yourcompany.com/docs
3

Authentication Test

Test a protected endpoint with a valid Azure AD token:
curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://api.yourcompany.com/api/your-protected-endpoint
4

WebSocket Connection

Verify Socket.io is accessible:
curl https://api.yourcompany.com/socket.io/socket.io.js
5

Database Connectivity

Check application logs for:
Conexión a SQL Server establecida.

Troubleshooting Production Issues

Application Won’t Start

Database Connection Failed

Symptoms: Application exits immediately with database errorSolutions:
  • Verify database credentials in .env
  • Check database server is accessible (firewall rules)
  • Ensure database exists
  • Test connection with SQL client tool

401 Authentication Errors

Azure AD Token Validation Failed

Symptoms: All requests return 401 UnauthorizedSolutions:
  • Verify AZURE_ISSUER_BASE_URL and AZURE_AUDIENCE are correct
  • Check Azure AD app registration configuration
  • Ensure frontend is requesting tokens with correct scope
  • Verify token isn’t expired

CORS Errors in Production

CORS Policy Blocking Requests

Symptoms: Browser console shows CORS errorsSolutions:
  • Update FRONTEND_URL to production domain
  • Modify corsOptions in src/app.js to use process.env.FRONTEND_URL
  • Restart application after changes
  • Clear browser cache

WebSocket Connection Issues

Socket.io Connection Failed

Symptoms: Real-time features not workingSolutions:
  • Verify WebSocket support in reverse proxy (Nginx/Apache)
  • Check FRONTEND_URL matches frontend domain
  • Ensure firewall allows WebSocket connections
  • Test Socket.io endpoint directly

Scaling Considerations

Horizontal Scaling

  • Use PM2 cluster mode for multi-core servers
  • Deploy multiple instances behind load balancer
  • Implement sticky sessions for WebSocket connections

Database Optimization

  • Monitor connection pool usage
  • Adjust pool size based on load
  • Consider read replicas for heavy read operations

File Storage

  • Move uploads to Azure Blob Storage or S3
  • Implement CDN for static files
  • Set up automated backups

Monitoring & Logging

  • Implement Application Insights or similar APM
  • Set up centralized logging (ELK, Splunk)
  • Configure alerts for errors and performance

Rollback Strategy

1

Maintain Previous Version

Keep the previous deployment available:
/opt/portal-selfservice-backend-v1.0.0/
/opt/portal-selfservice-backend-v1.0.1/ (current)
2

Quick Rollback with PM2

pm2 stop portal-selfservice-api
cd /opt/portal-selfservice-backend-v1.0.0
pm2 start ecosystem.config.cjs
3

Database Rollback Plan

  • Maintain database backups before deployments
  • Test rollback procedures in staging
  • Document schema migration reversals

Next Steps

Database Setup

Learn about database configuration and troubleshooting

Environment Config

Complete reference for all environment variables

API Documentation

Access Swagger UI at /docs endpoint

Monitoring Guide

Set up application monitoring and alerts