DevTools

Cheatsheet Node.js

Runtime JavaScript server-side

Back to languages
Node.js
101 cards found
Categories:
Versions:

Modules and Setup


11 cards
CommonJS (require)
// math.js
function add(a, b) { return a + b; }
module.exports = { add };

// app.js
const { add } = require("./math");
console.log(add(2, 3)); // 5

CommonJS is Node's classic module system. Use module.exports to export and require() to import.

Native modules
const fs = require("fs");
const path = require("path");
const http = require("http");
const the = require("the");
const crypto = require("crypto");

console.log(os.platform());    // "win32"
console.log(os.cpus().length); // 8

Node includes native modules (fs, path, http, the, crypto...) that need no installation. Import them directly by name.

Environment variables
// .env
PORT=3000
DB_URL=mongodb://localhost/app

// app.js
require("dotenv").config();
const port = process.env.PORT || 3000;

// Node 20+ (without dotenv):
// node --env-file=.env app.js

Environment variables keep configuration out of the code. dotenv loads the .env file into process.env; on Node 20+ use --env-file.

ES Modules (import)
// package.json
{ "type": "module" }

// math.js
export function add(a, b) {
  return a + b;
}

// app.js
import { add } from "./math.js";

ES Modules are the modern standard. Enable them with "type": "module" in package.json. The .js extension is required in the import.

Dynamic import
// Loads only when needed
const module = await import("./pesado.js");

// Conditional
if (process.env.DEBUG) {
  const { log } = await import("./debug.js");
  log("modo debug");
}

The dynamic import() loads modules on demand and returns a Promise. Useful for code-splitting and optional dependencies.

CLI arguments
// node app.js --name=Anna --age=30
const args = process.argv.slice(2);

// Simple parse:
const params = Object.fromEntries(
  args.map(a => a.replace("--", "").split("="))
);
console.log(params.name); // "Anna"

process.argv contains the command-line arguments. The first two are node and the file — use slice(2) to ignore them.

Exports: several ways
// Several functions:
module.exports = { add, subtract, PI };

// A class:
module.exports = class Server { ... };

// Add to the existing exports:
exports.name = "app";
exports.version = "1.0";

module.exports is the object returned by require. exports is a shortcut — but reassigning exports = {} does not work (use module.exports).

__dirname and __filename
// CommonJS
console.log(__dirname);  // the file's folder
console.log(__filename); // full path

const path = require("path");
const config = path.join(__dirname, "config.json");

In CommonJS, __dirname is the current file's folder and __filename the full path. Essential for building absolute paths.

Events (EventEmitter)
const EventEmitter = require("events");
const emitter = new EventEmitter();

emitter.on("data", (d) => {
  console.log("Received:", d);
});
emitter.once("start", () => console.log("1x"));

emitter.emit("data", { id: 1 });

The EventEmitter is the base of Node's event pattern. on registers a listener, emit fires it and once runs only once.

Default vs Named
// math.js (ESM)
export default function add(a, b) {
  return a + b;
}
export const PI = 3.14;

// app.js
import add, { PI } from "./math.js";

A module can have one export default (imported without braces) and several named exports (with braces). Combine both in the same import.

import.meta (ESM)
// ES Modules (no __dirname)
import { fileURLToPath } from "url";
import path from "path";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

In ES Modules there is no __dirname. Use import.meta.url with fileURLToPath to get the file's path.

File System


11 cards
Read a file
const fs = require("fs/promises");

// Asynchronous (recommended)
const content = await fs.readFile(
  "data.txt", "utf-8"
);

// Synchronous (blocks the event loop)
const fsSync = require("fs");
const txt = fsSync.readFileSync("data.txt", "utf-8");

Use fs/promises with await to avoid blocking. readFileSync is only acceptable in startup or CLI scripts.

List and check
const fs = require("fs/promises");

// List a directory
const items = await fs.readdir("./src");

// With the file type:
const comTipo = await fs.readdir("./src", {
  withFileTypes: true,
});
comTipo.filter(e => e.isFile());

// Check existence
await fs.access("file.txt"); // throws an error if it doesn't exist

readdir lists the contents of a folder. With withFileTypes you get the type of each entry. access checks existence (throws an error otherwise).

Delete and rename
const fs = require("fs/promises");

// Delete a file
await fs.unlink("temp.txt");

// Delete a folder (with contents)
await fs.rm("folder", { recursive: true });

// Rename / move
await fs.rename("old.txt", "new.txt");

unlink deletes files. rm with recursive deletes whole folders. rename also works for moving.

Write a file
const fs = require("fs/promises");

// Overwrite (creates if it doesn't exist)
await fs.writeFile("log.txt", "Hello");

// Append at the end
await fs.appendFile("log.txt", "\nNova line");

// Create a folder (with subfolders)
await fs.mkdir("folder/sub", { recursive: true });

writeFile overwrites the content and appendFile adds to the end. mkdir with recursive creates the whole hierarchy.

path module
const path = require("path");

path.join(__dirname, "src", "app.js")
path.basename("/a/b/f.txt")   // "f.txt"
path.extname("photo.png")      // ".png"
path.resolve("./config")      // absolute path
path.dirname("/a/b/f.txt")    // "/a/b"

path handles paths safely on any OS. join concatenates with the correct separator (/ or \).

Information (stat)
const fs = require("fs/promises");

const info = await fs.stat("file.txt");

info.size          // size in bytes
info.isFile()      // true
info.isDirectory() // false
info.mtime         // last modification
info.birthtime     // creation

stat returns file metadata: size, type and dates. Useful to check before processing or to display to the user.

Copy files
const fs = require("fs/promises");

// Single file
await fs.copyFile("source.txt", "destination.txt");

// Whole folder (Node 16.7+)
await fs.cp("folder-src", "folder-dst", {
  recursive: true,
});

copyFile copies a file. cp with recursive copies whole folders — useful for backups and templates.

Streams (large files)
const fs = require("fs");

const reading = fs.createReadStream("big.csv");
const writing = fs.createWriteStream("copy.csv");

reading.pipe(writing);

reading.on("data", (chunk) => {
  console.log("Lido:", chunk.length, "bytes");
});
reading.on("end", () => console.log("End"));

streams process files in blocks (chunks) without loading everything into memory. pipe connects the read to the write automatically.

Watch changes (watch)
const fs = require("fs");

const watcher = fs.watch("./src", (event, file) => {
  console.log(event, file); // "change" "app.js"
});

// Stop watching
watcher.close();

fs.watch notifies when files change (create, modify, delete). Useful for build tools and live reload. Call close() to stop.

Read and write JSON
const fs = require("fs/promises");

// Read
const text = await fs.readFile("config.json", "utf-8");
const config = JSON.parse(text);

// Write (formatted)
await fs.writeFile(
  "config.json",
  JSON.stringify(config, null, 2)
);

Combine readFile/writeFile with JSON.parse and JSON.stringify. The third argument (2) formats with indentation.

Stream pipeline
const { pipeline } = require("stream/promises");
const fs = require("fs");
const zlib = require("zlib");

await pipeline(
  fs.createReadStream("data.csv"),
  zlib.createGzip(),
  fs.createWriteStream("data.csv.gz")
);

pipeline chains streams and propagates errors correctly (unlike the simple pipe). Ideal for transforming and compressing data.

Asynchronism


12 cards
Promises
function wait(ms) {
  return new Promise((resolve) =>
    setTimeout(resolve, ms)
  );
}

wait(1000).then(() => {
  console.log("1s after");
});

// Reject:
new Promise((_, reject) =>
  reject(new Error("failed"))
);

A Promise represents a future value. resolve indicates success and reject indicates an error. It is consumed with .then().

setTimeout / setInterval
// Once after 2s
setTimeout(() => {
  console.log("delayed");
}, 2000);

// Repeat every 1s
const id = setInterval(() => {
  console.log("tick");
}, 1000);

clearInterval(id); // stop
clearTimeout(id);  // cancel timeout

setTimeout runs once after a delay and setInterval repeats periodically. Store the ID to cancel with clear.

AbortController
const controller = new AbortController();

// Cancel after 3s
const timeout = setTimeout(
  () => controller.abort(), 3000
);

try {
  const resp = await fetch(url, {
    signal: controller.signal,
  });
} catch (e) {
  console.log("Cancelado:", e.name); // "AbortError"
}

AbortController cancels asynchronous operations (like fetch). Pass the signal and call abort() to interrupt.

async / await
async function fetchData() {
  try {
    const resp = await fetch(url);
    const data = await resp.json();
    return data;
  } catch (error) {
    console.error("Failure:", error.message);
    throw error;
  }
}

async/await makes asynchronous code readable like synchronous code. await pauses until the Promise resolves. try/catch handles errors.

setImmediate / nextTick
console.log("1 - synchronous");

process.nextTick(() => console.log("2 - nextTick"));
Promise.resolve().then(() => console.log("3 - microtask"));
setImmediate(() => console.log("4 - immediate"));
setTimeout(() => console.log("5 - timer"), 0);

// Typical order: 1, 2, 3, 4/5

process.nextTick runs before microtasks. setImmediate executes in the check phase of the event loop. Both defer code without using timers.

Limited concurrency
async function mapLimit(items, limit, fn) {
  const results = [];
  for (let i = 0; i < items.length; i += limit) {
    const lote = items.slice(i, i + limit);
    results.push(...await Promise.all(lote.map(fn)));
  }
  return results;
}

await mapLimit(urls, 5, (u) => fetch(u));

To avoid opening hundreds of requests in parallel, process in batches (Promise.all per group). It controls memory and socket usage.

Promise.all / allSettled
// All in parallel (fails if one fails)
const [a, b, c] = await Promise.all([
  fetch("/api/a"),
  fetch("/api/b"),
  fetch("/api/c"),
]);

// All (even with errors)
const results = await Promise.allSettled(promessas);
results.filter(r => r.status === "fulfilled");

Promise.all runs in parallel but rejects if one fails. allSettled waits for all and returns the state of each one.

Promisify (callbacks)
const { promisify } = require("util");
const fs = require("fs");

// Convert callback → Promise
const read = promisify(fs.readFile);
const content = await read("data.txt", "utf-8");

// Today fs/promises is preferred:
const fsp = require("fs/promises");

util.promisify converts callback functions ((err, result) style) into Promises. Nowadays, prefer the native */promises modules.

Handle errors (pattern)
// Wrapper to avoid repeated try/catch
async function capture(promise) {
  try {
    const data = await promise;
    return [data, null];
  } catch (error) {
    return [null, error];
  }
}

const [data, error] = await capture(fetch(url));
if (error) console.error(error);

This [data, error] pattern (inspired by Go) avoids nested try/catch. Check for an error before using the data.

Promise.race / any
// First to resolve OR reject
const fastest = await Promise.race([
  fetch("/api/slow"),
  wait(5000).then(() => "timeout"),
]);

// First to resolve (ignores errors)
const first = await Promise.any([
  fetch("/api/server1"),
  fetch("/api/server2"),
]);

race returns the first result (success or error). any returns the first success, ignoring rejections. Useful for timeouts.

Async iterators (for await)
const fs = require("fs");

// Read a stream chunk by chunk
const stream = fs.createReadStream("big.txt");

for await (const chunk of stream) {
  console.log("Block:", chunk.length);
}

for await...of iterates over asynchronous sources (streams, async generators) sequentially. It consumes each chunk the it arrives.

Event Loop (concept)
console.log("1 - synchronous");

setTimeout(() => console.log("3 - timer"), 0);

Promise.resolve().then(
  () => console.log("2 - microtask")
);

// Order: 1, 2, 3
// Synchronous → Microtasks → Timers

The Event Loop processes: synchronous code → microtasks (Promises) → timers (setTimeout). Even with delay 0, the timer runs last.

HTTP and Server


10 cards
Native HTTP server
const http = require("http");

const server = http.createServer((req, res) => {
  res.writeHead(200, {
    "Content-Type": "text/plain",
  });
  res.end("Hello do Node!");
});

server.listen(3000, () => {
  console.log("Em http://localhost:3000");
});

The http module creates a server with no dependencies. The callback receives the request (req) and the response (res). listen starts it on the port.

Serve static files
const http = require("http");
const fs = require("fs");
const path = require("path");

http.createServer((req, res) => {
  const file = path.join("./public", req.url);
  fs.readFile(file, (error, data) => {
    if (error) {
      res.writeHead(404);
      return res.end("Not found");
    }
    res.end(data);
  });
}).listen(3000);

A static file server reads from disk and returns it to the client. In production, use Express (express.static) or a CDN.

Redirects
// 301 (permanent)
res.writeHead(301, { Location: "/new-page" });
res.end();

// 302 (temporary)
res.writeHead(302, { Location: "/login" });
res.end();

301 is permanent (SEO transfers to the new URL). 302 is temporary. The browser follows the Location header automatically.

fetch (Node 18+)
// GET
const resp = await fetch("https://api.example.com/data");
const data = await resp.json();

// POST
await fetch(url, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Anna" }),
});

Since Node 18, fetch is native (no installation needed). It works like in the browser: resp.json() to read the body.

URL and query params
const url = new URL(
  "http://site.com/search?q=node&p=2"
);

url.pathname               // "/search"
url.searchParams.get("q")  // "node"
url.searchParams.get("p")  // "2"

// Build a URL:
const u = new URL("http://api.com/data");
u.searchParams.set("page", "1");

The URL class parses and builds URLs. searchParams gives easy access to the query string parameters.

HTTPS / TLS
const https = require("https");
const fs = require("fs");

const options = {
  key: fs.readFileSync("key.pem"),
  cert: fs.readFileSync("cert.pem"),
};

https.createServer(options, (req, res) => {
  res.end("secure connection");
}).listen(443);

The https module creates an encrypted server. It needs a key (key) and certificate (cert). In production, a reverse proxy is normally used.

fetch with timeout
const resp = await fetch(url, {
  signal: AbortSignal.timeout(5000), // 5s
});

if (!resp.ok) {
  throw new Error("HTTP " + resp.status);
}
const data = await resp.json();

AbortSignal.timeout() cancels the fetch after a limit. Always check resp.ok — fetch does not throw an error on 4xx/5xx status.

Headers and status
// Read request headers:
req.headers["content-type"]
req.headers["authorization"]

// Set on the response:
res.writeHead(200, {
  "Content-Type": "application/json",
  "Cache-Control": "no-cache",
  "X-Custom": "value",
});

headers carry metadata (content type, authentication, cache). Read them in req.headers and set them with writeHead.

Read request body (POST)
http.createServer((req, res) => {
  let body = "";
  req.on("data", (chunk) => { body += chunk; });
  req.on("end", () => {
    const data = JSON.parse(body);
    res.end("Received: " + data.name);
  });
}).listen(3000);

On the native server, the body arrives in chunks via data events. Accumulate them and process on the end event. Express simplifies this with express.json().

Cookies
// Set a cookie on the response:
res.writeHead(200, {
  "Set-Cookie": "session=abc123; HttpOnly; Path=/",
});

// Read cookies from the request:
const cookies = req.headers.cookie; // "session=abc123"

cookies are set with the Set-Cookie header and read in req.headers.cookie. The HttpOnly flag prevents access via JavaScript.

Express.js


12 cards
Express setup
npm install express

const express = require("express");
const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.listen(3000, () => {
  console.log("Server active na port 3000");
});

Express is Node's most popular web framework. express.json() parses the JSON body automatically.

Middleware
// Custom logger
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();  // pass to the next
});

// Error middleware (4 arguments)
app.use((error, req, res, next) => {
  console.error(error.stack);
  res.status(500).json({ error: error.message });
});

middleware runs between the request and the route. next() passes to the next one. The error handler has 4 arguments and must be last.

Validation with middleware
function validarId(req, res, next) {
  const id = Number(req.params.id);
  if (isNaN(id)) {
    return res.status(400).json({
      error: "ID must be numeric",
    });
  }
  req.id = id;
  next();
}

app.get("/users/:id", validarId, (req, res) => {
  res.json({ id: req.id });
});

A validation middleware checks the data before the route. If it fails, it responds with an error and does not call next().

Routes and parameters
app.get("/", (req, res) => {
  res.send("Start");
});

// Route parameter
app.get("/users/:id", (req, res) => {
  res.json({ id: req.params.id });
});

// Query string ?search=x
app.get("/search", (req, res) => {
  res.json({ q: req.query.search });
});

Routes map method + path. The :params capture URL segments. req.query reads the query string.

Chain handlers
function validate(req, res, next) {
  if (!req.body.name) return next(new Error("name"));
  next();
}
function authenticate(req, res, next) {
  if (!req.user) return res.status(401).end();
  next();
}

app.post("/data", authenticate, validate, (req, res) => {
  res.json({ ok: true });
});

You can pass several handlers on a route — they run in sequence. Each one calls next() to advance or responds/throws an error to stop.

File upload (multer)
npm install multer

const multer = require("multer");
const upload = multer({ dest: "uploads/" });

app.post("/upload", upload.single("file"),
  (req, res) => {
    console.log(req.file.originalname);
    console.log(req.file.path);
    res.json({ ok: true });
  }
);

multer handles multipart uploads. single() accepts one file. The data ends up in req.file (name, path, size).

req object (request)
app.post("/users/:id", (req, res) => {
  req.params.id      // route parameter
  req.query.active    // query string
  req.body           // body (with express.json)
  req.headers        // headers
  req.method         // "POST"
  req.path           // "/users/5"
  req.ip             // client IP
});

The req object carries everything about the request: params, query, body, headers, method and ip.

Responses (res)
res.send("text HTML");
res.json({ ok: true });
res.status(201).json({ id: 1 });
res.redirect("/login");
res.download("/file.pdf");
res.sendFile(path.join(__dirname, "index.html"));
res.status(204).end();  // no body

The res object has methods for each response type. json() sets the Content-Type automatically. 204 is "success with no content".

app.set and configuration
app.set("port", process.env.PORT || 3000);
app.set("view engine", "ejs");

// Read configuration
const port = app.get("port");

// Enable/disable options
app.enable("trust proxy");
app.disable("x-powered-by");

app.listen(app.get("port"));

app.set stores configuration key-value pairs and app.get("key") reads them. enable/disable toggle boolean flags.

Modular router
// routes/products.js
const router = express.Router();

router.get("/", list);
router.post("/", create);
router.get("/:id", ver);
router.put("/:id", update);
router.delete("/:id", delete);

module.exports = router;

// app.js
app.use("/products", require("./routes/products"));

The Router organizes routes into separate files. Each module handles a resource. The prefix is set in app.use.

Static files
// Serve the public/ folder
app.use(express.static("public"));

// With a prefix
app.use("/assets", express.static("public/assets"));

// Access: http://localhost:3000/img/logo.png
// Or: http://localhost:3000/assets/style.css

express.static serves files directly (CSS, JS, images). Without a prefix, the file is accessed from the root; with a prefix, it goes under that path.

404 and error handler
// 404: no route matched (at the end)
app.use((req, res) => {
  res.status(404).json({ error: "Not found" });
});

// Error handler (4 args, after the 404)
app.use((error, req, res, next) => {
  res.status(error.status || 500).json({
    error: error.message,
  });
});

A final middleware with no path catches nonexistent routes (404). The error handler (4 args) centralizes failures. Both must be at the end.

API REST e Auth


12 cards
Complete CRUD
let products = [];

app.get("/products", (req, res) =>
  res.json(products));

app.post("/products", (req, res) => {
  const new = { id: Date.now(), ...req.body };
  products.push(new);
  res.status(201).json(new);
});

app.delete("/products/:id", (req, res) => {
  products = products.filter(p => p.id != req.params.id);
  res.status(204).end();
});

A CRUD uses GET (list), POST (create, 201), PUT (update) and DELETE (204). The data comes in req.body.

Error handling
class ApiError extends Error {
  constructor(status, message) {
    super(message);
    this.status = status;
  }
}

// In the route:
throw new ApiError(404, "Product not found");

// Global handler (last middleware):
app.use((error, req, res, next) => {
  const status = error.status || 500;
  res.status(status).json({ error: error.message });
});

Create an ApiError class with the HTTP status. The global handler centralizes all error responses in one place.

Password hashing (bcrypt)
npm install bcrypt

const bcrypt = require("bcrypt");

// Signup: store the hash (never the plain password)
const hash = await bcrypt.hash(password, 10);

// Login: compare
const ok = await bcrypt.compare(password, hash);
if (!ok) return res.status(401).end();

Never store passwords in plain text. bcrypt.hash generates a hash with salt (cost 10) and compare verifies it at login.

HTTP status codes
res.status(200)  // OK
res.status(201)  // Created
res.status(204)  // Success, without content
res.status(400)  // Order invalid
res.status(401)  // Not autenticado
res.status(403)  // Sem permission
res.status(404)  // Not found
res.status(500)  // Error do server

Status codes communicate the result: 2xx success, 4xx client error, 5xx server error. Use them correctly instead of always returning 200.

CORS
npm install cors

const cors = require("cors");

// Allow everything (development)
app.use(cors());

// Specific configuration (production)
app.use(cors({
  origin: "http://localhost:5173",
  methods: ["GET", "POST", "PUT", "DELETE"],
  credentials: true,
}));

CORS controls which domains can access the API. In production, specify the exact origin instead of allowing everything.

Pagination
app.get("/products", (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const start = (page - 1) * limit;

  const results = products.slice(start, start + limit);

  res.json({
    data: results,
    total: products.length,
    page,
    pages: Math.ceil(products.length / limit),
  });
});

Pagination returns only a slice of the data. The client sends page and limit. The response includes the total for the frontend.

Input validation
function validarProduto(req, res, next) {
  const { name, price } = req.body;

  if (!name || name.length < 3) {
    return res.status(400).json({
      error: "Name must have 3+ characters",
    });
  }
  if (typeof price !== "number" || price < 0) {
    return res.status(400).json({ error: "Price invalid" });
  }
  next();
}

app.post("/products", validarProduto, create);

Always validate client data. Check type, length and format. Respond with 400 and a clear message if something fails.

Rate limiting
npm install express-rate-limit

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100,                  // 100 requests per IP
  message: { error: "Many orders" },
});

app.use("/api/", limiter);

rate limiting protects against abuse and DDoS. It limits the number of requests per IP within a time window (windowMs).

API versioning
// By URL prefix (most common)
app.use("/api/v1/products", routerV1);
app.use("/api/v2/products", routerV2);

// Or by Router
const v1 = express.Router();
v1.get("/products", listarV1);
app.use("/api/v1", v1);

Versioning the API (e.g. /api/v1) lets it evolve without breaking old clients. Each version has its own router.

Validate with Zod
npm install zod

const { z } = require("zod");
const schema = z.object({
  name: z.string().min(3),
  price: z.number().positive(),
});

app.post("/products", (req, res) => {
  const result = schema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json(result.error.issues);
  }
  // result.data is validated and typed
});

Zod validates and transforms data with declarative schemas. safeParse does not throw — it returns success and the issues.

JWT authentication
npm install jsonwebtoken

const jwt = require("jsonwebtoken");

// Generate token:
const token = jwt.sign(
  { id: user.id }, "secret", { expiresIn: "1h" }
);

// Verify (middleware):
function auth(req, res, next) {
  const token = req.headers.authorization?.split(" ")[1];
  try {
    req.user = jwt.verify(token, "secret");
    next();
  } catch {
    res.status(401).json({ error: "Not autorizado" });
  }
}

JWT authenticates without a session. The token goes in the Authorization: Bearer ... header. The middleware verifies it and injects the user into req.user.

Request logs (morgan)
npm install morgan

const morgan = require("morgan");

// Predefined format
app.use(morgan("dev"));   // colorido to dev
app.use(morgan("combined")); // formato Apache

// Custom output
app.use(morgan(":method :url :status :response-time ms"));

morgan logs each HTTP request (method, URL, status, time). The dev format is colorful; combined is for production.

NPM and Packages


11 cards
Start a project
npm init -y              // create package.json
npm install              // install dependencies
npm install express      // dependency de production
npm install -D nodemon   // dependency de dev
npm install -g pm2       // global (CLI)

npm init -y creates package.json with default values. -D saves to devDependencies (development only).

package-lock.json
// The lock records the EXACT versions of everything
// (including transitive dependencies)

npm ci    // clean install based on the lock
          // (faster, reproducible)

// Rules:
// - Never edit manually
// - Always commit to git
// - Use npm ci in CI/CD

package-lock.json ensures everyone installs exactly the same versions. npm ci is ideal for servers and CI.

engines and module type
// package.json
{
  "type": "module",        // usar ES Modules
  "engines": {
    "node": ">=20.0.0"     // version minimum
  }
}

// "type": "commonjs" is o default (require)

"type": "module" enables ES Modules in .js files. engines documents the minimum required Node version.

Remove and list packages
npm uninstall package        // remove
npm uninstall -D nodemon    // remove de dev

npm ls                      // tree de dependencies
npm ls --depth=0            // only diretas
npm outdated                // versions desatualizadas
npm audit                   // vulnerabilities

uninstall removes a package and updates package.json. npm ls shows the tree and audit detects known vulnerabilities.

npx
// Run without installing globally
npx create-react-app my-app
npx nodemon app.js
npx jest --watch

// Specific version
npx node@18 -v

// Local packages from node_modules/.bin
npx eslint src/

npx runs packages without installing them globally. If the package exists in node_modules, it uses it; otherwise, it downloads it temporarily.

.npmrc and registries
# .npmrc (project or ~/.npmrc)
registry=https://registry.npmjs.org/
@my-company:registry=https://npm.company.com/
//npm.company.com/:_authToken=${NPM_TOKEN}

# Save token with login:
npm login --registry=https://npm.company.com/

.npmrc configures the registry and authentication tokens. It allows using private registries for scoped packages (@company).

package.json scripts
{
  "scripts": {
    "start": "node app.js",
    "dev": "nodemon app.js",
    "test": "jest",
    "lint": "eslint src/"
  }
}

// Run:
npm run dev
npm start       // "start" not precisa de "run"
npm test

scripts automate commands. npm run name runs any script. start and test work without run.

Workspaces (monorepo)
// package.json (root)
{
  "workspaces": ["packages/*"]
}

// Structure:
// packages/api/package.json
// packages/frontend/package.json
// packages/shared/package.json

npm install        // installs everything
npm run dev -w api // runs in a workspace

workspaces manage multiple packages in a single repository. Shared dependencies are installed once at the root.

Publish a package
// Minimal package.json:
{
  "name": "my-package",
  "version": "1.0.0",
  "main": "index.js"
}

npm login
npm publish

// Update version:
npm version patch  // 1.0.0 → 1.0.1
npm version minor  // 1.0.1 → 1.1.0
npm version major  // 1.1.0 → 2.0.0

To publish, you need an account on npmjs.com. npm version updates the version and creates a commit/tag automatically.

Version management (semver)
npm install package          // latest version
npm install package@1.2.3    // version exata
npm install package@^1.2.0   // compatible 1.x.x
npm install package@~1.2.0   // only patches 1.2.x

npm outdated   // ver desatualizados
npm update     // update inside do range

In semver: ^ accepts minor+patch, ~ only patch. outdated shows what is old and update respects the ranges.

bin and package CLI
// package.json
{
  "name": "my-cli",
  "bin": { "my-cli": "./cli.js" }
}

// cli.js (first line)
#!/usr/bin/env node
console.log("Hello da CLI!");

// npm link → makes the command available

The bin field registers the package's executable commands. The shebang #!/usr/bin/env node indicates the interpreter. npm link installs it locally for testing.

CLI, Tests and Best Practices


11 cards
Watch mode (Node 18+)
// Restart on file save
node --watch app.js

// With environment variables
node --watch --env-file=.env app.js

// Check versions
node -v
npm -v

--watch restarts the server automatically when you save the file. It replaces nodemon in simple projects.

Recommended structure
project/
  src/
    routes/       // routes by resource
    controllers/  // handler logic
    services/     // business logic
    models/       // data access
    middleware/   // validation, auth
    app.js        // Express configuration
  package.json
  .env
  .gitignore

Separate by responsibility: routes define URLs, controllers orchestrate, services contain the logic. Each file does one thing.

API tests (supertest)
npm install -D supertest vitest

const request = require("supertest");

it("creates product", async () => {
  const resp = await request(app)
    .post("/products")
    .send({ name: "Keyboard", price: 50 });

  expect(resp.status).toBe(201);
  expect(resp.body.id).toBeDefined();
});

supertest tests HTTP endpoints without starting the server. Pass the Express app and simulate requests with get/post and send.

Debugging
// Inspect with Chrome DevTools
node --inspect app.js
node --inspect-brk app.js  // pause on start

// Useful logs
console.log("info");
console.warn("warning");
console.error("error");
console.table(arrayOfObjects);
console.time("op");
console.timeEnd("op");  // "op: 123ms"

--inspect opens Chrome DevTools to debug. console.table shows arrays/objects in a table. time/timeEnd measures duration.

Best practices
// Use async/await (not callbacks)
// Always handle errors (try/catch)
// Config in environment variables
// Small, focused modules
// ESLint + Prettier for consistency
// Never commit node_modules
// Use .gitignore and .env.example

Conventions that keep Node code clean and production-ready. .env.example documents the required variables without exposing secrets.

Basic security (helmet)
npm install helmet

const helmet = require("helmet");
app.use(helmet());  // headers de security

// Never expose stack traces in production:
app.use((error, req, res, next) => {
  res.status(500).json({
    error: process.env.NODE_ENV === "production"
      ? "Error internal"
      : error.message,
  });
});

helmet adds security headers automatically. In production, never expose stack traces — show only "Error internal".

Process and shutdown
// Shut down gracefully (Ctrl+C)
process.on("SIGINT", () => {
  console.log("Shutting down...");
  server.close(() => process.exit(0));
});

// Uncaught error
process.on("uncaughtException", (e) => {
  console.error("Fatal:", e);
  process.exit(1);
});

// Rejected promise without catch
process.on("unhandledRejection", (r) => {
  console.error("Rejected:", r);
});

Handle SIGINT to shut down without losing data. uncaughtException is the last resort — log the error and exit.

ESLint and Prettier
npm install -D eslint prettier

// eslint.config.js (flat config)
npx eslint --init

// Format on save (VS Code)
// "editor.formatOnSave": true
// "editor.defaultFormatter": "esbenp.prettier-vscode"

npx eslint src/ --fix

ESLint detects errors and style issues; Prettier formats code automatically. Use them together for consistency.

Performance and profiling
// Generate a CPU profile
node --prof app.js
node --prof-process isolate-*.log > profile.txt

// Heap snapshot (memory)
node --inspect app.js
// → DevTools → Memory → Take snapshot

// Monitor the event loop
const { monitorEventLoopDelay } = require("perf_hooks");

--prof generates CPU profiles and --inspect allows heap snapshots in DevTools. Essential to find bottlenecks and memory leaks.

NODE_ENV and configuration
const env = process.env.NODE_ENV || "development";

const config = {
  development: { debug: true, dbUrl: "localhost" },
  production: { debug: false, dbUrl: process.env.DB_URL },
}[env];

// Start:
// NODE_ENV=production node app.js

NODE_ENV distinguishes environments (development/production). It adjusts behavior such the debug, logging and database connections.

Tests (Vitest / Jest)
npm install -D vitest

// math.test.js
import { describe, it, expect } from "vitest";
import { add } from "./math.js";

describe("add", () => {
  it("sum two numbers", () => {
    expect(add(2, 3)).toBe(5);
  });
});

// npx vitest

Vitest (or Jest) tests functions in isolation. describe groups tests and expect checks results. Run it with npx vitest.

Advanced


11 cards
Buffers (binary data)
const buf = Buffer.from("Hello", "utf-8");
buf.length        // 4 bytes
buf.toString("utf-8")  // "Hello"
buf.toString("base64") // "T2zDoA=="

// Allocate and write
const b = Buffer.alloc(8);
b.writeUInt32BE(1234, 0);

Buffer.concat([buf, b]);

The Buffer represents raw binary data (outside the string). Essential for files, network and encryption. Convert with toString(encoding).

Crypto: Hash
const crypto = require("crypto");

const hash = crypto
  .createHash("sha256")
  .update("secret text")
  .digest("hex");

// MD5 (do not use for security)
crypto.createHash("md5").update("x").digest("hex");

crypto.createHash generates one-way hashes (sha256, md5). Use SHA-256 for integrity; for passwords prefer bcrypt.

Socket.IO
npm install socket.io

const io = require("socket.io")(servidorHttp);

io.on("connection", (socket) => {
  socket.on("chat", (msg) => {
    io.emit("chat", msg); // send a all
  });
  socket.join("sala1");
  io.to("sala1").emit("new", "data");
});

Socket.IO adds rooms, events and automatic fallback over WebSockets. emit sends to everyone; to(room) restricts to a group.

Child Processes
const { exec, spawn } = require("child_process");

// exec: output in a buffer (short commands)
exec("ls -la", (error, stdout) => {
  console.log(stdout);
});

// spawn: stream (long / real-time output)
const proc = spawn("node", ["-v"]);
proc.stdout.on("data", (d) => console.log(d.toString()));

The child_process module runs external commands. exec returns the full output; spawn streams it (better for long processes).

Crypto: HMAC and tokens
const crypto = require("crypto");

// HMAC (hash with a secret key)
const hmac = crypto
  .createHmac("sha256", "secret")
  .update("data")
  .digest("hex");

// Secure random token
const token = crypto.randomBytes(32).toString("hex");

createHmac generates an authenticated hash with a key (verifies integrity + origin). randomBytes creates cryptographically secure tokens.

the module (system)
const the = require("the");

os.platform()      // "win32" | "linux" | "darwin"
os.arch()          // "x64" | "arm64"
os.cpus().length   // CPU colors
os.totalmem()      // RAM total (bytes)
os.freemem()       // RAM free
os.homedir()       // user folder
os.tmpdir()        // temporary folder

The the module gives operating system information: platform, architecture, CPU and memory. Useful to adapt behavior to the environment.

Cluster (multi-core)
const cluster = require("cluster");
const the = require("the");

if (cluster.isPrimary) {
  const numCpus = os.cpus().length;
  for (let i = 0; i < numCpus; i++) {
    cluster.fork(); // one worker por CPU
  }
} else {
  require("./server"); // each worker corre o server
}

cluster creates one process per CPU core, sharing the same port. It works around Node's single-thread limit to use all colors.

Crypto: AES encryption
const crypto = require("crypto");

const key = crypto.scryptSync("password", "salt", 32);
const iv = crypto.randomBytes(16);

const cifra = crypto.createCipheriv("aes-256-cbc", key, iv);
const encriptado = Buffer.concat([
  cifra.update("message secreta", "utf-8"),
  cifra.final(),
]);

createCipheriv encrypts with AES (reversible). Derive the key with scryptSync and use a random iv. To decrypt use createDecipheriv.

Performance (perf_hooks)
const { performance } = require("perf_hooks");

const start = performance.now();
// ... operation ...
const duration = performance.now() - start;
console.log(`Took ${duration.toFixed(2)}ms`);

// Measure with markers
performance.mark("start");
performance.mark("end");
performance.measure("op", "start", "end");

perf_hooks measures time with high precision (decimal milliseconds). performance.now() is more precise than Date.now().

Worker Threads
const { Worker, isMainThread, parentPort } =
  require("worker_threads");

if (isMainThread) {
  const w = new Worker(__filename);
  w.postMessage("calculate");
  w.on("message", (r) => console.log(r));
} else {
  parentPort.on("message", () => {
    parentPort.postMessage("done: " + 40 * 40);
  });
}

worker_threads run JavaScript in parallel (real threads), ideal for CPU-intensive tasks. They communicate via postMessage.

WebSockets (ws)
npm install ws

const { WebSocketServer } = require("ws");
const wss = new WebSocketServer({ port: 8080 });

wss.on("connection", (ws) => {
  ws.on("message", (msg) => {
    ws.send("eco: " + msg);
  });
});

WebSockets keep a bidirectional real-time connection. The ws package creates the server; each connection receives and sends messages.