typescript-guide

TypeScript/JavaScript Guide

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "typescript-guide" with this command: npx skills add ar4mirez/samuel/ar4mirez-samuel-typescript-guide

TypeScript/JavaScript Guide

Applies to: TypeScript 5+, Node.js 20+, ES2022+, React, Server-Side JS

Core Principles

  • Strict TypeScript: Enable all strict flags; treat type errors as build failures

  • Immutability by Default: Use const , readonly , as const , and spread operators; mutate only when profiling demands it

  • Explicit Types at Boundaries: All function signatures, API responses, and public interfaces must have explicit type annotations; infer internally

  • Functional Patterns: Prefer pure functions, map/filter/reduce, and composition over classes and mutation

  • Zero any : Use unknown for truly unknown data, Zod/io-ts for runtime narrowing; every any requires a code-review comment explaining why

Guardrails

TypeScript Configuration

  • Enable "strict": true (this activates strictNullChecks , noImplicitAny , strictFunctionTypes , etc.)

  • Enable "noUncheckedIndexedAccess": true (arrays and records return T | undefined )

  • Enable "noImplicitReturns": true and "noFallthroughCasesInSwitch": true

  • Set "target": "ES2022" , "module": "ESNext" , "moduleResolution": "bundler"

  • Never suppress errors with @ts-ignore ; use @ts-expect-error with a justification comment

Code Style

  • const over let ; never use var

  • Nullish coalescing (?? ) over logical OR (|| ) for defaults (avoids falsy-value bugs with 0 , "" , false )

  • Optional chaining (?. ) over nested null checks

  • Template literals over string concatenation

  • Destructuring at call sites: const { id, name } = user

  • Barrel exports (index.ts ) only at package boundaries, not within internal modules (causes circular imports and tree-shaking failures)

  • Enums: prefer as const objects over TypeScript enum (enums produce runtime code and have surprising behaviors with reverse mappings)

// Prefer this const Status = { Active: "active", Inactive: "inactive", } as const; type Status = (typeof Status)[keyof typeof Status];

// Over this enum Status { Active = "active", Inactive = "inactive", }

Error Handling

  • Every catch block must type-narrow the error before accessing properties

  • Never throw primitive values (strings, numbers); always throw Error subclasses

  • Use the cause option for error chaining: new AppError("msg", { cause: original })

  • Never swallow errors with empty catch blocks

  • Async functions must have error handling at the call site or a global boundary handler

Async/Await

  • Always await or return promises; never create fire-and-forget promises without explicit void annotation

  • Use Promise.all for independent concurrent operations, not sequential await in a loop

  • Set timeouts on all external calls (fetch, database, third-party APIs) using AbortController

  • Use Promise.allSettled when partial failure is acceptable

  • Never mix .then() chains with await in the same function

// Bad: sequential when order does not matter const users = await fetchUsers(); const posts = await fetchPosts();

// Good: concurrent const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()]);

// Good: timeout with AbortController async function fetchWithTimeout(url: string, ms: number): Promise<Response> { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), ms); try { return await fetch(url, { signal: controller.signal }); } finally { clearTimeout(timeout); } }

Module System

  • Use ES modules (import /export ) exclusively; no require() in TypeScript

  • One export per concept: prefer named exports over default exports (improves refactoring, grep-ability)

  • Side-effect imports (import "./setup" ) must be documented with a comment explaining why

  • Re-export from index.ts only at package/feature boundaries

  • Keep import order: stdlib/node builtins, external packages, internal modules, relative paths (enforce with ESLint import/order )

Project Structure

myproject/ ├── src/ │ ├── index.ts # Application entry point │ ├── config/ # Environment, feature flags │ │ └── env.ts # Validated env vars (Zod schema) │ ├── domain/ # Business logic (no I/O) │ │ ├── user.ts │ │ └── order.ts │ ├── services/ # Application services (orchestrate domain + I/O) │ ├── repositories/ # Data access (database, external APIs) │ ├── routes/ # HTTP handlers / controllers │ ├── middleware/ # Express/Koa/Hono middleware │ ├── utils/ # Pure utility functions │ └── types/ # Shared type definitions ├── tests/ │ ├── unit/ # Mirror src/ structure │ ├── integration/ # API and database tests │ └── fixtures/ # Shared test data ├── tsconfig.json ├── package.json ├── .eslintrc.cjs ├── .prettierrc └── vitest.config.ts

  • Domain logic in domain/ must have zero I/O dependencies (pure functions, easy to test)

  • No business logic in route handlers; delegate to services

  • Shared types in types/ ; co-locate component-specific types with their module

Error Handling Patterns

Custom Application Errors

class AppError extends Error { constructor( message: string, public readonly code: string, public readonly statusCode: number = 500, options?: ErrorOptions ) { super(message, options); this.name = "AppError"; } }

class NotFoundError extends AppError { constructor(resource: string, id: string) { super(${resource} with id ${id} not found, "NOT_FOUND", 404); this.name = "NotFoundError"; } }

Result Pattern (for Expected Failures)

type Result<T, E = AppError> = | { ok: true; value: T } | { ok: false; error: E };

function parseAge(input: string): Result<number> { const age = Number(input); if (Number.isNaN(age) || age < 0 || age > 150) { return { ok: false, error: new AppError("Invalid age", "VALIDATION") }; } return { ok: true, value: age }; }

// Usage: caller must check before accessing value const result = parseAge(input); if (!result.ok) { return res.status(400).json({ error: result.error.message }); } console.log(result.value); // Type-safe: number

Typed Catch Blocks

try { await externalService.call(); } catch (error: unknown) { if (error instanceof AppError) { logger.warn(error.message, { code: error.code }); return res.status(error.statusCode).json({ error: error.message }); } // Unknown error: wrap and re-throw throw new AppError("Unexpected failure", "INTERNAL", 500, { cause: error }); }

Testing

Standards

  • Test runner: Vitest (preferred) or Jest

  • Test files: co-located as *.test.ts or under tests/ mirroring src/

  • Naming: describe("ModuleName") with it("should [expected behavior] when [condition]")

  • Use beforeEach for setup; avoid beforeAll for mutable state

  • Coverage target: >80% for business logic, >60% overall

  • No any in test files; test helpers must be typed

  • Mock at boundaries (HTTP, database, file system), not internal functions

Table-Driven Tests

import { describe, it, expect } from "vitest"; import { validateEmail } from "./validate";

describe("validateEmail", () => { const cases = [ { input: "user@example.com", expected: true, desc: "valid email" }, { input: "no-at-sign", expected: false, desc: "missing @ symbol" }, { input: "", expected: false, desc: "empty string" }, { input: "a@b.c", expected: true, desc: "minimal valid email" }, ] as const;

it.each(cases)("returns $expected for $desc", ({ input, expected }) => { expect(validateEmail(input)).toBe(expected); }); });

Mocking External Dependencies

import { describe, it, expect, vi, beforeEach } from "vitest"; import { UserService } from "./user.service"; import type { UserRepository } from "./user.repository";

describe("UserService", () => { let service: UserService; let repo: UserRepository;

beforeEach(() => { repo = { findById: vi.fn(), save: vi.fn(), } as unknown as UserRepository; service = new UserService(repo); });

it("should throw NotFoundError when user does not exist", async () => { vi.mocked(repo.findById).mockResolvedValue(null);

await expect(service.getUser("abc")).rejects.toThrow("not found");
expect(repo.findById).toHaveBeenCalledWith("abc");

}); });

Tooling

Essential Commands

tsc --noEmit # Type check (no output) eslint . --ext .ts,.tsx # Lint prettier --check . # Format check prettier --write . # Format fix vitest # Run tests (watch mode) vitest run # Run tests (CI mode) vitest run --coverage # With coverage

ESLint Configuration (Flat Config)

// eslint.config.mjs import tseslint from "typescript-eslint"; import importPlugin from "eslint-plugin-import";

export default tseslint.config( ...tseslint.configs.strictTypeChecked, { rules: { "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/explicit-function-return-type": ["warn", { allowExpressions: true, }], "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-misused-promises": "error", "@typescript-eslint/strict-boolean-expressions": "error", "import/order": ["error", { groups: ["builtin", "external", "internal", "parent", "sibling"], "newlines-between": "always", }], }, } );

Strict tsconfig.json

{ "compilerOptions": { "strict": true, "target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", "esModuleInterop": true, "skipLibCheck": true, "noUncheckedIndexedAccess": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "forceConsistentCasingInFileNames": true, "exactOptionalPropertyTypes": true, "noPropertyAccessFromIndexSignature": true, "isolatedModules": true, "verbatimModuleSyntax": true } }

Input Validation (Zod)

import { z } from "zod";

const CreateUserSchema = z.object({ email: z.string().email(), name: z.string().min(1).max(100), age: z.number().int().min(0).max(150), role: z.enum(["admin", "user", "viewer"]), });

type CreateUserInput = z.infer<typeof CreateUserSchema>;

// At API boundary function handleCreateUser(rawBody: unknown): CreateUserInput { return CreateUserSchema.parse(rawBody); // throws ZodError on failure }

Advanced Topics

For detailed patterns and examples, see:

  • references/patterns.md -- Advanced type patterns, Zod validation, async orchestration, module organization, React + TypeScript idioms

  • references/pitfalls.md -- Common TypeScript footguns, type narrowing mistakes, async traps

  • references/security.md -- XSS prevention, CSRF, content security policy, dependency auditing

External References

  • TypeScript Handbook

  • TypeScript Strict Mode Explained

  • typescript-eslint

  • Zod Documentation

  • Vitest Documentation

  • Effective TypeScript (Book)

  • Total TypeScript (Matt Pocock)

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

code-review

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

python-guide

No summary provided by upstream source.

Repository SourceNeeds Review
General

actix-web

No summary provided by upstream source.

Repository SourceNeeds Review