How to sanitize inputs in Node.js

Input sanitization removes or encodes potentially dangerous characters from user input to prevent security vulnerabilities like XSS, SQL injection, and command injection. As the creator of CoreUI with 12 years of Node.js development experience, I’ve secured applications serving millions of users by implementing proper input sanitization strategies that block malicious payloads while preserving legitimate user data.

The most reliable approach combines validation libraries with context-specific sanitization.

Sanitize HTML with DOMPurify

npm install isomorphic-dompurify
const createDOMPurify = require('isomorphic-dompurify')
const DOMPurify = createDOMPurify()

function sanitizeHTML(dirty) {
  return DOMPurify.sanitize(dirty)
}

// User input
const userInput = '<script>alert("XSS")</script><p>Hello</p>'

// Sanitized output
const clean = sanitizeHTML(userInput)
console.log(clean) // <p>Hello</p>

Escape Special Characters

function escapeHtml(text) {
  const map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  }
  return text.replace(/[&<>"']/g, char => map[char])
}

const userInput = '<script>alert("XSS")</script>'
const escaped = escapeHtml(userInput)
console.log(escaped)
// &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

Sanitize with validator.js

npm install validator
const validator = require('validator')

// Email sanitization
const email = '  [email protected]  '
const sanitizedEmail = validator.normalizeEmail(email)
console.log(sanitizedEmail) // [email protected]

// Trim and escape
const username = '  <script>admin</script>  '
const sanitizedUsername = validator.trim(validator.escape(username))
console.log(sanitizedUsername) // &lt;script&gt;admin&lt;/script&gt;

// Remove non-alphanumeric
const slug = 'Hello World! @#$'
const sanitizedSlug = slug.toLowerCase()
  .replace(/[^a-z0-9]+/g, '-')
  .replace(/^-|-$/g, '')
console.log(sanitizedSlug) // hello-world

Prevent SQL Injection with Parameterized Queries

const mysql = require('mysql2/promise')

// BAD - Vulnerable to SQL injection
async function getUserBad(userId) {
  const query = `SELECT * FROM users WHERE id = ${userId}`
  const [rows] = await pool.query(query)
  return rows[0]
}

// GOOD - Parameterized query
async function getUserGood(userId) {
  const query = 'SELECT * FROM users WHERE id = ?'
  const [rows] = await pool.query(query, [userId])
  return rows[0]
}

// Named parameters with object
async function findUser(filters) {
  const query = 'SELECT * FROM users WHERE email = :email AND active = :active'
  const [rows] = await pool.query(query, {
    email: filters.email,
    active: filters.active
  })
  return rows[0]
}

Sanitize File Paths

const path = require('path')

function sanitizeFilePath(userPath) {
  // Remove directory traversal attempts
  const normalized = path.normalize(userPath).replace(/^(\.\.[\/\\])+/, '')

  // Remove absolute paths
  const relative = normalized.replace(/^[\/\\]+/, '')

  // Whitelist allowed characters
  const safe = relative.replace(/[^a-zA-Z0-9_\-\.\/]/g, '')

  return safe
}

// Test cases
console.log(sanitizeFilePath('../../../etc/passwd')) // etcpasswd
console.log(sanitizeFilePath('/etc/passwd')) // etcpasswd
console.log(sanitizeFilePath('user/documents/file.txt')) // user/documents/file.txt
console.log(sanitizeFilePath('file<script>.txt')) // filescript.txt

Sanitize MongoDB Queries

const mongoose = require('mongoose')

// BAD - Vulnerable to NoSQL injection
async function findUserBad(req) {
  const user = await User.findOne({ username: req.body.username })
  return user
}

// GOOD - Validate and sanitize
function sanitizeMongoString(value) {
  if (typeof value !== 'string') {
    throw new Error('Expected string')
  }

  // Remove MongoDB operators
  return value.replace(/^\$/, '')
}

async function findUserGood(req) {
  const username = sanitizeMongoString(req.body.username)
  const user = await User.findOne({ username })
  return user
}

// Better - Use schema validation
const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    match: /^[a-zA-Z0-9_]+$/
  }
})

Express Middleware for Input Sanitization

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

app.use(express.json())

// Sanitize middleware
function sanitizeInputs(req, res, next) {
  if (req.body) {
    Object.keys(req.body).forEach(key => {
      if (typeof req.body[key] === 'string') {
        // Trim whitespace
        req.body[key] = validator.trim(req.body[key])

        // Escape HTML
        req.body[key] = validator.escape(req.body[key])
      }
    })
  }

  next()
}

app.use(sanitizeInputs)

app.post('/api/users', (req, res) => {
  // req.body is already sanitized
  const { name, email } = req.body

  res.json({ name, email })
})

Comprehensive Sanitization Function

const validator = require('validator')

function sanitizeInput(input, options = {}) {
  const {
    type = 'text',
    allowHTML = false,
    maxLength = 10000
  } = options

  if (typeof input !== 'string') {
    return ''
  }

  // Trim whitespace
  let sanitized = validator.trim(input)

  // Enforce max length
  sanitized = sanitized.slice(0, maxLength)

  // Type-specific sanitization
  switch (type) {
    case 'email':
      sanitized = validator.normalizeEmail(sanitized) || ''
      if (!validator.isEmail(sanitized)) {
        return ''
      }
      break

    case 'url':
      if (!validator.isURL(sanitized)) {
        return ''
      }
      break

    case 'number':
      if (!validator.isNumeric(sanitized)) {
        return ''
      }
      break

    case 'alphanumeric':
      sanitized = sanitized.replace(/[^a-zA-Z0-9]/g, '')
      break

    case 'html':
      if (allowHTML) {
        const DOMPurify = require('isomorphic-dompurify')()
        sanitized = DOMPurify.sanitize(sanitized)
      } else {
        sanitized = validator.escape(sanitized)
      }
      break

    case 'text':
    default:
      sanitized = validator.escape(sanitized)
      break
  }

  return sanitized
}

// Usage
console.log(sanitizeInput('  [email protected]  ', { type: 'email' }))
// [email protected]

console.log(sanitizeInput('<script>alert("xss")</script>', { type: 'text' }))
// &lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;

console.log(sanitizeInput('<b>Bold</b> text', { type: 'html', allowHTML: true }))
// <b>Bold</b> text

Best Practice Note

This is the input sanitization strategy we use across all CoreUI Node.js applications to prevent security vulnerabilities. Always sanitize user input at the point of entry, use parameterized queries for database operations, validate data types before sanitization, and apply context-specific sanitization (HTML escaping for display, SQL parameters for queries, path sanitization for file operations). Remember that sanitization is not a replacement for proper validation - always validate first, then sanitize.

For production applications, consider using CoreUI’s Node.js Admin Template which includes pre-configured security middleware.

For complete security implementation, check out how to validate data in Node.js and how to prevent XSS attacks in Node.js.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
What is JavaScript Array.pop() Method?
What is JavaScript Array.pop() Method?

How to remove a property from an object in Javascript
How to remove a property from an object in Javascript

How to show or hide elements in React? A Step-by-Step Guide.
How to show or hide elements in React? A Step-by-Step Guide.

How to Center a Button in CSS
How to Center a Button in CSS

Answers by CoreUI Core Team