Master DevOps practices and deployment strategies including Docker containerization, CI/CD pipelines, cloud deployment, and monitoring for Node.js applications.
Master DevOps practices and deployment strategies including Docker containerization, CI/CD pipelines, cloud deployment, and monitoring for Node.js applications.
Learn Docker containerization for Node.js applications including multi-stage builds, optimization, and best practices.
Content by: Praveen Kumar
MERN Stack Developer
Docker allows you to package applications and their dependencies into lightweight, portable containers that can run consistently across different environments.
# Basic Dockerfile for Node.js
FROM node:18-alpine
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy application code
COPY . .
# Expose port
EXPOSE 3000
# Start application
CMD ["npm", "start"]# Multi-stage Dockerfile for optimization
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM node:18-alpine AS production
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
WORKDIR /app
# Copy built application
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/package*.json ./
USER nextjs
EXPOSE 3000
CMD ["node", "dist/index.js"]# docker-compose.yml for full stack application
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:password@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
volumes:
- ./logs:/app/logs
restart: unless-stopped
db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=myapp
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
restart: unless-stopped
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- app
restart: unless-stopped
volumes:
postgres_data:
redis_data:# Complete production Docker setup
# Dockerfile
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci --only=production
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build the application
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]
# .dockerignore
node_modules
npm-debug.log
.next
.git
.env.local
.env.development.local
.env.test.local
.env.production.local
README.md
Dockerfile
.dockerignore
# docker-compose.prod.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:password@db:5432/myapp
depends_on:
- db
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=myapp
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
postgres_data:Test your understanding of this topic:
Learn to set up continuous integration and continuous deployment pipelines for Node.js applications using GitHub Actions and other CI/CD tools.
Content by: Sachin Patel
Node.js Developer
Continuous Integration and Continuous Deployment automate the process of building, testing, and deploying applications, ensuring faster and more reliable releases.
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node - version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node - version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test -- --coverage --watchAll=false
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '18.x'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
env:
NODE_ENV: production
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Push Docker image
run: |
docker tag myapp:${{ github.sha }} myapp:latest
docker push myapp:${{ github.sha }}
docker push myapp:latest
- name: Deploy to AWS ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: task-definition.json
service: myapp-service
cluster: myapp-cluster# .github/workflows/deploy-staging.yml
name: Deploy to Staging
on:
push:
branches: [ develop ]
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v3
- name: Deploy to staging
run: |
echo "Deploying to staging environment"
# Add staging deployment commands here
- name: Run smoke tests
run: |
echo "Running smoke tests against staging"
# Add smoke test commands here
---
# .github/workflows/deploy-production.yml
name: Deploy to Production
on:
push:
tags:
- 'v*'
jobs:
deploy-production:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v3
- name: Deploy to production
run: |
echo "Deploying to production environment"
# Add production deployment commands here# .github/workflows/security.yml
name: Security Scan
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run npm audit
run: npm audit --audit-level moderate
- name: Run Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
- name: Run CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
languages: javascript# Complete CI/CD pipeline with multiple environments
# .github/workflows/complete-pipeline.yml
name: Complete CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
NODE_VERSION: '18.x'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# Test job
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node - version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test -- --coverage --watchAll=false
- name: Upload coverage
uses: codecov/codecov-action@v3
# Build and push Docker image
build:
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push'
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Container Registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Deploy to staging
deploy-staging:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/develop'
environment: staging
steps:
- name: Deploy to staging
run: |
echo "Deploying to staging environment"
# Add staging deployment commands
# Deploy to production
deploy-production:
runs-on: ubuntu-latest
needs: build
if: startsWith(github.ref, 'refs/tags/v')
environment: production
steps:
- name: Deploy to production
run: |
echo "Deploying to production environment"
# Add production deployment commands
# Notify deployment status
notify:
runs-on: ubuntu-latest
needs: [deploy-staging, deploy-production]
if: always()
steps:
- name: Notify Slack
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
channel: '#deployments'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}Test your understanding of this topic:
Learn to deploy Node.js applications to various cloud platforms including AWS, Google Cloud, and Azure.
Content by: Parth Patel
Node.js Developer
Deploy Node.js applications to AWS using services like ECS, Lambda, and Elastic Beanstalk.
# task-definition.json
{
"family": "myapp-task",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"executionRoleArn": "arn:aws:iam::account:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::account:role/ecsTaskRole",
"containerDefinitions": [
{
"name": "myapp",
"image": "myapp:latest",
"portMappings": [
{
"containerPort": 3000,
"protocol": "tcp"
}
],
"environment": [
{
"name": "NODE_ENV",
"value": "production"
}
],
"secrets": [
{
"name": "DATABASE_URL",
"valueFrom": "arn:aws:ssm:region:account:parameter/myapp/database-url"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/myapp",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
},
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3
}
}
]
}# serverless.yml for AWS Lambda
service: myapp
provider:
name: aws
runtime: nodejs18.x
region: us-east-1
environment:
NODE_ENV: production
iam:
role:
statements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource: "arn:aws:dynamodb:us-east-1:*:table/myapp-*"
functions:
api:
handler: src/handler.api
events:
- http:
path: /{proxy+}
method: ANY
cors: true
timeout: 30
memorySize: 512
processData:
handler: src/handler.processData
events:
- schedule: rate(5 minutes)
timeout: 60
memorySize: 1024
plugins:
- serverless-offline
- serverless-dynamodb-local
custom:
serverless-offline:
httpPort: 3000
dynamodb:
start:
port: 8000
inMemory: true
migrate: true# app.yaml for Google App Engine
runtime: nodejs18
env_variables:
NODE_ENV: production
DATABASE_URL: "postgresql://user:password@/myapp?host=/cloudsql/project:region:instance"
automatic_scaling:
min_instances: 1
max_instances: 10
target_cpu_utilization: 0.6
handlers:
- url: /.*
script: auto
secure: always
# cloudbuild.yaml for Google Cloud Build
steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'gcr.io/$PROJECT_ID/myapp', '.']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'gcr.io/$PROJECT_ID/myapp']
- name: 'gcr.io/cloud-builders/gcloud'
args: ['run', 'deploy', 'myapp', '--image', 'gcr.io/$PROJECT_ID/myapp', '--platform', 'managed', '--region', 'us-central1']# azure-pipelines.yml
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
buildConfiguration: 'Release'
stages:
- stage: Build
displayName: Build stage
jobs:
- job: Build
displayName: Build
steps:
- task: NodeTool@0
inputs:
versionSpec: '18.x'
displayName: 'Install Node.js'
- script: |
npm install
npm run build
displayName: 'npm install and build'
- task: Docker@2
displayName: 'Build and push image'
inputs:
command: buildAndPush
repository: myapp
dockerfile: '$(Build.SourcesDirectory)/Dockerfile'
containerRegistry: 'myRegistry'
tags: |
$(Build.BuildId)
latest
- stage: Deploy
displayName: Deploy stage
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: Deploy
displayName: Deploy
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: AzureWebAppContainer@1
displayName: 'Azure Web App on Container Deploy'
inputs:
azureSubscription: 'mySubscription'
appName: 'myapp'
containers: 'myRegistry.azurecr.io/myapp:$(Build.BuildId)'# Complete multi-cloud deployment setup
# terraform/main.tf
provider "aws" {
region = var.aws_region
}
provider "google" {
project = var.gcp_project
region = var.gcp_region
}
# AWS ECS Cluster
resource "aws_ecs_cluster" "main" {
name = "myapp-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
}
# Google Cloud Run Service
resource "google_cloud_run_service" "main" {
name = "myapp"
location = var.gcp_region
template {
spec {
containers {
image = "gcr.io/${var.gcp_project} / myapp: latest"
ports {
container_port = 3000
}
env {
name = "NODE_ENV"
value = "production"
}
}
}
}
}
# Azure Container Instance
resource "azurerm_container_group" "main" {
name = "myapp"
location = var.azure_location
resource_group_name = var.azure_resource_group
os_type = "Linux"
container {
name = "myapp"
image = "myapp.azurecr.io/myapp:latest"
cpu = "0.5"
memory = "1.5"
ports {
port = 3000
protocol = "TCP"
}
environment_variables = {
NODE_ENV = "production"
}
}
}
# variables.tf
variable "aws_region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "gcp_project" {
description = "GCP project ID"
type = string
}
variable "gcp_region" {
description = "GCP region"
type = string
default = "us-central1"
}
variable "azure_location" {
description = "Azure location"
type = string
default = "East US"
}
variable "azure_resource_group" {
description = "Azure resource group"
type = string
} Test your understanding of this topic:
Implement comprehensive monitoring, logging, and alerting for production Node.js applications.
Content by: Bansi Patel
Node.js Developer
Set up monitoring to track application performance, errors, and user behavior in production environments.
// prometheus-client setup
const prometheus = require('prom-client');
// Create a Registry to register the metrics
const register = new prometheus.Registry();
// Add a default label which is added to all metrics
register.setDefaultLabels({
app: 'myapp'
});
// Enable the collection of default metrics
prometheus.collectDefaultMetrics({ register });
// Custom metrics
const httpRequestDuration = new prometheus.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10]
});
const httpRequestTotal = new prometheus.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
});
const activeConnections = new prometheus.Gauge({
name: 'active_connections',
help: 'Number of active connections'
});
// Register custom metrics
register.registerMetric(httpRequestDuration);
register.registerMetric(httpRequestTotal);
register.registerMetric(activeConnections);
// Express middleware for metrics
const metricsMiddleware = (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
const route = req.route ? req.route.path : req.path;
httpRequestDuration
.labels(req.method, route, res.statusCode)
.observe(duration);
httpRequestTotal
.labels(req.method, route, res.statusCode)
.inc();
});
next();
};
// Metrics endpoint
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});
module.exports = { register, metricsMiddleware }; // winston logger configuration
const winston = require('winston');
const { combine, timestamp, errors, json, printf, colorize } = winston.format;
// Custom format
const logFormat = printf(({ level, message, timestamp, stack, ...meta }) => {
return JSON.stringify({
timestamp,
level,
message,
stack,
...meta
});
});
// Create logger
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: combine(
timestamp(),
errors({ stack: true }),
json()
),
defaultMeta: { service: 'myapp' },
transports: [
// Write all logs with level 'error' and below to 'error.log'
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5
}),
// Write all logs to 'combined.log'
new winston.transports.File({
filename: 'logs/combined.log',
maxsize: 5242880, // 5MB
maxFiles: 5
})
]
});
// If we're not in production, log to the console
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: combine(
colorize(),
timestamp(),
logFormat
)
}));
}
// Express middleware for request logging
const requestLogger = (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info('HTTP Request', {
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration: `${duration}ms`,
userAgent: req.get('User-Agent'),
ip: req.ip
});
});
next();
};
module.exports = { logger, requestLogger };// Sentry integration
const Sentry = require('@sentry/node');
const { nodeProfilingIntegration } = require('@sentry/profiling-node');
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
integrations: [
nodeProfilingIntegration(),
],
tracesSampleRate: 1.0,
profilesSampleRate: 1.0,
});
// Express error handler
const errorHandler = (err, req, res, next) => {
// Log error
logger.error('Unhandled error', {
error: err.message,
stack: err.stack,
url: req.url,
method: req.method,
userAgent: req.get('User-Agent'),
ip: req.ip
});
// Send to Sentry
Sentry.captureException(err);
// Send response
res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'production'
? 'Something went wrong'
: err.message
});
};
// Health check endpoint
app.get('/health', (req, res) => {
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
memory: process.memoryUsage(),
version: process.env.npm_package_version
};
res.json(health);
});
module.exports = { errorHandler };// Alerting system
const nodemailer = require('nodemailer');
const axios = require('axios');
class AlertManager {
constructor() {
this.emailTransporter = nodemailer.createTransporter({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
secure: true,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
});
}
async sendEmailAlert(alert) {
const mailOptions = {
from: process.env.SMTP_FROM,
to: process.env.ALERT_EMAIL,
subject: `[ALERT] ${alert.title}`,
html: `
<h2>${alert.title}</h2>
<p><strong>Severity:</strong> ${alert.severity}</p>
<p><strong>Message:</strong> ${alert.message}</p>
<p><strong>Timestamp:</strong> ${alert.timestamp}</p>
<p><strong>Service:</strong> ${alert.service}</p>
`
};
await this.emailTransporter.sendMail(mailOptions);
}
async sendSlackAlert(alert) {
const webhookUrl = process.env.SLACK_WEBHOOK_URL;
const payload = {
text: `🚨 ${alert.title}`,
attachments: [{
color: alert.severity === 'critical' ? 'danger' : 'warning',
fields: [
{ title: 'Severity', value: alert.severity, short: true },
{ title: 'Service', value: alert.service, short: true },
{ title: 'Message', value: alert.message, short: false },
{ title: 'Timestamp', value: alert.timestamp, short: true }
]
}]
};
await axios.post(webhookUrl, payload);
}
async sendAlert(alert) {
try {
await Promise.all([
this.sendEmailAlert(alert),
this.sendSlackAlert(alert)
]);
} catch (error) {
logger.error('Failed to send alert', { error: error.message });
}
}
}
// Alert conditions
class AlertConditions {
constructor(alertManager) {
this.alertManager = alertManager;
this.thresholds = {
errorRate: 0.05, // 5%
responseTime: 1000, // 1 second
memoryUsage: 0.8, // 80%
cpuUsage: 0.8 // 80%
};
}
checkErrorRate(errorCount, totalRequests) {
const errorRate = errorCount / totalRequests;
if (errorRate > this.thresholds.errorRate) {
this.alertManager.sendAlert({
title: 'High Error Rate',
severity: 'critical',
message: `Error rate is ${(errorRate * 100).toFixed(2)}%, threshold is ${(this.thresholds.errorRate * 100)}%`,
service: 'myapp',
timestamp: new Date().toISOString()
});
}
}
checkMemoryUsage() {
const memUsage = process.memoryUsage();
const memUsagePercent = memUsage.heapUsed / memUsage.heapTotal;
if (memUsagePercent > this.thresholds.memoryUsage) {
this.alertManager.sendAlert({
title: 'High Memory Usage',
severity: 'warning',
message: `Memory usage is ${(memUsagePercent * 100).toFixed(2)}%, threshold is ${(this.thresholds.memoryUsage * 100)}%`,
service: 'myapp',
timestamp: new Date().toISOString()
});
}
}
}
module.exports = { AlertManager, AlertConditions };// Complete monitoring and observability system
const express = require('express');
const prometheus = require('prom-client');
const winston = require('winston');
const Sentry = require('@sentry/node');
// Initialize Sentry
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0
});
// Prometheus metrics
const register = new prometheus.Registry();
register.setDefaultLabels({ app: 'myapp' });
prometheus.collectDefaultMetrics({ register });
const httpRequestDuration = new prometheus.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10]
});
const httpRequestTotal = new prometheus.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
});
const activeConnections = new prometheus.Gauge({
name: 'active_connections',
help: 'Number of active connections'
});
register.registerMetric(httpRequestDuration);
register.registerMetric(httpRequestTotal);
register.registerMetric(activeConnections);
// Winston logger
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'myapp' },
transports: [
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' })
]
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console());
}
// Express app
const app = express();
// Middleware
app.use(Sentry.requestHandler());
app.use(express.json());
// Metrics middleware
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
const route = req.route ? req.route.path : req.path;
httpRequestDuration
.labels(req.method, route, res.statusCode)
.observe(duration);
httpRequestTotal
.labels(req.method, route, res.statusCode)
.inc();
});
next();
});
// Request logging middleware
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info('HTTP Request', {
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration: `${duration}ms`,
userAgent: req.get('User-Agent'),
ip: req.ip
});
});
next();
});
// Routes
app.get('/health', (req, res) => {
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
memory: process.memoryUsage(),
version: process.env.npm_package_version
};
res.json(health);
});
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});
app.get('/api/test', (req, res) => {
res.json({ message: 'Test endpoint working' });
});
// Error handling
app.use(Sentry.errorHandler());
app.use((err, req, res, next) => {
logger.error('Unhandled error', {
error: err.message,
stack: err.stack,
url: req.url,
method: req.method
});
res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'production'
? 'Something went wrong'
: err.message
});
});
// Graceful shutdown
process.on('SIGTERM', () => {
logger.info('SIGTERM received, shutting down gracefully');
process.exit(0);
});
process.on('SIGINT', () => {
logger.info('SIGINT received, shutting down gracefully');
process.exit(0);
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
logger.info(`Server running on port ${PORT}`);
});
module.exports = app;Test your understanding of this topic:
Continue your learning journey and master the next set of concepts.
Continue to Module 12