Busca en todas las páginas de la documentación
8 examples to get you started with Security for Node.js backends - 6 basic and 2 intermediate.
npm install zod express@5 helmet
npm install -D typescript@5.6 tsxNode.js services face untrusted input from HTTP bodies, query strings, headers, uploaded files, webhooks, and environment variables loaded at boot.
Reject malformed input before it reaches business logic.
import { z } from "zod";
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(120),
});
type CreateUser = z.infer<typeof CreateUserSchema>;
function parseCreateUser(body
parse throws on invalid data - map to HTTP 400 in your error handler.unknown for body forces validation - never trust req.body typing from middleware alone.schemas/ module.Related: OWASP Top 10 for APIs - API1 broken object level authorization
IDs and filters from the URL are attacker-controlled.
import { z } from "zod";
const ListQuerySchema = z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(
z.coerce handles string query values from Express/Fastify.limit to prevent DoS via ?limit=999999.Related: Pagination & Filtering - bounded list params
API keys in git history are forever.
import { z } from "zod";
const EnvSchema = z.object({
JWT_SECRET: z.string().min(32),
DATABASE_URL: z.string().url(),
});
export const env = EnvSchema.parse at boot fails deploys early when secrets are missing..env in the image.env or process.env in startup banners.Related: Configuration Basics - twelve-factor config
Default headers reduce XSS, clickjacking, and MIME sniffing risk.
import express from "express";
import helmet from "helmet";
const app = express();
app.use(helmet());
app.disable("x-powered-by");helmet() sets sensible defaults for common headers.disable("x-powered-by") removes framework fingerprinting.Related: Security Headers & CORS - credential CORS rules
Identity and permission checks are separate steps.
type AuthUser = { id: string; role: "admin" | "member" };
function requireAuth(user: AuthUser | undefined): AuthUser {
if (!user) throw
user.id === resource.ownerId) on every object access.user; handlers still verify object-level access.Related: Security Rules - baseline HTTP rules
Stack traces belong in logs, not JSON responses.
import express from "express";
import pino from "pino";
class HttpError extends Error {
constructor(public status: number, message: string) {
super(message);
}
err with Pino { err } serializer for stack traces.ZodError to 400 with field paths, not internal messages.Related: Error Response Standards - consistent API errors
Fetching arbitrary URLs enables access to internal metadata services.
import { lookup } from "node:dns/promises";
import ipaddr from "ipaddr.js";
async function assertPublicUrl(raw: string): Promise<URL> {
const url = new URL(raw);
if
Related: SSRF Guards - full guard implementation
Deep merge from untrusted JSON can poison Object.prototype.
function safeAssign<T extends Record<string, unknown>>(
target: T,
source: unknown
): T {
if (source === null || typeof source !== "object"
Object.assign(target, req.body) without key filtering on untrusted input.parse over deep merge for request shaping.lodash versions and avoid _.merge on user JSON.Related: Prototype Pollution - lodash and merge risks
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.