Deployment and Production Readiness
Deploying applications to production requires careful planning and execution. This article covers deployment best practices.
Introduction
Production readiness provides:
- Reliable deployments
- Scalability
- Performance optimization
- Security hardening
- Monitoring and alerting
Understanding production readiness helps you:
- Deploy applications safely
- Scale applications
- Monitor performance
- Handle failures
- Maintain uptime
Environment Configuration
Environment Variables
// โ
Good: Install dotenv
// npm install dotenv
require('dotenv').config();
// .env
NODE_ENV=production
PORT=3000
DATABASE_URL=mongodb://localhost:27017/myapp
JWT_SECRET=your-secret-key
LOG_LEVEL=info
// app.js
const port = process.env.PORT || 3000;
const nodeEnv = process.env.NODE_ENV || 'development';
const dbUrl = process.env.DATABASE_URL;
console.log(`Environment: ${nodeEnv}`);
console.log(`Port: ${port}`);
// โ
Good: Validate environment variables
const requiredEnvVars = [
'DATABASE_URL',
'JWT_SECRET',
'NODE_ENV'
];
requiredEnvVars.forEach(envVar => {
if (!process.env[envVar]) {
throw new Error(`Missing required environment variable: ${envVar}`);
}
});
// โ
Good: Environment-specific configuration
const config = {
development: {
debug: true,
logLevel: 'debug'
},
production: {
debug: false,
logLevel: 'error'
}
};
const appConfig = config[process.env.NODE_ENV];
Configuration Management
// config/index.js
module.exports = {
app: {
port: process.env.PORT || 3000,
env: process.env.NODE_ENV || 'development'
},
database: {
url: process.env.DATABASE_URL,
pool: {
min: 2,
max: 10
}
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: '1h'
},
logging: {
level: process.env.LOG_LEVEL || 'info',
format: 'json'
},
security: {
corsOrigin: process.env.CORS_ORIGIN || 'http://localhost:3000',
rateLimit: {
windowMs: 15 * 60 * 1000,
max: 100
}
}
};
// app.js
const config = require('./config');
const app = express();
const port = config.app.port;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Security Hardening
Security Middleware
// โ
Good: Install security packages
// npm install helmet express-rate-limit cors
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const cors = require('cors');
const app = express();
// โ
Good: Use Helmet for security headers
app.use(helmet());
// โ
Good: Enable CORS
app.use(cors({
origin: process.env.CORS_ORIGIN,
credentials: true
}));
// โ
Good: Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Too many requests'
});
app.use('/api/', limiter);
// โ
Good: Disable powered by header
app.disable('x-powered-by');
// โ
Good: Validate input
const { body, validationResult } = require('express-validator');
app.post('/api/users', [
body('email').isEmail(),
body('password').isLength({ min: 8 })
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Process request
});
HTTPS and SSL
// โ
Good: Use HTTPS in production
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync(process.env.SSL_KEY_PATH),
cert: fs.readFileSync(process.env.SSL_CERT_PATH)
};
https.createServer(options, app).listen(443, () => {
console.log('HTTPS server running on port 443');
});
// โ
Good: Redirect HTTP to HTTPS
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
res.redirect(`https://${req.header('host')}${req.url}`);
} else {
next();
}
});
Performance Optimization
Caching
// โ
Good: Install caching packages
// npm install redis
const redis = require('redis');
const client = redis.createClient({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT
});
// โ
Good: Cache middleware
const cacheMiddleware = (duration) => {
return (req, res, next) => {
if (req.method !== 'GET') {
return next();
}
const key = `cache:${req.originalUrl}`;
client.get(key, (err, data) => {
if (data) {
res.json(JSON.parse(data));
} else {
res.sendResponse = res.json;
res.json = (body) => {
client.setex(key, duration, JSON.stringify(body));
res.sendResponse(body);
};
next();
}
});
};
};
app.get('/api/users', cacheMiddleware(300), (req, res) => {
res.json([{ id: 1, name: 'John' }]);
});
Compression
// โ
Good: Install compression
// npm install compression
const compression = require('compression');
app.use(compression());
// โ
Good: Custom compression settings
app.use(compression({
level: 6,
threshold: 1024
}));
Monitoring and Logging
Application Monitoring
// โ
Good: Install monitoring packages
// npm install pm2
// ecosystem.config.js
module.exports = {
apps: [{
name: 'app',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production'
},
error_file: './logs/error.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
}]
};
// Start with: pm2 start ecosystem.config.js
// Monitor: pm2 monit
// Logs: pm2 logs
Health Checks
// โ
Good: Health check endpoint
app.get('/health', (req, res) => {
const health = {
status: 'ok',
timestamp: new Date(),
uptime: process.uptime(),
memory: process.memoryUsage()
};
res.json(health);
});
// โ
Good: Readiness check
app.get('/ready', async (req, res) => {
try {
await checkDatabaseConnection();
res.json({ ready: true });
} catch (err) {
res.status(503).json({ ready: false, error: err.message });
}
});
Deployment Strategies
Docker Deployment
# โ
Good: Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "app.js"]
# โ
Good: .dockerignore
node_modules
npm-debug.log
.git
.env
Docker Compose
# โ
Good: docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
NODE_ENV: production
DATABASE_URL: mongodb://db:27017/myapp
depends_on:
- db
restart: unless-stopped
db:
image: mongo:5
volumes:
- mongo-data:/data/db
restart: unless-stopped
volumes:
mongo-data:
Cloud Deployment
# โ
Good: Deploy to Heroku
heroku create my-app
git push heroku main
heroku logs --tail
# โ
Good: Deploy to AWS
aws elasticbeanstalk create-environment \
--application-name my-app \
--environment-name production
# โ
Good: Deploy to Google Cloud
gcloud app deploy
# โ
Good: Deploy to Azure
az webapp up --name my-app
Scaling
Horizontal Scaling
// โ
Good: Cluster mode with PM2
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const numCPUs = os.cpus().length;
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork();
});
} else {
app.listen(3000, () => {
console.log(`Worker ${process.pid} started`);
});
}
Load Balancing
# โ
Good: Nginx configuration
upstream app {
server localhost:3000;
server localhost:3001;
server localhost:3002;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Best Practices
-
Use environment variables:
// โ Good: Environment variables const port = process.env.PORT || 3000; // โ Bad: Hardcoded values const port = 3000; -
Implement health checks:
// โ Good: Health check app.get('/health', (req, res) => { res.json({ status: 'ok' }); }); // โ Bad: No health check -
Use process managers:
# โ Good: PM2 pm2 start app.js # โ Bad: Direct node node app.js
Summary
Production readiness is essential. Key takeaways:
- Configure environment variables
- Harden security
- Optimize performance
- Monitor applications
- Implement health checks
- Use process managers
- Scale horizontally
- Deploy safely
Related Resources
- Node.js Production Best Practices
- PM2 Documentation
- Docker Documentation
- Nginx Documentation
- Heroku Deployment
Next Steps
- Learn about Full-Stack Development
- Explore API Design
- Study Database Design
- Practice deployment
- Deploy production applications
Comments