TypeScript / JavaScript
Modern patterns, tooling, and best practices for the JavaScript/TypeScript ecosystem.
TypeScript Essentials
Strict Mode (Always Enable)
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}
Type Design Principles
- Model domain, not data shapes —
type UserId = string & { readonly brand: unique symbol }
- Discriminated unions over optional fields for state machines
- Zod/Arktype for runtime validation + static types (single source of truth)
- Avoid
any — Use unknown + type guards; enable noImplicitAny
Functional Patterns
// Result type for error handling
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
// Option type for nullable
type Option<T> = T | null;
// Pipeline / composition
const pipe = <T>(...fns: Array<(x: T) => T>) => (x: T) => fns.reduce((v, f) => f(v), x);
Modern JavaScript (ES2022+)
Key Features to Use
- Top-level await — Modules as async entry points
- Private fields —
#field for true encapsulation
- Logical assignment —
??=, ||=, &&=
- Array grouping —
Object.groupBy(), Map.groupBy()
- Temporal API —
Temporal.PlainDate, ZonedDateTime (stage 3)
Avoid
var — Use const / let
for...in on arrays — Use for...of or .forEach()
- Callback hell — Use
async/await or Promise combinators
- Mutating arguments — Treat params as immutable
| Category |
Recommended |
Alternatives |
| Runtime |
Node.js 20+ (LTS) |
Bun, Deno |
| Package Manager |
pnpm (fast, disk-efficient) |
npm v10+, yarn 4+ |
| Build |
Vite (dev), esbuild/tsup (lib) |
Webpack, Rollup, Turbopack |
| Type Checking |
tsc --noEmit + tsx for dev |
tsc watch mode |
| Linting |
ESLint (flat config) + @typescript-eslint |
Biome (fast, integrated) |
| Formatting |
Prettier (or Biome) |
dprint |
| Testing |
Vitest (unit), Playwright (E2E) |
Jest, Cypress |
| Type Generation |
Zod + zod-to-ts, or Arktype |
io-ts, valibot |
Recommended ESLint Flat Config
// eslint.config.js
export default [
{ ignores: ['dist/', 'node_modules/'] },
...eslint.configs.recommended,
...eslintTypescript.configs.recommended,
...eslintPrettier.configs.recommended,
{ rules: { '@typescript-eslint/no-explicit-any': 'error' } }
];
Project Structure
src/
├── domain/ # Pure business logic (no framework deps)
│ ├── entities/
│ ├── value-objects/
│ ├── services/
│ └── repositories/ # Interfaces only
├── application/ # Use cases, orchestration
│ ├── commands/
│ ├── queries/
│ └── dtos/
├── infrastructure/ # Framework implementations
│ ├── persistence/ # Repository impls (Prisma, Drizzle, TypeORM)
│ ├── http/ # Controllers, routes (Fastify, Hono, Express)
│ ├── messaging/ # Event publishers/consumers
│ └── config/ # Env validation (Zod)
├── presentation/ # API layer (tRPC, GraphQL, REST)
└── main.ts # Composition root (DI wiring)
Database Access
ORM vs Query Builder
| Approach |
When |
| Prisma |
Full ORM; type-safe; migrations; good for standard CRUD |
| Drizzle |
Lightweight; SQL-like; better for complex queries |
| Kysely |
Type-safe query builder; no migrations; full control |
| Raw SQL + pg |
Maximum control; complex analytics; performance-critical |
Repository Pattern (Domain-Driven)
// domain/repositories/user-repository.ts
export interface UserRepository {
findById(id: UserId): Promise<Option<User>>;
save(user: User): Promise<void>;
}
// infrastructure/persistence/prisma-user-repository.ts
export class PrismaUserRepository implements UserRepository {
constructor(private prisma: PrismaClient) {}
async findById(id: UserId) { /* ... */ }
async save(user: User) { /* ... */ }
}
API Layer
tRPC (End-to-End Typesafe)
// Server
export const appRouter = router({
user: router({
byId: protectedProcedure
.input(z.object({ id: UserIdSchema }))
.query(({ ctx, input }) => ctx.userRepo.findById(input.id)),
}),
});
// Client — fully typed
const user = await trpc.user.byId({ id: '123' });
REST with OpenAPI
- Hono +
@hono/zod-openapi for runtime validation + OpenAPI generation
- Fastify +
fastify-type-provider-zod for type-safe routes
State Management (Frontend)
| Scale |
Solution |
| Local component |
useState, useReducer |
| Cross-component |
Zustand, Jotai, Valtio (atomic) |
| Server state |
TanStack Query (React Query) — caching, deduping, invalidation |
| Forms |
React Hook Forms + Zod resolver |
| URL state |
nuqs, search-params |
Concurrency & Async Patterns
Structured Concurrency
// Run with timeout + cancellation
const result = await Promise.race([
doWork(),
new Promise((_, reject) => setTimeout(() => reject(new TimeoutError()), 5000))
]);
// Parallel with concurrency limit (p-map, bottleneck)
import pMap from 'p-map';
await pMap(items, processItem, { concurrency: 5 });
Worker Threads (CPU-bound)
// main.ts
const worker = new Worker('./worker.ts', { workerData: input });
worker.on('message', handleResult);
// worker.ts
parentPort!.on('message', (data) => {
const result = heavyComputation(data);
parentPort!.postMessage(result);
});
Testing Patterns
Unit (Vitest)
import { describe, it, expect, vi } from 'vitest';
describe('PricingService', () => {
it('applies discount for premium users', () => {
const repo = { findUser: vi.fn().mockResolvedValue({ tier: 'premium' }) };
const service = new PricingService(repo);
const price = await service.calculate(userId, basePrice);
expect(price).toBe(basePrice * 0.9);
});
});
Contract Tests (Pact)
// Consumer test
await provider.addInteraction({
state: 'user exists',
uponReceiving: 'request for user',
withRequest: { method: 'GET', path: '/users/123' },
willRespondWith: { status: 200, body: like({ id: '123', name: 'John' }) },
});
- Bundle analysis —
vite-bundle-analyzer, webpack-bundle-analyzer
- Tree shaking — ESM only; sideEffects: false in package.json
- Code splitting — Dynamic
import(); route-based chunks
- Streaming SSR — React 18
renderToPipeableStream, Astro, Next.js
- Edge runtime — Cloudflare Workers, Vercel Edge, Deno Deploy
Security
- Dependency scanning —
pnpm audit, npm audit, Snyk, Socket
- CSP headers — Nonce-based for inline scripts
- Sanitization — DOMPurify for user HTML; Zod for all inputs
- Secrets — Never in code; use env vars + secret manager
- Language Agnostic Principles — Universal patterns
- Python — Python patterns
- Go — Go patterns
- Design Patterns — Patterns in TS/JS
- Architectural Styles — Hexagonal in TS
- Test Pyramid — Testing strategy
- CI CD Best Practices — Pipeline for JS/TS
Sources: TypeScript Handbook, Effective TypeScript (Dan Vanderkam), Matt Pocock (Total TypeScript), React Query Docs, TanStack, Vite, Biome, Zod, tRPC, Hono, Fastify, Node.js Best Practices