8 Topics
Chapter•40 Minutes

API Security and Performance Optimization

Secure the REST API using security headers, rate limiting, input controls, password protection, MongoDB indexes, and efficient database queries.

Understand Common REST API Security Risks

A public REST API can be exposed to several security threats.

Common risks include:

  • Broken authentication.
  • Broken object-level authorization.
  • Broken function-level authorization.
  • Injection attacks.
  • Cross-site scripting in consuming applications.
  • Excessive request rates.
  • Sensitive data exposure.
  • Mass assignment vulnerabilities.
  • Unrestricted resource consumption.

Security should be implemented at multiple layers rather than relying on one middleware package.

Our application will combine authentication, authorization, validation, request limits, secure response handling, and database access restrictions.

Key Takeaways
  • API security requires multiple layers of protection.

  • Authentication and authorization failures are common API risks.

  • Never trust arbitrary client input.

Configure Helmet Security Headers

Helmet is an Express middleware package that sets HTTP headers to help protect applications against common web vulnerabilities.

It configures headers such as:

  • Content-Security-Policy.
  • X-Content-Type-Options.
  • Referrer-Policy.
  • Strict-Transport-Security under appropriate conditions.

Helmet is already installed in our project.

Register it near the beginning of Express application initialization.

Review the default configuration and customize it according to your deployment requirements.

Configure Helmet
import helmet from 'helmet';

app.use(helmet());

Registers Helmet's default security header middleware.

Key Takeaways
  • Helmet sets security-related HTTP headers.

  • Register security middleware early.

  • Review security headers according to application requirements.

Implement API Rate Limiting

Rate limiting restricts the number of requests a client can make during a specified time window.

For example, a login endpoint might allow only a limited number of attempts from a particular IP address within a time window.

We will use express-rate-limit.

Rate limiting helps reduce brute-force attempts and excessive resource consumption.

For distributed deployments, configure a shared rate-limit store rather than relying on independent in-memory counters.

Distributed Applications

The default in-memory rate-limit store is process-specific. Use a shared store such as Redis when requests are distributed across multiple application instances.

Configure API Rate Limiting
import rateLimit from 'express-rate-limit';

export const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 100,
  standardHeaders: 'draft-8',
  legacyHeaders: false,
  message: {
    success: false,
    message: 'Too many requests. Please try again later.'
  }
});

app.use('/api', apiLimiter);

Limits clients to 100 requests within a 15-minute window using the default in-memory store.

Key Takeaways
  • Rate limiting controls excessive requests.

  • Login endpoints may require stricter limits.

  • Use a shared store for distributed applications.

Prevent Mass Assignment Vulnerabilities

Mass assignment occurs when an API blindly accepts client-provided properties and applies them directly to a database document.

Imagine a user update endpoint that accepts:

{
  "name": "John",
  "role": "admin",
  "isActive": false
}

If the backend directly passes this object into a database update operation, an ordinary user may be able to modify privileged properties.

Prevent this by explicitly selecting the properties that clients are allowed to update.

For profile updates, allow only fields such as name and age.

Role changes should be implemented through a separate administrator-authorized operation.

Never Trust req.body

Do not spread arbitrary request bodies into Mongoose updates. Restrict writable properties using validation schemas and explicit allowlists.

Allowlist Update Properties
export function pickUserUpdateFields(body) {
  const allowedFields = ['name', 'age'];

  return Object.fromEntries(
    Object.entries(body).filter(([key]) =>
      allowedFields.includes(key)
    )
  );
}

Creates an update object containing only explicitly permitted properties.

Key Takeaways
  • Mass assignment can expose privileged fields.

  • Use explicit property allowlists.

  • Protect role and account-status modifications.

Understand MongoDB Indexes

Indexes improve the efficiency of database queries by allowing MongoDB to locate matching documents without scanning the entire collection.

Without an appropriate index, a query may perform a collection scan.

For example, our application frequently searches users by email.

An index on email can improve this lookup.

MongoDB supports several index types:

  • Single-field indexes.
  • Compound indexes.
  • Multikey indexes.
  • Text indexes.
  • Geospatial indexes.
  • TTL indexes.

The correct index depends on the query patterns used by the application.

Common Index Types

Single-field

Indexes one field

Compound

Indexes multiple fields

Multikey

Indexes array values

Text

Supports text search

TTL

Automatically expires documents

Key Takeaways
  • Indexes improve query efficiency.

  • Choose indexes based on actual query patterns.

  • Indexes consume storage and add write overhead.

Create a Unique Email Index

Our user model declares email as unique.

For clarity, you can explicitly declare the index on the schema.

A unique index ensures that MongoDB does not allow multiple documents with the same indexed email value.

Existing duplicate records must be resolved before MongoDB can successfully create the index.

Index Deployment

In production, manage index creation through controlled database migrations or deployment procedures. Do not rely on automatic index creation for every application startup.

Define Email Index
userSchema.index(
  { email: 1 },
  { unique: true }
);

Creates a unique ascending index on the email field.

Key Takeaways
  • Unique indexes enforce uniqueness at the database level.

  • Resolve duplicate records before index creation.

  • Manage production indexes deliberately.

Analyze MongoDB Query Performance

MongoDB provides explain() to inspect how a query executes.

It can reveal:

  • Whether MongoDB uses an index.
  • Whether the query performs a collection scan.
  • How many documents were examined.
  • How many documents were returned.
  • Query execution statistics.

For example, a query filtering users by email can be inspected using execution statistics.

Analyze a MongoDB Query
const result = await User.find({
  email: 'john@example.com'
}).explain('executionStats');

console.log(result.executionStats);

Returns execution statistics for the email lookup query.

Key Takeaways
  • explain() helps diagnose query execution.

  • Inspect documents examined and returned.

  • Use query plans to identify missing indexes and inefficient queries.

Prevent MongoDB Injection

MongoDB injection occurs when untrusted input influences database query operators or query structure.

For example, an application should never accept arbitrary MongoDB filters directly from the client.

Avoid patterns such as:

User.find(req.query);

A malicious client might provide unexpected operator structures.

Instead:

  • Validate query parameters.
  • Allowlist supported filter fields.
  • Convert values to expected data types.
  • Avoid blindly spreading request objects into database operations.
  • Use controlled query builders.
Safe Query Construction
const filter = {};

if (typeof req.query.email === 'string') {
  filter.email = req.query.email.toLowerCase().trim();
}

if (typeof req.query.isActive === 'string') {
  if (req.query.isActive === 'true') {
    filter.isActive = true;
  } else if (req.query.isActive === 'false') {
    filter.isActive = false;
  }
}

const users = await User.find(filter).select('-password');

Constructs a query from explicitly supported scalar parameters instead of passing arbitrary client data to MongoDB.

Key Takeaways
  • Never accept arbitrary MongoDB query objects from clients.

  • Allowlist query fields and operators.

  • Validate types before database operations.