Asia Pacific

Top 10 Express.js Tips for Asia

Master Express.js development with these essential tips and best practices used by top developers in Asia.

1

Structure Your Application with Router Modules

Organize your Express application by splitting routes into separate modules. This improves maintainability and makes your codebase easier to navigate as it grows.

// routes/users.js
const router = require('express').Router();

router.get('/', getUsers);
router.post('/', createUser);
router.get('/:id', getUserById);

module.exports = router;

// app.js
app.use('/api/users', require('./routes/users'));
2

Implement Middleware for Cross-Cutting Concerns

Use middleware to handle authentication, logging, error handling, and request validation. This keeps your route handlers clean and focused on business logic.

const authenticate = async (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  try {
    req.user = await verifyToken(token);
    next();
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

app.use('/api/protected', authenticate);
3

Use Async/Await with Error Handling Wrapper

Create a wrapper function to handle async errors automatically. This eliminates repetitive try-catch blocks and ensures all errors are properly caught.

const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

// Usage
router.get('/users', asyncHandler(async (req, res) => {
  const users = await User.find();
  res.json(users);
}));

// Error handler middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Something went wrong!' });
});
4

Validate Request Input with Express-Validator

Always validate incoming data to prevent security vulnerabilities and ensure data integrity. Express-validator provides a clean, declarative API for validation.

const { body, validationResult } = require('express-validator');

const validateUser = [
  body('email').isEmail().normalizeEmail(),
  body('password').isLength({ min: 8 }),
  body('name').trim().notEmpty(),
];

router.post('/users', validateUser, (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  // Create user...
});
5

Implement Proper Security Middleware

Use Helmet for security headers, implement CORS properly, and rate limit your endpoints. These are essential for production-ready applications.

const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');

app.use(helmet());

app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(','),
  credentials: true
}));

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100
});
app.use('/api/', limiter);
6

Use Environment-Based Configuration

Load configuration from environment variables and create environment-specific settings. Never hardcode secrets or environment-specific values.

// config/index.js
require('dotenv').config();

module.exports = {
  port: process.env.PORT || 3000,
  db: {
    url: process.env.DATABASE_URL,
    options: {
      maxPoolSize: parseInt(process.env.DB_POOL_SIZE) || 10
    }
  },
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN || '1d'
  }
};
7

Implement Logging with Request Correlation

Use structured logging with request IDs to trace requests across your system. This is invaluable for debugging production issues.

const { v4: uuid } = require('uuid');
const pino = require('pino-http');

app.use((req, res, next) => {
  req.id = req.headers['x-request-id'] || uuid();
  res.setHeader('x-request-id', req.id);
  next();
});

app.use(pino({
  genReqId: (req) => req.id,
  serializers: {
    req: (req) => ({
      id: req.id,
      method: req.method,
      url: req.url
    })
  }
}));
8

Handle Graceful Shutdown

Implement graceful shutdown to properly close database connections and finish processing requests before the server stops.

const server = app.listen(port);

const shutdown = async () => {
  console.log('Shutting down gracefully...');

  server.close(async () => {
    await mongoose.connection.close();
    await redis.quit();
    console.log('Server closed');
    process.exit(0);
  });

  setTimeout(() => {
    console.error('Forced shutdown');
    process.exit(1);
  }, 10000);
};

process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
9

Implement Response Compression

Use compression middleware to reduce response sizes. This significantly improves performance, especially for JSON-heavy APIs.

const compression = require('compression');

app.use(compression({
  filter: (req, res) => {
    if (req.headers['x-no-compression']) {
      return false;
    }
    return compression.filter(req, res);
  },
  threshold: 1024 // Only compress if > 1KB
}));
10

Use TypeScript for Better Developer Experience

TypeScript provides type safety, better IDE support, and catches errors at compile time. It's especially valuable for larger Express applications.

import { Request, Response, NextFunction } from 'express';

interface User {
  id: string;
  email: string;
  name: string;
}

interface AuthRequest extends Request {
  user?: User;
}

const getUserProfile = async (
  req: AuthRequest,
  res: Response,
  next: NextFunction
) => {
  const user = req.user;
  if (!user) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  res.json({ profile: user });
};

Need Expert Express.js Developers?

Slashdev.io provides top Express.js developers for your projects in Asia and worldwide.

Hire Express.js Developers