Skip to content
Back to blog
  • Engineering

Securing Node.js APIs Against Common Injection Attacks

Learn practical input sanitization and query modeling patterns to defend Express and NestJS endpoints against injection vulnerabilities.

MMuhammad Muneeb3 min read

The Real Risk of Injection in JavaScript Backends

Injection vulnerabilities remain one of the most critical threats to web applications. Whether you are building an API with Express or NestJS, accepting raw input from clients without proper validation and query structuring is a direct path to data breaches. In JavaScript environments, dynamic typing and flexible object structures often create a false sense of security. Developers assume that because JSON payloads look structured, they are safe to pass directly into database drivers or ORMs.

The reality is that attackers can manipulate request bodies, query parameters, and headers to alter how your backend queries databases or executes system commands. Protecting your application requires moving beyond simple type checks and implementing rigorous query modeling and sanitization patterns at the controller and service boundaries.

Defending Against NoSQL Injection in MongoDB

When using document databases like MongoDB with Mongoose, native JavaScript object structures can be weaponized. If an API endpoint accepts a password reset token or a login query directly from req.body without validation, an attacker can pass an operator object instead of a string, bypassing authentication entirely.

auth.service.ts
// VULNERABLE CODE
async function findUserVulnerable(userInput: any) {
  // If userInput.email is an object like { $gt: "" }, this returns the first user in the collection
  return await UserModel.findOne({ email: userInput.email });
}

// SECURE CODE
import validator from 'validator';

async function findUserSecure(emailInput: unknown) {
  if (typeof emailInput !== 'string' || !validator.isEmail(emailInput)) {
    throw new Error('Invalid email format');
  }

  // Explicitly casting and validating ensures only primitive strings reach the query
  return await UserModel.findOne({ email: emailInput });
}

Always ensure that user-supplied values destined for database queries are primitive types. Libraries like class-validator in NestJS or Joi and Zod in Express provide robust schema validation out of the box, stripping out unexpected nested objects or operators before they ever touch your database layer.

Preventing SQL Injection in PostgreSQL and MySQL

Relational databases managed through tools like TypeORM, Sequelize, or Knex are generally safe from classical SQL injection as long as you rely on parameterized queries. However, vulnerabilities frequently appear when developers resort to raw SQL queries or dynamic table and column name interpolation.

Parameterized queries ensure that the database driver treats user input strictly as data, never as executable code. If you must allow sorting or dynamic column selection, never concatenate strings directly into the query string.

user.repository.ts
import { DataSource } from 'typeorm';

// SECURE: Using parameterized queries
async function findUsersByRole(dataSource: DataSource, role: string) {
  const query = 'SELECT id, email FROM users WHERE role = $1';
  return await dataSource.query(query, [role]);
}

// UNSAFE: String interpolation allows SQL injection
async function findUsersUnsafe(dataSource: DataSource, role: string) {
  const query = `SELECT id, email FROM users WHERE role = '${role}'`;
  return await dataSource.query(query);
}

Practical Takeaways for API Security

  • Validate all incoming request data against strict schemas using Zod, Joi, or class-validator before processing.
  • Never pass raw request bodies directly into database find or update methods.
  • Use query builders and ORMs correctly, and avoid raw string concatenation for SQL or NoSQL queries.
  • Implement strict type checking to ensure input parameters are primitives rather than unexpected objects or arrays.

Security is not a feature you add at the end of a project. By establishing strict validation boundaries and treating all client input as hostile by default, you eliminate entire classes of injection vulnerabilities from your Node.js codebases.

Related posts

DevOps1 min read

Self-Hosting Next.js on a VPS with PM2 and Nginx

You don’t need a platform to run Next.js well. Here’s how I deploy it on a plain VPS — PM2 for the process, Nginx out front, and a one-command deploy.

Read article
Engineering2 min read

Background Jobs in Node.js with BullMQ and Redis

Slow work doesn’t belong in a request. Offload it to a queue. Here’s how I structure reliable background jobs in Node.js with BullMQ and Redis.

Read article