Skip to content
Back to skills

Node Backend

ASecurity

Build production Node.js backends — Express/Fastify/Nest, the event loop, async/await correctness, streams, graceful shutdown, and not blocking the single thread. Use when writing a Node HTTP service, debugging "the server hangs / is slow under load", handling errors in async code, streaming large payloads, managing the process lifecycle, or choosing a framework. Triggers — "node server", "Express", "Fastify", "NestJS", "event loop", "the API is slow", "unhandled rejection", "stream", "gracef...

  • 6 stars
  • 0 votes
  • 1 copy
  • 5 views
  • Added September 4, 2026
developmentjavascripttypescriptpythonjavanodeexpressdebuggingapibackendsecurity

Works with

  • cursor
  • api

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned September 4, 2026

npx -y skills add kouroshez/coding-os --skill node-backend --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Node Backend?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Node Backend
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/kouroshez-node-backend-a918119f/badge)](https://www.skillsdirectory.com/skills/kouroshez-node-backend-a918119f)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: node-backend
tier: stack
domain: [backend]
description: Build production Node.js backends — Express/Fastify/Nest, the event loop, async/await correctness, streams, graceful shutdown, and not blocking the single thread. Use when writing a Node HTTP service, debugging "the server hangs / is slow under load", handling errors in async code, streaming large payloads, managing the process lifecycle, or choosing a framework. Triggers — "node server", "Express", "Fastify", "NestJS", "event loop", "the API is slow", "unhandled rejection", "stream", "graceful shutdown", any backend `*.js`/`*.ts` with `http`/`express`. Pairs with typescript (typed Node), api-design (the contract), security-web (server hardening), observability (logging/metrics), performance (profiling).
globs: ""
paths: []
last_reviewed: "2026-06-04"
versions_ref: versions.json
---

# Node.js Backend

Node runs your JavaScript on **one** thread with an event loop. It scales by never blocking that thread — every CPU-bound or sync call freezes *all* requests. The craft is async-correct code, streaming over buffering, and a clean process lifecycle.

> Audit a package.json for engine pin, lockfile, and risky scripts:
> `python3 scripts/check_package.py package.json`

## Never block the event loop

```javascript
// Wrong — sync read blocks EVERY in-flight request until the file loads
import { readFileSync } from "node:fs";
app.get("/data", (req, res) => res.send(readFileSync("big.json")));

// Correct — async yields the loop while I/O happens
import { readFile } from "node:fs/promises";
app.get("/data", async (req, res) => res.send(await readFile("big.json")));
```

`*Sync` calls, `JSON.parse` of a huge string, a tight `for` over a million items,
synchronous crypto/zlib — all freeze the loop and tank p99 for every concurrent
request. For genuine CPU work (image resize, hashing), offload to a Worker Thread
or a separate service; don't compute it inline. Detail → [references/event-loop.md](references/event-loop.md).

## Async errors must be caught — or the process dies

```javascript
// Wrong — a rejected promise in a handler with no catch → unhandledRejection
app.get("/x", async (req, res) => { const u = await db.get(); res.json(u); });
// (if db.get rejects, Express 4 doesn't catch async throws → hangs or crashes)

// Correct — wrap, or use a framework that awaits handlers (Express 5 / Fastify do)
app.get("/x", async (req, res, next) => {
  try { res.json(await db.get()); } catch (e) { next(e); }
});
```

In Express 4 an async handler that throws is **not** caught by the error
middleware — you need a wrapper (`express-async-errors`) or per-handler
try/catch. Express 5 and Fastify await handlers natively. Always register a
top-level `process.on("unhandledRejection")` + `uncaughtException` that logs and
exits — a silent rejection corrupts state.

## Stream large payloads, don't buffer

```javascript
// Wrong — loads the whole file into memory; 10 concurrent = OOM
res.send(await readFile("video.mp4"));

// Correct — pipe; constant memory regardless of size
import { createReadStream } from "node:fs";
createReadStream("video.mp4").pipe(res);
```

Buffering a large response holds it all in RAM per request; streams move it in
chunks at constant memory. Use `pipeline()` (handles backpressure + cleanup) over
manual `.pipe()` for production. Same for request bodies and DB cursors.

## Graceful shutdown (zero-downtime deploys)

```javascript
const server = app.listen(8080);
for (const sig of ["SIGTERM", "SIGINT"]) {
  process.on(sig, async () => {
    server.close();                 // stop accepting new connections
    await db.end();                 // drain pools / in-flight work
    process.exit(0);
  });
}
```

On deploy the orchestrator sends `SIGTERM`; without a handler, in-flight requests
are killed mid-response. Close the listener, drain connections, then exit. Set a
timeout so a stuck drain still exits.

## Framework pick

| | Express | Fastify | NestJS |
|---|---|---|---|
| style | minimal, middleware | minimal, fast, schema-first | opinionated, DI, decorators |
| async errors | v5 native (v4 needs wrapper) | native | native |
| validation | bring your own | built-in JSON-schema | class-validator |
| use when | simplest, huge ecosystem | perf + built-in validation | large team, structured DI |

Pin the Node version in `engines` and use the **Active LTS** in production, not
Current — Current ships breaking changes. Versions → [versions.json](versions.json).

## Anti-patterns (reject on sight)

- Any `*Sync` fs/crypto/zlib call on the request path → blocks the loop.
- An async route handler with no error path (Express 4) → unhandled rejection.
- No `process.on("unhandledRejection")` / `uncaughtException` → silent death.
- Buffering a large file/response instead of streaming → OOM under load.
- No `SIGTERM` handler → killed in-flight requests on every deploy.
- CPU-heavy work inline (sync hashing, big loops) → use a Worker Thread / queue.
- `npm install` in CI (mutates lockfile) → `npm ci` (lockfile-exact, reproducible).

## See also

- [references/event-loop.md](references/event-loop.md) — the loop phases, blocking sources, workers, backpressure.
- [references/async-and-errors.md](references/async-and-errors.md) — promises, error propagation, AbortController, timeouts.
- [assets/node-checklist.md](assets/node-checklist.md) — the ship gate.
- [typescript](../typescript/SKILL.md) · [api-design](../api-design/SKILL.md) · [security-web](../security-web/SKILL.md) · [observability](../observability/SKILL.md).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.