Code Review Standards
Code review is how Node.js tech leads scale quality without reviewing every line forever. These standards prioritize async safety, SQL injection, log PII, and pool lifecycle - the bugs that become SEV2 incidents.
Search across all documentation pages
Code review is how Node.js tech leads scale quality without reviewing every line forever. These standards prioritize async safety, SQL injection, log PII, and pool lifecycle - the bugs that become SEV2 incidents.
Quick-reference recipe card - copy-paste ready.
## Node PR Review Checklist
### Blockers (must fix)
- [ ] No floating promises (`@typescript-eslint/no-floating-promises`)
- [ ] DB queries parameterized (no string concat SQL)
- [ ] No secrets, tokens, or PII in logs
- [ ] `try/finally` or `using` for acquired connections/streams
- [ ] Migrations backward-compatible with N-1 deploy
- [ ] AuthZ check on every mutating route
### Should fix
- [ ] Timeouts on outbound HTTP (`AbortSignal.timeout`)
- [ ] Rate limit on public endpoints
- [ ] Tests for error paths, not only happy path
### Nits (optional)
- [ ] Naming, comment clarityWhen to reach for this:
// BLOCK: floating promise - handler returns before work completes
app.get("/bad", (req, res) => {
fetchData().then((d) => res.json(d)); // no .catch
});
// OK: async handler with explicit error propagation (Express 5 passes to error middleware)
app.get("/good", async (req, res) => {
const data = await fetchData();
res.json(data);
});// BLOCK: SQL injection
const id = req.params.id;
await pool.query(`SELECT * FROM users WHERE id = ${id}`);
// OK: parameterized
await pool.query("SELECT * FROM users WHERE id = $1", [id]);// BLOCK: PII in logs
req.log.info({ email: user.email, ssn: user.ssn });
// OK: redacted identifiers
req.log.info({ userId: user.id, action: "login" });// BLOCK: connection leak on error path
const client = await pool.connect();
const result = await client.query("SELECT 1");
client.release();
// OK: always release
const client = await pool.connect();
try {
return await client.query("SELECT 1");
} finally {
client.release();
}What this demonstrates:
try/catch for domain errors| Smell | Risk | Fix |
|---|---|---|
.then() without .catch() | Unhandled rejection | async/await + lint rule |
forEach with async callback | Fire-and-forget | for...of with await |
Missing await on Prisma call | Race, wrong response | ESLint @typescript-eslint/return-await |
| Event emitter without error handler | Process crash | emitter.on("error", ...) |
merge on user objectsconst REDACT = ["password", "token", "email", "ssn", "authorization"];
// Use pino redact paths: ['req.headers.authorization', 'body.password']userId[ ] Expand step only in this PR?
[ ] Down migration exists OR forward-fix documented?
[ ] Index CONCURRENTLY for large tables?
[ ] Worker job payload still compatible?| Size | First response | Approval target |
|---|---|---|
| Small (< 200 LOC) | 4 business hours | 24 hours |
| Medium | 1 business day | 2 days |
| Large | Schedule pairing | Split PR |
nit: in comments.| Alternative | Use When | Don't Use When |
|---|---|---|
| Pair programming | Complex migration | Trivial typo |
| AI review bot | First pass on security | Final approval without human |
| CODEOWNERS | Platform files | Every file slows squad |
One required approver; two for migrations and auth changes. Platform CODEOWNERS for shared libs.
Yes for learning and diversity of bugs caught. Senior approver still required for merge.
Verify response schema matches actual return shape - prevents serialization surprises.
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.
Reviewed by Chris St. John·Last updated Jul 16, 2026