Claude Code's default behavior is already good for TypeScript projects. With a few optimizations — a well-written CLAUDE.md, some custom hooks, and targeted skills — it becomes exceptional.
This guide covers the setup I use for production TypeScript backends.
1. CLAUDE.md for TypeScript Projects
The single most impactful thing you can do is write a solid CLAUDE.md:
# Project: my-api
## Stack
- Node.js 20 LTS + TypeScript 5.x
- Express 4 + Prisma ORM
- PostgreSQL 16
## Commands
- Test: `npm test` (Vitest)
- Lint: `npm run lint` (ESLint + Prettier)
- Build: `npm run build`
- DB migration: `npx prisma migrate dev`
## Architecture
- Layers: Router → Controller → Service → Repository
- No DB calls in Controllers
- No HTTP-specific code in Services
- Dependency injection via constructor parameters
## Code Rules
- Strict TypeScript (`strict: true` in tsconfig)
- No `any` type without explicit comment justification
- No `console.log` in production code (use `logger.ts`)
- All async functions must handle errors (try/catch or `.catch()`)
- Magic numbers must be named constants
## Security
- All SQL via Prisma ORM (no raw string queries)
- All external input validated with Zod
- No PII in logs
- Secrets only via `process.env`, never hardcoded
## Testing
- Test files: `src/**/*.test.ts`
- Coverage target: 80%
- Mock external services, never hit production APIs in tests
2. TypeScript-Specific Hooks
Auto-format on write
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "python .claude/hooks/ts_format.py"
}]
}
]
}
}
# .claude/hooks/ts_format.py
import json, subprocess, sys
from pathlib import Path
data = json.load(sys.stdin)
file_path = data.get("tool_input", {}).get("file_path", "")
if not file_path:
sys.exit(0)
p = Path(file_path)
if p.suffix in (".ts", ".tsx", ".js", ".jsx"):
subprocess.run(["npx", "prettier", "--write", str(p)], capture_output=True)
subprocess.run(["npx", "eslint", "--fix", str(p)], capture_output=True)
sys.exit(0)
Block console.log in source files
# .claude/hooks/no_console_log.py
import json, sys
from pathlib import Path
data = json.load(sys.stdin)
file_path = data.get("tool_input", {}).get("file_path", "")
content = data.get("tool_input", {}).get("content", "")
if not file_path or not file_path.endswith((".ts", ".js")):
sys.exit(0)
if "src/" in file_path and "test" not in file_path:
if "console.log(" in content and "// allow-console" not in content:
print("[BLOCKED] console.log in source file. Use logger.ts instead.", file=sys.stderr)
sys.exit(2)
sys.exit(0)
3. Prisma-Aware Development
Claude Code handles Prisma well when you give it context. Add your schema path to CLAUDE.md:
## Database
- Schema: `prisma/schema.prisma`
- Client: `src/lib/db.ts` (singleton)
- Run migrations: `npx prisma migrate dev --name <description>`
Then instructions like "add a refreshToken field to the User model" work correctly.
4. Testing with Vitest
Claude Code generates good Vitest tests when you specify:
## Testing Details
- Framework: Vitest
- Mocking: `vi.mock()` for modules, `vi.spyOn()` for methods
- Test pattern: AAA (Arrange, Act, Assert)
- No shared state between tests (`beforeEach` for setup)
Run /test-gen src/services/user.ts and get tests that match your project's style.
5. Type Safety Patterns
Tell Claude Code to enforce specific TypeScript patterns:
## TypeScript Patterns
- Use `satisfies` instead of `as` for type assertions
- Prefer `readonly` arrays for function parameters
- Error types: use discriminated unions, not `Error` subclasses
- API responses: use `Result<T, E>` pattern, never throw from services
## Zod Schemas
- All API request bodies validated with Zod
- Schema files in `src/schemas/`
- Infer types from schemas: `type UserCreate = z.infer<typeof UserCreateSchema>`
6. Common TypeScript Mistakes Claude Code Catches
With a well-written CLAUDE.md and /code-review, Claude Code reliably flags:
| Issue | Example |
|---|---|
| Floating promises |
fetchUser() without await
|
| Type escape | const x = value as any |
| Null assertion overuse | user!.email |
| Missing error handling |
async function without try/catch |
Implicit any from external APIs |
Missing type assertions on JSON.parse()
|
Setup Checklist
□ CLAUDE.md with stack, commands, rules, architecture
□ tsconfig.json with strict: true
□ ESLint + Prettier configured
□ Hooks for auto-format on write
□ Custom skill: /code-review for PR review
□ Custom skill: /test-gen for test coverage
If you want pre-built skills instead of building from scratch, Code Review Pack (¥980) includes /code-review, /refactor-suggest, and /test-gen — all with TypeScript support.
Myouga (@myougatheaxo) — Security-focused Claude Code engineer.
Top comments (0)