Skip to main content
Node.js Basics
CHAPTER 28 Beginner

Node.js Best Practices and Security

Updated: May 13, 2026
25 min read

# Node.js Best Practices and Security

Welcome to Chapter 28! We are almost at the finish line. You know how to build a functional backend. But a functional backend is not necessarily a *professional* or *secure* backend.

If you put a raw Express app on the internet, it will be scanned by malicious bots within minutes. They will attempt to overload your server, inject malicious code into your database, and exploit HTTP headers. In this chapter, we will organize our code professionally and lock down our server using standard security packages.

---

1. Introduction

Security in Node.js relies on "Defense in Depth"—adding multiple layers of protection.

  1. 1. Architecture: Organizing code so it is easy to maintain and test.
  1. 2. HTTP Headers: Hiding the fact that you are using Express, and enforcing strict browser rules.
  1. 3. Rate Limiting: Preventing hackers from brute-forcing passwords or crashing your server with massive traffic (DDoS attacks).
  1. 4. Data Sanitization: Preventing NoSQL Injection and Cross-Site Scripting (XSS).

---

2. Learning Objectives

By the end of this chapter, you will be able to:

  • Structure a Node.js project using the MVC (Model-View-Controller) pattern.
  • Install and configure helmet to secure HTTP headers.
  • Install and configure express-rate-limit to prevent spam requests.
  • Install mongo-sanitize to prevent NoSQL injection attacks.
  • Understand the principles of Clean Code in backend development.

---

3. Beginner-Friendly Explanations

The MVC Folder Structure

Writing 500 lines of code in app.js is a nightmare. Professionals split code into three main folders (MVC):
  • Models: Files that define the database structure (e.g., User.js).
  • Views: Files that define the frontend HTML/EJS (e.g., index.ejs).
  • Controllers: Files containing the actual logic. Instead of writing the callback function inside the route, you write it in a Controller, and the route just points to it.

What is Rate Limiting?

If a hacker wants to guess an admin password, they will write a script that sends 10,000 login requests per second. Rate Limiting is a middleware that tracks IP addresses. If one IP sends more than 100 requests in 15 minutes, the middleware automatically blocks them.

---

4. Syntax Explanation

Let's look at how to implement the helmet package. By default, Express sends an HTTP header X-Powered-By: Express. Hackers read this, look up known vulnerabilities for Express, and attack.

bash
1
npm install helmet

```javascript id="ch28-syntax-1" const express = require('express'); const helmet = require('helmet'); const app = express();

// Just by adding this one line at the top of your app, // Helmet automatically adds 14 critical security headers // and hides the X-Powered-By header! app.use(helmet());

app.get('/', (req, res) => res.send("Secure API")); app.listen(3000);

123456789101112131415161718192021
---

## 5. Real-world Examples

**NoSQL Injection:**
Imagine a login route: `User.findOne({ email: req.body.email })`. 
Normally, `req.body.email` is `"john@test.com"`. 
But what if a hacker uses Postman to send an object instead of a string? 
`req.body.email = { "$gt": "" }` (MongoDB syntax for "greater than empty string").
The query becomes: `User.findOne({ email: { "$gt": "" } })`. 
This always evaluates to TRUE, and the database will log the hacker in as the very first user it finds (usually the Admin)! 

We use **Data Sanitization** middleware to strip out illegal characters like `$` from all incoming requests to prevent this.

---

## 6. Multiple Code Examples

### Example 1: Rate Limiting
Prevent brute-force attacks on your API.

bash npm install express-rate-limit

1

javascript id="ch28-code-1" const rateLimit = require('express-rate-limit');

// Define the limit rules const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs message: "Too many requests from this IP, please try again later." });

// Apply globally to all /api routes app.use('/api/', apiLimiter);

123
### Example 2: Data Sanitization
Prevent NoSQL Injection.

bash npm install express-mongo-sanitize

1

javascript id="ch28-code-2" const mongoSanitize = require('express-mongo-sanitize');

app.use(express.json());

// Must be placed AFTER express.json() // This scans req.body, req.query, and req.params and removes any keys containing '$' or '.' app.use(mongoSanitize());

1234
### Example 3: MVC Controller Pattern
Refactoring an app.js file into the Controller pattern.

**`controllers/userController.js`**:

javascript id="ch28-code-3" const User = require('../models/User');

// Export just the logic exports.getAllUsers = async (req, res) => { try { const users = await User.find(); res.json(users); } catch (err) { res.status(500).json({ error: err.message }); } };

1
**`routes/userRoutes.js`**:

javascript id="ch28-code-4" const express = require('express'); const router = express.Router(); const userController = require('../controllers/userController');

// The route is perfectly clean! It just points to the controller. router.get('/', userController.getAllUsers);

module.exports = router;

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
---

## 7. Output Explanations

By implementing the Controller pattern, your routes file becomes an index. You can read `router.get('/', userController.getAllUsers)` and immediately know exactly what that route does without having to read 50 lines of complex database code. This is what Senior Developers look for in a portfolio!

---

## 8. Common Mistakes

1. **Applying limits to static files:** If you apply a 100-request Rate Limit globally, and your `index.html` loads 50 images and 5 CSS files, the user hits the limit instantly on their first visit! Only apply Rate Limiting to your `/api` routes or specific login routes.
2. **Ignoring Validation:** Sanitization removes bad characters (`$`), but it doesn't check if the email is actually an email. You still need validation (like Mongoose schemas or Joi).
3. **Fat Controllers:** Don't put hundreds of lines of code in a controller. If logic gets too complex, abstract it into a `services/` folder.

---

## 9. Best Practices Summary

1. **Use Helmet** for HTTP headers.
2. **Use Rate Limiting** for API protection.
3. **Use express-mongo-sanitize** against NoSQL injection.
4. **Use Bcrypt** for passwords.
5. **Use .env** for secrets.
6. **Use MVC Architecture** to organize files.
7. **Use a Global Error Handler** to catch crashes.

---

## 10. Exercises

1. Review your `package.json` file. Ensure you don't have unused packages.
2. Create a folder structure on your computer:
   - `models/`
   - `views/`
   - `controllers/`
   - `routes/`
   - `public/`
   - `app.js`
   This is the standard backend skeleton!

---

## 11. Mini Project: Secure API app

**Objective:** Build a production-ready API skeleton equipped with modern security middlewares.

**Step 1:** Dependencies
`npm install express helmet express-rate-limit express-mongo-sanitize`

**Step 2:** The Code (`secureApp.js`)

javascript id="ch28-mini-project" const express = require('express'); const helmet = require('helmet'); const rateLimit = require('express-rate-limit'); const mongoSanitize = require('express-mongo-sanitize');

const app = express();

// --- 1. GLOBAL SECURITY MIDDLEWARES ---

// Set secure HTTP headers app.use(helmet());

// Limit requests to 50 per 10 minutes const limiter = rateLimit({ windowMs: 10 * 60 * 1000, max: 50, message: "Rate limit exceeded. Try again in 10 minutes." }); app.use('/api', limiter); // Only apply to API routes

// Body parser app.use(express.json({ limit: '10kb' })); // Limit body size to prevent payload crashing

// Data Sanitization against NoSQL query injection app.use(mongoSanitize());

// --- 2. ROUTES ---

app.get('/api/secure-data', (req, res) => { res.json({ success: true, message: "You have accessed a highly secure API endpoint!" }); });

app.post('/api/login', (req, res) => { // Because of mongoSanitize, if req.body.email contained {"$gt": ""}, // the '$' is completely stripped out before reaching this point! console.log("Sanitized Body:", req.body); res.json({ message: "Login attempt processed securely." }); });

// --- 3. SERVER --- const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(Secure Server running on port ${PORT}); }); ``

---

12. Coding Challenges

Challenge 1: Look up an NPM package called cors. Install it and implement it in the Secure API. CORS is required if your frontend (hosted on domain A) tries to fetch data from your backend (hosted on domain B).

Challenge 2: Currently, the rate limiter limits the entire /api. Create a specific rate limiter called loginLimiter that only allows 5 requests per hour. Apply it *only* to the app.post('/api/login') route to strictly prevent password guessing.

---

13. MCQs with Answers

Q1: Which NPM package is used to secure Express apps by setting various HTTP headers? A) cors B) helmet C) shield D) express-secure Answer: B

Q2: What is the primary purpose of Rate Limiting? A) To speed up the database. B) To compress images. C) To limit the number of requests a single IP can make, preventing brute-force and DDoS attacks. D) To limit how much data the server can send back. Answer: C

Q3: What does the MVC acronym stand for in software architecture? A) Model, View, Controller B) Mongo, Vue, Compiler C) Main, Variable, Component D) Middleware, Verification, Control Answer: A

Q4: How does a NoSQL Injection attack usually work in MongoDB? A) By uploading a .exe file. B) By injecting SQL DROP TABLE commands. C) By passing MongoDB query operators (like $gt) inside the JSON body to manipulate the database query. D) By guessing the JWT secret. Answer: C

---

14. Interview Questions

  1. 1. What is 'Defense in Depth' in web security?
*Answer:* It is the practice of using multiple layers of security controls. If one layer fails (e.g., a hacker bypasses validation), another layer (e.g., data sanitization or database-level restrictions) catches the threat. It prevents a single point of failure from compromising the entire app.
  1. 2. Explain the benefits of the MVC folder structure.
*Answer:* MVC separates concerns. Models handle database structure, Views handle the UI presentation, and Controllers handle the business logic. This modularity makes the codebase easier to read, easier for teams to collaborate on, and much easier to write automated tests for.

---

15. FAQs

Q: Do I need all these security packages for a personal project? A: If it's just for learning locally, no. But if you plan to deploy it to the internet or put it on your resume, YES. Employers actively look for helmet and rate-limit in your code to see if you understand production environments.

Q: Does Helmet protect against everything? A: No. Helmet helps protect against common header-based vulnerabilities (like Clickjacking or XSS), but it does not protect against bad business logic, weak passwords, or SQL/NoSQL injections.

---

16. Summary

  • Architecture: Use MVC (Models, Views, Controllers) to organize code cleanly.
  • Headers: Use helmet() to hide Express and secure HTTP headers.
  • Traffic Control: Use express-rate-limit to block spam and brute-force attacks.
  • Data Safety: Use express-mongo-sanitize to block NoSQL injection.
  • Body Limits: Use express.json({ limit: '10kb' })` to prevent massive payload crashing.

---

17. Next Chapter Recommendation

Our application is built, secured, and heavily tested. It is living perfectly on our laptop. But a backend is useless if the world can't access it. In Chapter 29: Deployment of Node.js Applications, we will take our code off our laptop and deploy it live to the internet using cloud hosting providers!

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·