🎯

typescript-best-practices

🎯Skill

from 0xbigboss/claude-code

VibeIndex|
What it does

Enforces TypeScript best practices by guiding developers to create robust, type-safe code through compile-time validation, discriminated unions, and explicit type definitions.

typescript-best-practices

Installation

Install skill:
npx skills add https://github.com/0xbigboss/claude-code --skill typescript-best-practices
34
Last UpdatedJan 26, 2026

Skill Details

SKILL.md

Provides TypeScript patterns for type-first development, making illegal states unrepresentable, exhaustive handling, and runtime validation. Must use when reading or writing TypeScript/JavaScript files.

Overview

# TypeScript Best Practices

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.

Type-First Development

Types define the contract before implementation. Follow this workflow:

  1. Define the data model - types, interfaces, and schemas first
  2. Define function signatures - input/output types before logic
  3. Implement to satisfy types - let the compiler guide completeness
  4. Validate at boundaries - runtime checks where data enters the system

Make Illegal States Unrepresentable

Use the type system to prevent invalid states at compile time.

Discriminated unions for mutually exclusive states:

```ts

// Good: only valid combinations possible

type RequestState =

| { status: 'idle' }

| { status: 'loading' }

| { status: 'success'; data: T }

| { status: 'error'; error: Error };

// Bad: allows invalid combinations like { loading: true, error: Error }

type RequestState = {

loading: boolean;

data?: T;

error?: Error;

};

```

Branded types for domain primitives:

```ts

type UserId = string & { readonly __brand: 'UserId' };

type OrderId = string & { readonly __brand: 'OrderId' };

// Compiler prevents passing OrderId where UserId expected

function getUser(id: UserId): Promise { / ... / }

function createUserId(id: string): UserId {

return id as UserId;

}

```

Const assertions for literal unions:

```ts

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);

}

```

Required vs optional fields - be explicit:

```ts

// Creation: some fields required

type CreateUser = {

email: string;

name: string;

};

// Update: all fields optional

type UpdateUser = Partial;

// Database row: all fields present

type User = CreateUser & {

id: UserId;

createdAt: Date;

};

```

Module Structure

Prefer smaller, focused files: one component, hook, or utility per file. Split when a file handles multiple concerns or exceeds ~200 lines. Colocate tests with implementation (foo.test.ts alongside foo.ts). Group related files by feature rather than by type.

Functional Patterns

  • Prefer const over let; use readonly and Readonly for immutable data.
  • Use array.map/filter/reduce over for loops; chain transformations in pipelines.
  • Write pure functions for business logic; isolate side effects in dedicated modules.
  • Avoid mutating function parameters; return new objects/arrays instead.

Instructions

  • Enable strict mode; model data with interfaces and types. Strong typing catches bugs at compile time.
  • Every code path returns a value or throws; use exhaustive switch with never checks in default. Unhandled cases become compile errors.
  • Propagate errors with context; catching requires re-throwing or returning a meaningful result. Hidden failures delay debugging.
  • Handle edge cases explicitly: empty arrays, null/undefined inputs, boundary values. Defensive checks prevent runtime surprises.
  • Use await for async calls; wrap external calls with contextual error messages. Unhandled rejections crash Node processes.
  • Add or update focused tests when changing logic; test behavior, not implementation details.

Examples

Explicit failure for unimplemented logic:

```ts

export function buildWidget(widgetType: string): never {

throw new Error(buildWidget not implemented for type: ${widgetType});

}

```

Exhaustive switch with never check:

```ts

type Status = "active" | "inactive";

export 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});

}

}

}

```

Wrap external calls with context:

```ts

export async function fetchWidget(id: string): Promise {

const response = await fetch(/api/widgets/${id});

if (!response.ok) {

throw new Error(fetch widget ${id} failed: ${response.status});

}

return response.json();

}

```

Debug logging with namespaced logger:

```ts

import debug from "debug";

const log = debug("myapp:widgets");

export function createWidget(name: string): Widget {

log("creating widget: %s", name);

const widget = { id: crypto.randomUUID(), name };

log("created widget: %s", widget.id);

return widget;

}

```

Runtime Validation with Zod

  • Define schemas as single source of truth; infer TypeScript types with z.infer<>. Avoid duplicating types and schemas.
  • Use safeParse for user input where failure is expected; use parse at 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).
  • Include descriptive error messages; use .refine() for custom validation logic.

Examples

Schema as source of truth with type inference:

```ts

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;

```

Return parse results to callers (never swallow errors):

```ts

import { z, SafeParseReturnType } from "zod";

export function parseUserInput(raw: unknown): SafeParseReturnType {

return UserSchema.safeParse(raw);

}

// Caller handles both success and error:

const result = parseUserInput(formData);

if (!result.success) {

setErrors(result.error.flatten().fieldErrors);

return;

}

await submitUser(result.data);

```

Strict parsing at trust boundaries:

```ts

export async function fetchUser(id: string): Promise {

const response = await fetch(/api/users/${id});

if (!response.ok) {

throw new Error(fetch user ${id} failed: ${response.status});

}

const data = await response.json();

return UserSchema.parse(data); // throws if API contract violated

}

```

Schema composition:

```ts

const CreateUserSchema = UserSchema.omit({ id: true, createdAt: true });

const UpdateUserSchema = CreateUserSchema.partial();

const UserWithPostsSchema = UserSchema.extend({

posts: z.array(PostSchema),

});

```

Configuration

  • Load config from environment variables at startup; validate with Zod before use. Invalid config should crash immediately.
  • Define a typed config object as single source of truth; avoid accessing process.env throughout the codebase.
  • Use sensible defaults for development; require explicit values for production secrets.

Examples

Typed config with Zod validation:

```ts

import { z } from "zod";

const ConfigSchema = z.object({

PORT: z.coerce.number().default(3000),

DATABASE_URL: z.string().url(),

API_KEY: z.string().min(1),

NODE_ENV: z.enum(["development", "production", "test"]).default("development"),

});

export const config = ConfigSchema.parse(process.env);

```

Access config values (not process.env directly):

```ts

import { config } from "./config";

const server = app.listen(config.PORT);

const db = connect(config.DATABASE_URL);

```

Optional: type-fest

For advanced type utilities beyond TypeScript builtins, consider [type-fest](https://github.com/sindresorhus/type-fest):

  • Opaque - cleaner branded types than manual & { __brand } pattern
  • PartialDeep - recursive partial for nested objects
  • ReadonlyDeep - recursive readonly for immutable data
  • LiteralUnion - literals with autocomplete + string fallback
  • SetRequired / SetOptional - targeted field modifications
  • Simplify - flatten complex intersection types in IDE tooltips

```ts

import type { Opaque, PartialDeep, SetRequired } from 'type-fest';

// Branded type (cleaner than manual approach)

type UserId = Opaque;

// Deep partial for patch operations

type UserPatch = PartialDeep;

// Make specific fields required

type UserWithEmail = SetRequired, 'email'>;

```

More from this repository10

🎯
react-best-practices🎯Skill

Guides React developers in writing clean, efficient components by providing best practices for hooks, effects, and component design.

🎯
python-best-practices🎯Skill

Guides Python developers in implementing type-first development with robust type checking, dataclasses, discriminated unions, and domain-specific type primitives.

🎯
openai-image-gen🎯Skill

Generates images using OpenAI's image generation API within the Claude Code environment.

🎯
tamagui-best-practices🎯Skill

Optimizes Tamagui configurations and styling patterns for high-performance, cross-platform React Native and web applications with v4 compiler settings.

🎯
playwright-best-practices🎯Skill

Provides Playwright test best practices for creating resilient, maintainable tests using user-facing locators, Page Object Models, and robust testing patterns.

🎯
go-best-practices🎯Skill

Enforces type-first Go development with custom types, interfaces, and patterns to create robust, self-documenting code with strong compile-time guarantees.

🎯
web-fetch🎯Skill

Fetches web content and converts HTML to clean markdown using intelligent content extraction and CSS selectors.

🎯
zig-best-practices🎯Skill

Enforces type-first development in Zig by leveraging tagged unions, explicit error sets, comptime validation, and type safety techniques.

🎯
gen-env🎯Skill

Generates environment configuration files or environment variables for development projects, ensuring consistent and reproducible setup across different environments.

🎯
zig-docs🎯Skill

Retrieves Zig language documentation from Codeberg sources, standard library files, and online references via CLI commands.