Top 5 Ways to Secure Your Express.js APIs


APIs are the backbone of modern web applications, and Express.js, being one of the most popular Node.js frameworks, makes building APIs simple and efficient. But simplicity doesn’t mean we can skip security. Every unsecured API endpoint is a potential vulnerability that attackers could exploit.

In this blog, I’ll share the top 5 ways to secure your Express.js APIs with actionable examples.

1. Enforce HTTPS and Secure Connections Using HTTPS ensures that data exchanged between the client and server is encrypted, preventing attackers from intercepting sensitive information.

Here’s how to enforce HTTPS in an Express.js app:

const express = require('express');
const app = express();

app.use((req, res, next) => {
  if (req.headers['x-forwarded-proto'] !== 'https') {
    return res.redirect(`https://${req.headers.host}${req.url}`);
  }
  next();
});

app.get('/', (req, res) => {
  res.send('Hello, secure world!');
});

app.listen(3000, () => console.log('Server running on port 3000'));

Pro Tip: Use tools like Let's Encrypt for free SSL certificates.

2. Use Helmet to Secure HTTP Headers HTTP headers are often overlooked, but they can play a significant role in securing APIs. Helmet is a middleware that adds several security headers to your Express app.

const helmet = require('helmet');
const express = require('express');
const app = express();

app.use(helmet());

app.get('/', (req, res) => {
  res.send('Your API is now safer with Helmet!');
});

app.listen(3000, () => console.log('Server is protected by Helmet'));

Helmet protects against:

  • Clickjacking (via X-Frame-Options)

  • XSS attacks (via Content-Security-Policy) And much more!

3. Protect Against Rate-Based Attacks Prevent DDoS and brute-force attacks by limiting the number of requests a client can make. Use express-rate-limit to achieve this:

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

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests
  message: 'Too many requests, please try again later.'
});

app.use(limiter);

app.get('/', (req, res) => {
  res.send('Rate limiting is active!');
});

app.listen(3000, () => console.log('Server running with rate limiting'));

4. Authenticate and Authorize Users Authentication verifies a user’s identity, while authorization ensures they can only access allowed resources. Use JWT (JSON Web Tokens) for stateless authentication.

Example: Implementing JWT Authentication

const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();

const secret = 'your_jwt_secret';

// Login route to generate token
app.post('/login', (req, res) => {
  const user = { id: 1, username: 'john.doe' }; // Replace with actual user validation
  const token = jwt.sign(user, secret, { expiresIn: '1h' });
  res.json({ token });
});

// Middleware to verify token
const authenticateToken = (req, res, next) => {
  const token = req.header('Authorization')?.replace('Bearer ', '');
  if (!token) return res.status(401).send('Access Denied');

  try {
    const verified = jwt.verify(token, secret);
    req.user = verified;
    next();
  } catch (err) {
    res.status(400).send('Invalid Token');
  }
};

// Protected route
app.get('/protected', authenticateToken, (req, res) => {
  res.send('Welcome to the protected route!');
});

app.listen(3000, () => console.log('Server with JWT authentication running'));

5. Validate and Sanitize User Input SQL Injection and Cross-Site Scripting (XSS) attacks often target APIs with poorly validated input. Always validate and sanitize incoming data using libraries like Joi or express-validator.

Example: Input Validation with express-validator

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

app.use(express.json());

app.post(
  '/register',
  [
    body('email').isEmail().withMessage('Invalid email address'),
    body('password').isLength({ min: 8 }).withMessage('Password must be at least 8 characters long')
  ],
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    res.send('Registration successful!');
  }
);

app.listen(3000, () => console.log('Input validation server running'));

If you found this blog helpful, ❤️ give it a like and follow for more JavaScript tips and best practices!