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.
Toolchain, TypeScript and linting
// 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"Testing strategy, real databases and fuzzing
| layer | what to test | where it runs |
|---|---|---|
| unit | pure logic: fee rules, state machines, parsers | every save and every PR |
| integration | repositories against a real Postgres, queue workers against real Redis | every PR, with Testcontainers |
| contract | the API matches its OpenAPI schema; consumers' expectations (Pact) | every PR |
| end to end | a few critical journeys through deployed services | after deploy to staging |
// 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.
Monorepos, dependencies and Node security
| Node-specific risk | example | defence |
|---|---|---|
| prototype pollution | deep-merging user JSON sets __proto__.isAdmin | validate input schemas; Object.create(null) maps; avoid naive deep merge; --disable-proto=delete |
| unsafe deserialisation | eval, node-serialize, YAML with custom tags | JSON only; safe YAML loaders |
| SSRF | fetching a user-supplied URL reaches the cloud metadata endpoint | allowlists, block private ranges after DNS resolution, egress proxies |
| ReDoS | a regex with nested quantifiers on user input | safe patterns, length limits, RE2 |
| path traversal | ../../etc/passwd in a file name | path.resolve then check the prefix |
| supply chain | malicious install scripts | lockfiles, npm ci, ignore-scripts, provenance (Infra P8) |
# 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.prototypeMonorepos: 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).