Building Scalable Node.js Applications

March 20, 2024
12 min read
Backend

Introduction

Node.js has become a popular choice for building scalable server-side applications. However, building production-ready applications requires careful planning and best practices.

Architecture Patterns

MVC Pattern

// Model
class User {
  constructor(id, name, email) {
    this.id = id;
    this.name = name;
    this.email = email;
  }
}

// Controller
class UserController {
  getUser(req, res) {
    // Business logic
  }
}

// Routes
app.get('/users/:id', userController.getUser);

Microservices Architecture

When your application grows, consider splitting into microservices:

┌─────────────┐      ┌──────────────┐      ┌───────────────┐
│ API Gateway │─────▶│ User Service │      │ Product Service
└─────────────┘      └──────────────┘      └───────────────┘
                            │
                            ▼
                      ┌──────────────┐
                      │   Database   │
                      └──────────────┘

Performance Optimization

Caching Strategy

const redis = require('redis');
const client = redis.createClient();

async function getCachedUser(userId) {
  const cached = await client.get(`user:${userId}`);
  
  if (cached) {
    return JSON.parse(cached);
  }
  
  const user = await fetchUserFromDB(userId);
  await client.setex(`user:${userId}`, 3600, JSON.stringify(user));
  return user;
}

Connection Pooling

const pool = mysql.createPool({
  connectionLimit: 10,
  host: 'localhost',
  user: 'user',
  password: 'password',
  database: 'mydb'
});

Error Handling

Centralized Error Handler

app.use((err, req, res, next) => {
  console.error(err.stack);
  
  const status = err.status || 500;
  const message = err.message || 'Internal Server Error';
  
  res.status(status).json({
    status,
    message,
    error: process.env.NODE_ENV === 'development' ? err : {}
  });
});

Monitoring and Logging

Winston Logger

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

logger.info('Server started');

Database Optimization

Query Optimization

  1. Use indexes on frequently queried columns
  2. Avoid N+1 queries with proper joins
  3. Implement query caching

Pagination

async function getPaginatedUsers(page = 1, limit = 20) {
  const offset = (page - 1) * limit;
  
  return await User.findAndCountAll({
    limit,
    offset,
    order: [['createdAt', 'DESC']]
  });
}

Security Best Practices

  • Use environment variables for secrets
  • Implement rate limiting
  • Validate and sanitize all inputs
  • Use HTTPS in production
  • Implement proper authentication

Deployment

PM2 Process Manager

pm2 start app.js -i max
pm2 start app.js --watch
pm2 logs

Conclusion

Building scalable Node.js applications requires attention to architecture, performance, and security. Follow these patterns and best practices to create robust production applications.

#nodejs#backend#architecture#scalability

Author

Kirtan kalathiya

Full-stack developer passionate about creating exceptional web experiences. Sharing knowledge about web development, design, and technology.

💬 Discussion

Share your thoughts and insights about this article. Have questions? Let's discuss in the comments.