Claude Code for TypeScript & Node.js — Complete Guide
Claude Code handles TypeScript projects exceptionally well: it reads your tsconfig, follows existing patterns, runs tsc type checks after edits, and generates idiomatic TypeScript. This guide sets up the optimal configuration for Node.js, Next.js, Express, and general TypeScript development.
TypeScript CLAUDE.md Template
# Project: [Your App Name]
## Stack
- Node.js 22 LTS / TypeScript 5.x
- Framework: Next.js 15 (App Router) / Express 5 / Fastify 5
- Package manager: pnpm 9
- Testing: Jest + ts-jest / Vitest
- Linting: ESLint 9 + Prettier
## Commands
```bash
pnpm install # install dependencies
pnpm dev # start dev server
pnpm build # production build
pnpm test # run all tests
pnpm test:watch # watch mode
pnpm lint # eslint
pnpm typecheck # tsc --noEmit
```
## Conventions
- Strict TypeScript: no `any`, use `unknown` for external data
- Prefer `interface` for object shapes, `type` for unions/intersections
- Named exports only (no default exports except for Next.js pages/components)
- Use `import type` for type-only imports
- Async/await everywhere — no .then() chains
- Error handling: wrap external calls in try/catch, type catch as `unknown`
## Architecture
- src/app/ — Next.js App Router pages and layouts
- src/components/ — shared React components
- src/lib/ — utilities and shared logic
- src/types/ — shared TypeScript types
- __tests__/ — test files mirroring src/ structure
Permissions and Hooks
// .claude/settings.json
{
"allowedTools": ["Edit", "Write", "Read", "Bash", "Glob", "Grep"],
"permissions": {
"allow": [
"Bash(pnpm *)",
"Bash(npx *)",
"Bash(node *)",
"Bash(git *)"
]
},
"hooks": [
{
"event": "PostToolUse",
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "cd $PROJECT_ROOT && pnpm typecheck 2>&1 | tail -20"
}]
}
]
}
tsc needed.tsconfig Recommendations
// tsconfig.json — strict baseline
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM"],
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "src",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
Tell Claude Code your path aliases in CLAUDE.md so it uses @/lib/utils instead of ../../../lib/utils.
Automated Jest with Hooks
// package.json scripts
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
// jest.config.ts
export default {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: { '^@/(.*)$': '<rootDir>/src/$1' },
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts']
}
TDD hook for TypeScript
// .claude/settings.json — run jest after every test file edit
{
"hooks": [
{
"event": "PostToolUse",
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "cd $PROJECT_ROOT && pnpm test --passWithNoTests 2>&1 | tail -30"
}]
}
]
}
Next.js App Router Setup
# CLAUDE.md additions for Next.js
## Next.js
- Version: 15, App Router (not Pages Router)
- Server Components by default — add 'use client' only when needed
- Data fetching: fetch() in Server Components, SWR/TanStack Query in Client Components
- Routing: app/ directory, layouts in layout.tsx, pages in page.tsx
- Metadata: export const metadata object from page.tsx / layout.tsx
## Next.js conventions
- Co-locate components with their page: app/dashboard/components/
- Shared components: src/components/ui/ (shadcn/ui pattern)
- Server actions in app/actions/ directory
- API routes in app/api/ — use route.ts convention
Next.js prompt patterns
# Add a page
claude "add a /dashboard/settings page with:
- Server Component wrapper for fetching user settings
- Client Component form for updating display name and email
- Server Action to handle the form submit
- Proper loading.tsx and error.tsx
Follow the existing app/ patterns."
# Add an API route
claude "add POST /api/users route that:
- validates the request body with Zod (name: string, email: string)
- creates a user in the DB
- returns {id, name, email, createdAt}
- returns 400 on validation error, 409 on duplicate email
Add a test in __tests__/api/users.test.ts"
Express / Fastify Setup
# CLAUDE.md for Express
## Express
- Express 5, TypeScript, ESM modules
- Routes in src/routes/, middleware in src/middleware/
- Error handling: centralized error handler in src/middleware/error.ts
- Validation: Zod schemas in src/schemas/
- Database: Prisma ORM, client in src/lib/db.ts
## Express commands
npx ts-node src/index.ts # dev
npx tsx watch src/index.ts # dev with watch
pnpm build && node dist/index.js # production
Vitest Alternative
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [tsconfigPaths()],
test: {
environment: 'node',
globals: true,
coverage: { provider: 'v8', reporter: ['text', 'json'] }
}
})
# CLAUDE.md note for Vitest
## Testing
Using Vitest (not Jest). Run: pnpm test
Import from 'vitest': import { describe, it, expect, vi } from 'vitest'
Common TypeScript Workflows
| Task | Prompt |
|---|---|
| Add Zod validation | claude "add Zod schema for [interface]. Add parse() call at API boundary. Export the inferred type." |
| Write unit tests | claude "write Vitest tests for [file]. Mock external dependencies with vi.mock(). Cover happy path and error cases." |
| Refactor to generics | claude "refactor [function] to be generic. Preserve call sites. Keep the inferred type behaviour." |
| Add error handling | claude "add Result<T, E> error handling to [function]. Return Ok or Err, update callers." |
| Extract React hook | claude "extract the state logic from [component] into a custom use[Name] hook with the same interface." |
| Add Prisma model | claude "add [Model] to prisma/schema.prisma with fields [list]. Run prisma migrate dev. Generate typed queries." |
Frequently Asked Questions
How do I stop Claude from using JavaScript syntax in a TypeScript project?
Add to CLAUDE.md: This is a TypeScript project. Never generate plain .js files. All new files must have .ts or .tsx extension. Avoid // @ts-ignore comments — fix the type error instead. Claude reads existing files and matches the language, but an explicit instruction eliminates the occasional slip.
Does Claude Code work with Deno?
Yes. Note in CLAUDE.md: Runtime: Deno 2. Use Deno.readTextFile / Deno.writeTextFile instead of Node fs. Import from JSR or npm: with npm: prefix. Run: deno run --allow-net --allow-read src/main.ts. Claude adapts to Deno's import style and permissions model.
How do I get Claude to use my monorepo package imports?
Add your workspace packages to CLAUDE.md: Monorepo packages: @company/ui (packages/ui), @company/utils (packages/utils), @company/db (packages/db). Import from the package name, never from relative paths across packages. Claude will use the package name imports and understand the workspace structure.
Can Claude Code help with TypeScript migration from JavaScript?
claude "convert [file.js] to TypeScript. Add types to all functions, variables, and exports.
Use unknown for any data we don't control. Start with minimal types — we'll tighten later.
Do not change behaviour."
→ Testing Workflow — Jest, Vitest, and coverage
→ Hooks — automate typecheck, lint, and tests
→ Project Setup — CLAUDE.md and settings.json