Web dev - backend
Type discipline
TypeScript / Node.js
strict: true+noUncheckedIndexedAccess: true+exactOptionalPropertyTypes: trueintsconfig.json.- Validate every request/response with
zod. Inferred types come from schemas, not the other way around. - Use
unknownat boundaries (req.body: unknown); narrow withschema.parse(...)before touching. - Avoid
ascasts. If types disagree, fix the type - don't override the checker.
Python
- Type-annotate every function signature in new code.
mypy --strict(orpyright) on changed modules. No# type: ignoreto silence. pydanticv2 models for I/O boundaries - validation + types + serialization in one.from __future__ import annotationsfor forward refs and deferred eval.
Project layout & packaging
TypeScript / Node.js
- Monorepo? Use workspaces (npm/pnpm). Shared modules become
@scope/<pkg>packages with their ownpackage.json. dist/is build output - gitignored unless afile:workspace dep requires committed builds.- Pin Node version in
package.json("engines": { "node": ">=20" }) and.nvmrc. Lockfile (package-lock.jsonorpnpm-lock.yaml) is committed. - One
tsconfig.jsonfor build, optionaltsconfig.test.jsonfor tests.
Python
src/layout for libraries (pyproject.toml+src/<package>/). Avoids the import-from-cwd footgun.- Pin Python version in
pyproject.toml(requires-python = ">=3.11"). Pin runtime deps with upper bounds. Lock withuv lock/pip-compile/poetry lock. requirements.txtis for deployment lockfiles, not authoring. Author inpyproject.toml.- Use
uvorpoetryfor new projects.
Async vs sync
Node.js
- Everything is async by default. Never block the event loop with sync I/O or CPU-bound work (
fs.readFileSync,JSON.parseof a 1GB file, sync crypto). - CPU-bound work:
worker_threadsor a separate process. - Streams beat buffering for large payloads.
pipeline()fromnode:stream/promiseshandles cleanup correctly.
Python
- Pick one I/O model per service. Don't mix async and sync database clients in the same request path - you'll deadlock or block the event loop.
- In async code: never call sync I/O (
requests,time.sleep, blocking DB drivers). Usehttpx,asyncio.sleep, async DB drivers (asyncpg,motor). - CPU-bound work in an async service goes in a thread pool (
asyncio.to_thread- Python 3.9+) or a separate process.
Web frameworks
Express / Fastify (Node.js)
- Validate at the edge with
zod- reject before any business logic runs. - Middleware order matters: security headers → CORS → rate-limit → body parser → auth → routes → error handler.
- One router file per resource (
routes/users.ts,routes/cases.ts). Don't dump every endpoint inapp.ts. - Always set
trust proxycorrectly behind a load balancer or you'll log/limit the wrong IP.
FastAPI
- Pydantic models for every request/response. Use
Depends()for auth, db sessions, settings - not module globals. - Always validate input at the edge. Never trust query strings, headers, JSON bodies, or path params.
Django
- Fat models, thin views; use
select_related/prefetch_relatedto kill N+1 queries.
Flask
- Factory pattern (
create_app()); blueprints for grouping;flask-smorestif you want OpenAPI.
Data & I/O
SQL (SQLite, Postgres, MySQL)
- Parameterized queries always. Template literals / f-strings into SQL = SQL injection waiting to happen.
- SQLite specifics:
PRAGMA journal_mode = WALfor concurrent reads;PRAGMA foreign_keys = ON(off by default!);PRAGMA busy_timeout = 5000to avoidSQLITE_BUSY. - Postgres: use a connection pool;
LISTEN/NOTIFYbeats polling for change feeds. - Indexes: cover the queries you actually run.
EXPLAIN ANALYZEbefore adding a new one.
Files & streams
- Use context managers /
usingpatterns for files, connections, locks. Don't rely on GC. - Stream large files - don't
.read()/.readFile()a 4GB CSV into memory. - Temp files:
tmp/tempfilewith auto-cleanup, not hardcoded/tmp/foo.
Dataframes (Python)
- Prefer
polarsfor new pipelines (faster, eager-or-lazy, better memory). pandasis fine if the project already uses it.
Auth, sessions, tokens
Auth, JWT, and password discipline lives in the security-review skill - read it before wiring up auth in a new service. The short version: bcrypt/scrypt/argon2id only, JWT signature verified before claims, authorization is per-request not per-session, sessions are httpOnly + sameSite cookies.
Errors & logging
- Raise/throw specific errors, catch specific errors. Generic
catch (e)/except Exceptiononly at the top of a worker loop, and re-raise after logging. - Use
pino(Node) orstructlog/logging(Python). Configure once, at app entry. Neverconsole.log/printin production code paths. - Log structured fields, not interpolated strings:
log.info({ caseId, ms }, 'processed')beatslog.info("processed " + caseId). - Don't log secrets, tokens, full request bodies, PII/PHI.
- Redact array in the logger config covers every API key the app handles.
Concurrency safety
- Module-level mutable state is a bug. If you need shared state, use a lock or a dedicated store.
- Idempotency keys on any external mutation (payment, email, queue publishes) - retries are inevitable; double-sends are not.
- Database transactions: keep them short; don't await network calls inside them.
Tooling baseline
Node.js / TypeScript
eslint+prettier(orbiomefor both). Config inpackage.jsonor.eslintrc.tsc --noEmitin CI on every PR.vitestorjestfor tests (seeqa-automationskill).
Python
rufffor lint + format (replacesflake8,isort, partlyblack). Config inpyproject.toml.mypy/pyrightfor type checking.pytestfor tests (seeqa-automationskill).pre-committo run lint + types on staged files.
Verification before declaring done
- Type-check passes (
tsc --noEmit,mypy,pyright). - Tests pass (
vitest run,pytest -x). - For services: hit the endpoint with
curl/httpieand verify the response shape matches the schema. Don't assume. - For migrations: run forward + rollback against a real DB copy before merging.