Coding Standards
Coding standards for Node.js + TypeScript teams: enforceable in ESLint and CI, not a wiki nobody reads. Aligns with Code Review Standards.
Recipe
Quick-reference recipe card.
// package.json
{
"type": "module",
"scripts": {
"lint": "eslint . && prettier --check .",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@acme/eslint-config": "^3.0.0",
"typescript": "^5.6.0"
}
}// eslint.config.js (flat config)
import acme from "@acme/eslint-config/node-service";
export default [...acme];Non-negotiable rules:
@typescript-eslint/no-floating-promises: error@typescript-eslint/no-misused-promises: errorno-console: warn (use structured logger)import/no-cycle: errorprettier/prettier: error
Working Example
Shared Config Package
// packages/eslint-config/node-service.js
import tseslint from "typescript-eslint";
import prettier from "eslint-config-prettier";
export default tseslint.config(
...tseslint.configs.strictTypeChecked,
{
rules: {
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/require-await": "off",
"no-restricted-imports": ["error", {
patterns: [{ group: ["../**/infrastructure/**"], message: "Use application ports" }],
}],
},
},
prettier,
);tsconfig Baseline
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noUncheckedIndexedAccess": true,
"skipLibCheck": true
}
}Module Boundary (dependency-cruiser)
// .dependency-cruiser.cjs
module.exports = {
forbidden: [
{
name: "no-infra-in-domain",
from: { path: "^src/modules/[^/]+/domain" },
to: { path: "express|fastify|@nestjs" },
},
],
};Deep Dive
Naming Conventions
| Item | Convention |
|---|---|
| Files | kebab-case.ts |
| Types/interfaces | PascalCase |
| Functions/vars | camelCase |
| Env vars | SCREAMING_SNAKE |
| Test files | *.test.ts beside source |
Async Patterns
// Prefer
export async function getUser(id: string): Promise<User | null> { ... }
// Avoid
export function getUser(id: string): Promise<User | null> {
return db.user.findUnique(...); // OK if eslint clean
}Logging
// Use child logger with context
req.log.info({ userId, orderId, msg: "order_created" });
// Never
console.log("order created", user.email);File Layout
src/modules/orders/
domain/
application/
infrastructure/Gotchas
- Per-repo ESLint drift - 40 variants. Fix: Shared
@acme/eslint-configsemver. - strict false on legacy - Never catches up. Fix:
strictwith incremental// @ts-expect-errorbudget decreasing quarterly. - Prettier fights ESLint - CI noise. Fix:
eslint-config-prettierlast in array. - Standards without CI - Optional. Fix: Required check on
main.
Related
- Linting & Formatting Best Practices - tooling depth
- Import Boundaries - module rules
- Code Review Standards - human gates
- Governance Basics - policy context
- Governance Best Practices - condensed list
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.