typescript-best-practices▌
0xbigboss/claude-code · updated Apr 8, 2026
TypeScript patterns for type-first development, making illegal states unrepresentable, and exhaustive handling.
- ›Use discriminated unions, branded types, and const assertions to encode business rules in the type system and prevent invalid states at compile time
- ›Validate at system boundaries with Zod schemas as single source of truth; infer TypeScript types automatically to keep types and validation in sync
- ›Enforce exhaustive handling with never checks in switch statements and default
TypeScript Best Practices
Follows type-first, functional, and error handling patterns from CLAUDE.md. This skill covers language-specific idioms only.
Pair with React Best Practices
When working with React components (.tsx, .jsx files or @react imports), always load react-best-practices alongside this skill. This skill covers TypeScript fundamentals; React-specific patterns (effects, hooks, refs, component design) are in the dedicated React skill.
Make Illegal States Unrepresentable
Use the type system to prevent invalid states at compile time.
Discriminated unions for mutually exclusive states:
// Good: only valid combinations possible
type RequestState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
// Bad: allows invalid combinations like { loading: true, error: Error }
type RequestState<T> = {
loading: boolean;
data?: T;
error?: Error;
};
Branded types for domain primitives:
type UserId = string & { readonly __brand: 'UserId' };
type OrderId = string & { readonly __brand: 'OrderId' };
// Compiler prevents passing OrderId where UserId expected
function getUser(id: UserId): Promise<User> { /* ... */ }
Const assertions for literal unions:
const ROLES = ['admin', 'user', 'guest'] as const;
type Role = typeof ROLES[number]; // 'admin' | 'user' | 'guest'
// Array and type stay in sync automatically
function isValidRole(role: string): role is Role {
return ROLES.includes(role as Role);
}
Exhaustive switch with never check:
type Status = "active" | "inactive";
function processStatus(status: Status): string {
switch (status) {
case "active":
return "processing";
case "inactive":
return "skipped";
default: {
const _exhaustive: never = status;
throw new Error(`unhandled status: ${_exhaustive}`);
}
}
}
Runtime Validation with Zod
- Define schemas as single source of truth; infer TypeScript types with
z.infer<>. Avoid duplicating types and schemas. - Use
safeParsefor user input where failure is expected; useparseat trust boundaries where invalid data is a bug. - Compose schemas with
.extend(),.pick(),.omit(),.merge()for DRY definitions. - Add
.transform()for data normalization at parse time (trim strings, parse dates).
import { z } from "zod";
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string().min(1),
createdAt: z.string().transform((s) => new Date(s)),
});
type User = z.infer<typeof UserSchema>;
// Strict parsing at trust boundaries — throws if API contract violated
export async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`fetch user ${id} failed: ${response.status}`);
}
return UserSchema.parse(await response.json());
}
// Caller handles both success and error from user input
const result = UserSchema.safeParse(formData);
if (!result.success) {
setErrors(result.error.flatten().fieldErrors);
return;
}
Optional: type-fest
For advanced type utilities beyond TypeScript builtins, consider type-fest:
Opaque<T, Token>- cleaner branded types than manual& { __brand }patternPartialDeep<T>- recursive partial for nested objectsReadonlyDeep<T>- recursive readonly for immutable dataSetRequired<T, K>/SetOptional<T, K>- targeted field modificationsSimplify<T>- flatten complex intersection types in IDE tooltips
import type { Opaque, PartialDeep } from 'type-fest';
type UserId = Opaque<string, 'UserId'>;
type UserPatch = PartialDeep<User>;
Discussion
Product Hunt–style comments (not star reviews)- No comments yet — start the thread.
Ratings
4.6★★★★★28 reviews- ★★★★★Hana Sharma· Dec 28, 2024
Solid pick for teams standardizing on skills: typescript-best-practices is focused, and the summary matches what you get after install.
- ★★★★★Ganesh Mohane· Dec 24, 2024
typescript-best-practices reduced setup friction for our internal harness; good balance of opinion and flexibility.
- ★★★★★Aditi Johnson· Dec 20, 2024
typescript-best-practices is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.
- ★★★★★Omar Bansal· Dec 12, 2024
Registry listing for typescript-best-practices matched our evaluation — installs cleanly and behaves as described in the markdown.
- ★★★★★Hiroshi Jackson· Nov 19, 2024
We added typescript-best-practices from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.
- ★★★★★Sakshi Patil· Nov 15, 2024
I recommend typescript-best-practices for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.
- ★★★★★Naina Zhang· Nov 3, 2024
Useful defaults in typescript-best-practices — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
- ★★★★★Soo Abebe· Oct 22, 2024
I recommend typescript-best-practices for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.
- ★★★★★Soo Rao· Oct 10, 2024
typescript-best-practices fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.
- ★★★★★Chaitanya Patil· Oct 6, 2024
Useful defaults in typescript-best-practices — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
showing 1-10 of 28