Part 12 · 3 chapters · ~18 min

Code Quality, Tooling, Testing and Security

The toolchain landscape, TypeScript in Node with type stripping, linting, node:test against Vitest and Jest, test strategy and Testcontainers, module mocking, coverage internals, property-based testing and fuzzing, load testing methodology, monorepo tooling, dependency health, and Node-specific security.

38

Toolchain, TypeScript and linting

code
// tsconfig.json for a modern Node service
{ "compilerOptions": {
    "target": "es2023", "module": "nodenext", "moduleResolution": "nodenext",
    "strict": true, "noUncheckedIndexedAccess": true, "isolatedModules": true,
    "erasableSyntaxOnly": true, "verbatimModuleSyntax": true, "noEmit": true } }

// package.json scripts
"typecheck": "tsc",
"lint": "eslint .",
"test": "node --test 'test/**/*.test.ts'",
"start": "node src/server.ts"
THE TOOLCHAIN, BY JOB
what each tool actually does
tscType checks and can emit JS. Theonly full type checker. Slow onbig repos; run it in CI with--noEmit.esbuild / swcStrip types and transform syntaxvery fast, no type checking. Usedinside bundlers and test runners.type strippingNode 22.6+ runs .ts by erasingtypes (default on from 23.6). Noenums or namespaces with runtimecode.Rolldown / RspackRust bundlers: Rolldown powersnewer Vite, Rspack is awebpack-compatible fast bundler.ESLint / Biome / oxlintESLint flat config for rules andplugins; Biome and oxlint are muchfaster for common rules.node:test / VitestBuilt-in runner with mocks andcoverage, or Vitest for watch modeand a rich ecosystem.
swipe the figure sideways, or tap expand for full screen
1/6
tsc
tsc is the type checker; everything else only removes types. A typical setup: tsc --noEmit in CI for correctness, a fast transpiler for builds and tests.
the only real type checkertsc --noEmit in CI
39

Testing strategy, real databases and fuzzing

layerwhat to testwhere it runs
unitpure logic: fee rules, state machines, parsersevery save and every PR
integrationrepositories against a real Postgres, queue workers against real Redisevery PR, with Testcontainers
contractthe API matches its OpenAPI schema; consumers' expectations (Pact)every PR
end to enda few critical journeys through deployed servicesafter deploy to staging
code
// integration test against a real Postgres with Testcontainers and node:test
import { test, before, after } from 'node:test'; import assert from 'node:assert/strict';
import { PostgreSqlContainer } from '@testcontainers/postgresql';
let pg; before(async () => { pg = await new PostgreSqlContainer('postgres:17').start(); await migrate(pg.getConnectionUri()); });
after(() => pg.stop());
test('a transfer is applied once even when retried', async () => {
  const key = crypto.randomUUID();
  await createTransfer({ amountKobo: 500, to: 'b' }, key);
  await createTransfer({ amountKobo: 500, to: 'b' }, key);
  assert.equal(await balance('b'), 500);
});

// property-based test: fast-check
fc.assert(fc.property(fc.array(fc.integer({ min: 1, max: 1e6 })), xs => sumKobo(xs) === xs.reduce((a, b) => a + b, 0)));

Mocking modules (mock.module in node:test, vi.mock) couples tests to import paths and internal structure; prefer passing dependencies in. Coverage from V8 (c8, --experimental-test-coverage) counts lines executed, not behaviour checked: 90% coverage with weak assertions proves little. Load testing is a method: a hypothesis, a realistic workload mix, a ramp, a steady phase, percentiles, and a comparison with the last release.

40

Monorepos, dependencies and Node security

Node-specific riskexampledefence
prototype pollutiondeep-merging user JSON sets __proto__.isAdminvalidate input schemas; Object.create(null) maps; avoid naive deep merge; --disable-proto=delete
unsafe deserialisationeval, node-serialize, YAML with custom tagsJSON only; safe YAML loaders
SSRFfetching a user-supplied URL reaches the cloud metadata endpointallowlists, block private ranges after DNS resolution, egress proxies
ReDoSa regex with nested quantifiers on user inputsafe patterns, length limits, RE2
path traversal../../etc/passwd in a file namepath.resolve then check the prefix
supply chainmalicious install scriptslockfiles, npm ci, ignore-scripts, provenance (Infra P8)
code
# the permission model: deny by default for a process
node --permission --allow-fs-read=./config --allow-fs-write=/tmp server.js
# also: --allow-child-process, --allow-worker, --allow-addons; process.permission.has('fs.write', '/tmp')
node --frozen-intrinsics server.js     # freezes built-in prototypes (experimental): no pollution of Array.prototype

Monorepos: npm, pnpm or yarn workspaces link packages locally; Turborepo and Nx add a task graph and a cache keyed by input hashes, so only affected packages build and test, and results can be shared between CI and laptops (remote cache).