惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

雷峰网
雷峰网
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 三生石上(FineUI控件)
博客园 - 聂微东
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Hugging Face - Blog
Hugging Face - Blog
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - Franky
MyScale Blog
MyScale Blog
A
About on SuperTechFans
博客园_首页
B
Blog RSS Feed
Martin Fowler
Martin Fowler
大猫的无限游戏
大猫的无限游戏
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Vercel News
Vercel News
C
Check Point Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园 - 【当耐特】
M
MIT News - Artificial intelligence
宝玉的分享
宝玉的分享
T
Tailwind CSS Blog
I
InfoQ
罗磊的独立博客

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
The Art of the Side Project: How to Ship Fast and Validat...
Alex Chen · 2026-05-16 · via DEV Community

Alex Chen

7 TypeScript Patterns I Use in Every Project

These aren't groundbreaking. They're the boring patterns that prevent bugs and save hours of debugging.

1. The Result Type (No More try/catch Hell)

// Instead of throwing exceptions everywhere:
function divide(a: number, b: number): number {
  if (b === 0) throw new Error("Division by zero");
  return a / b;
}

// Use a Result type:
type Result<T, E = Error> = 
  | { ok: true; value: T }
  | { ok: false; error: E };

function divide(a: number, b: number): Result<number, string> {
  if (b === 0) return { ok: false, error: "Division by zero" };
  return { ok: true, value: a / b };
}

// Usage — the compiler FORCES you to handle errors:
const result = divide(10, 0);
if (result.ok) {
  console.log(result.value); // TypeScript knows this is number
} else {
  console.error(result.error); // TypeScript knows this is string
}

Enter fullscreen mode Exit fullscreen mode

Why this matters: No more "undefined is not a function" because you forgot a try/catch. The type system enforces error handling.

2. Branded Types (Prevent Mixing Up IDs)

// The problem:
function getUser(id: string) { /* ... */ }
function getPost(id: string) { /* ... */ }

// Easy to mix up:
getUser(postId);  // TypeScript allows this! But it's wrong!

// The fix — branded types:
type UserId = string & { readonly __brand: 'UserId' };
type PostId = string & { readonly __brand: 'PostId' };

function createUser(id: string): UserId {
  return id as UserId;
}

function getUser(id: UserId) { /* ... */ }
function getPost(id: PostId) { /* ... */ }

const userId = createUser('abc');
getUser(userId);  // ✅ Works
// getPost(userId); // ❌ TypeScript error! Type 'UserId' is not assignable to 'PostId'

Enter fullscreen mode Exit fullscreen mode

When to use: Anytime you have multiple IDs that look the same (userId, orderId, sessionId) but mean different things.

3. The Builder Pattern for Complex Objects

interface Config {
  host: string;
  port: number;
  database: string;
  ssl: boolean;
  poolSize: number;
  timeout: number;
}

class ConfigBuilder {
  private config: Partial<Config> = {};

  static create() {
    return new ConfigBuilder();
  }

  withHost(host: string) {
    this.config.host = host;
    return this; // Chain!
  }

  withPort(port: number) {
    this.config.port = port;
    return this;
  }

  withDatabase(database: string) {
    this.config.database = database;
    return this;
  }

  withSSL(ssl = true) {
    this.config.ssl = ssl;
    return this;
  }

  withPoolSize(size: number) {
    this.config.poolSize = size;
    return this;
  }

  build(): Config {
    if (!this.config.host) throw new Error('host is required');
    if (!this.config.database) throw new Error('database is required');

    return {
      host: this.config.host,
      port: this.config.port ?? 5432,
      database: this.config.database,
      ssl: this.config.ssl ?? false,
      poolSize: this.config.poolSize ?? 10,
      timeout: this.config.timeout ?? 5000,
    };
  }
}

// Usage — readable, flexible, with defaults:
const config = ConfigBuilder.create()
  .withHost('localhost')
  .withDatabase('myapp')
  .withSSL()
  .withPoolSize(20)
  .build();

Enter fullscreen mode Exit fullscreen mode

4. Strict Event Emitter Types

// Instead of the loose EventEmitter from Node:
import { EventEmitter } from 'events';

const emitter = new EventEmitter();
emitter.on('data', (payload: unknown) => { /* type safety? nah */ });
emitter.emit('data', 123);       // No type check
emitter.emit('typo', 'anything'); // No type check

// Strict typed version:
type EventMap = {
  'user:login': { userId: string; timestamp: Date };
  'user:logout': { userId: string };
  'error': { message: string; code: number };
  'data:update': { key: string; value: unknown };
};

class TypedEmitter<Events extends Record<string, unknown>> {
  private emitter = new EventEmitter();

  on<K extends keyof Events & string>(
    event: K,
    handler: (payload: Events[K]) => void
  ) {
    this.emitter.on(event, handler as (...args: unknown[]) => void);
    return () => this.emitter.off(event, handler as (...args: unknown[]) => void);
  }

  emit<K extends keyof Events & string>(
    event: K,
    payload: Events[K]
  ) {
    this.emitter.emit(event, payload);
  }
}

// Usage — fully typed:
const bus = new TypedEmitter<EventMap>();

bus.on('user:login', (payload) => {
  console.log(payload.userId);    // TypeScript knows this is string
  console.log(payload.timestamp); // TypeScript knows this is Date
});

bus.on('error', (payload) => {
  console.log(payload.code); // TypeScript knows this is number
});

// bus.emit('user:login', { userId: 123 }); // ❌ Error: string expected
// bus.emit('nonexistent', {});             // ❌ Error: not in EventMap

Enter fullscreen mode Exit fullscreen mode

5. The Guard Pattern (Narrow Types in Functions)

interface User {
  id: string;
  name: string;
  role: 'admin' | 'editor' | 'viewer';
  email?: string; // Might not be set
}

// Instead of checking everywhere:
function sendEmail(user: User) {
  if (!user.email) throw new Error('No email');
  // TypeScript still thinks email might be undefined here in some cases
}

// Use type guards:
function hasEmail(user: User): user is User & { email: string } {
  return typeof user.email === 'string' && user.email.length > 0;
}

function isAdmin(user: User): user is User & { role: 'admin' } {
  return user.role === 'admin';
}

// Now TypeScript knows:
function sendEmail(user: User) {
  if (!hasEmail(user)) return; // Guard clause
  console.log(user.email.toLowerCase()); // ✅ TypeScript knows email is string
}

function deleteEverything(user: User) {
  if (!isAdmin(user)) throw new Error('Unauthorized');
  // ✅ TypeScript knows role is 'admin' here
}

Enter fullscreen mode Exit fullscreen mode

6. Zod for Runtime Validation

import { z } from 'zod';

// Define schema once — use for both types AND runtime validation
const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1).max(100),
  email: z.string().email(),
  role: z.enum(['admin', 'editor', 'viewer']),
  createdAt: z.string().datetime(),
});

// Infer TypeScript type from schema
type User = z.infer<typeof UserSchema>;

// Validate API responses at runtime:
async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const data = await response.json();

  // This throws if data doesn't match schema
  return UserSchema.parse(data);
}

// Safe parse (doesn't throw):
const result = UserSchema.safeParse(mysteryData);
if (result.success) {
  const user: User = result.data;
} else {
  console.error(result.error.flatten());
}

Enter fullscreen mode Exit fullscreen mode

Why Zod over Joi/ Yup? Type inference. Your schema IS your type definition. No duplication.

7. The Async Pool Pattern

// When you need to run many async operations but limit concurrency:
async function asyncPool<T>(
  concurrency: number,
  items: T[],
  fn: (item: T, index: number) => Promise<void>
): Promise<void> {
  const executing = new Set<Promise<void>>();

  for (const [index, item] of items.entries()) {
    const promise = fn(item, index).then(() => {
      executing.delete(promise);
    });
    executing.add(promise);

    if (executing.size >= concurrency) {
      await Promise.race(executing);
    }
  }

  await Promise.all(executing);
}

// Usage — fetch 100 URLs but only 5 at a time:
const urls = Array.from({ length: 100 }, (_, i) => `https://api.example.com/page/${i}`);

await asyncPool(5, urls, async (url) => {
  const response = await fetch(url);
  if (!response.ok) console.error(`Failed: ${url}`);
});

// Compare with naive approach (all at once — might crash):
// await Promise.all(urls.map(url => fetch(url))); // 💥 Too many connections

Enter fullscreen mode Exit fullscreen mode

Bonus: The Never-Returning Function

// For functions that always throw (assertions, unreachable code):
function assert(condition: unknown, message: string): asserts condition {
  if (!condition) throw new Error(message);
}

function unreachable(x: never): never {
  throw new Error(`Unreachable code reached: ${x}`);
}

// Usage:
type Status = 'loading' | 'success' | 'error';

function handleStatus(status: Status): string {
  switch (status) {
    case 'loading': return 'Loading...';
    case 'success': return 'Done!';
    case 'error': return 'Failed!';
  }
  // If you add a new status type, TypeScript will error here
  // because the switch is no longer exhaustive
}

Enter fullscreen mode Exit fullscreen mode


Which patterns do you use daily? Drop your favorites in the comments.

Follow @armorbreak for more TypeScript and Node.js content.