Skip to content

TypeScript Primer

What is TypeScript?

TypeScript is a strongly typed programming language that builds on JavaScript by adding static type definitions. In the v4-monorepo, TypeScript is the primary language for most services and libraries, providing improved developer experience, better code quality, and enhanced maintainability.

Key Concepts

Type System

TypeScript's core feature is its type system:

  • Static Typing: Types are checked at compile-time, not runtime
  • Type Inference: TypeScript can infer types without explicit annotations
  • Type Declarations: Types can be explicitly declared for variables, functions, etc.
  • Interfaces and Types: Define structures for objects, parameters, and return values
  • Generics: Create reusable components that work with different types

Configuration

TypeScript's behavior is configured via tsconfig.json files, which define:

  • Target JavaScript version
  • Module system (ESM, CommonJS)
  • Type checking strictness
  • File inclusions/exclusions
  • Path aliases

TypeScript and ESM

The v4-monorepo uses TypeScript with ECMAScript Modules (ESM):

  • "type": "module" in package.json
  • import/export syntax for modules
  • Path extensions required in imports

How TypeScript is Used in This Repository

Project Structure

In the v4-monorepo:

  1. Configuration:

  2. Root tsconfig.base.json with shared settings

  3. Project-specific tsconfig files that extend the base
  4. Strict type checking enabled

  5. File Organization:

  6. Source files in src/ directories

  7. Type definitions in types/ directories or alongside code
  8. Tests in tests/ directories

  9. Module System:

  10. ECMAScript Modules (ESM) throughout
  11. Full path imports with .js extensions (for ESM compatibility)
  12. Path aliases for cleaner imports

TypeScript Standards

The repository enforces:

  1. Strictness:

  2. strict: true in tsconfig

  3. No implicit any types
  4. No unnecessary type assertions

  5. Code Quality:

  6. Type-safe API boundaries

  7. Proper error handling
  8. Zod for runtime validation

  9. Type Definitions:

  10. Explicit interface definitions
  11. Shared types in library packages
  12. Minimal use of any

TypeScript Configuration

Base Configuration

The root tsconfig.base.json provides shared settings:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true,
    "paths": {
      // Path aliases for project imports
    }
  }
}

Project Configuration

Each project extends the base configuration:

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Special Configurations

  • App tsconfig: For application code (tsconfig.app.json)
  • Test tsconfig: For test code (tsconfig.spec.json)
  • Build tsconfig: For optimized builds (tsconfig.build.json)

Common TypeScript Patterns

Type-Safe API Boundaries

API definitions use explicit interfaces:

interface UserRequest {
  username: string;
  email: string;
}

interface UserResponse {
  id: string;
  username: string;
  email: string;
  createdAt: Date;
}

async function createUser(req: UserRequest): Promise<UserResponse> {
  // Implementation...
}

Zod for Runtime Validation

Zod is used to validate inputs:

import { z } from 'zod/v4';

const UserSchema = z.object({
  username: z.string().min(3).max(50),
  email: z.string().email(),
});

// Type is inferred from the schema
type User = z.infer<typeof UserSchema>;

function validateUser(data: unknown): User {
  return UserSchema.parse(data);
}

Utility Types

The codebase uses TypeScript utility types for common operations:

// Extract a subset of properties
type UserPublicInfo = Pick<User, 'id' | 'username'>;

// Make properties optional
type PartialUser = Partial<User>;

// Make properties required
type RequiredUser = Required<User>;

// Exclude specific types
type NonNullableUser = NonNullable<User | null | undefined>;

ES-Toolkit Integration

The repository uses ES-Toolkit as the primary utility library for JavaScript/TypeScript operations. ES-Toolkit provides high-performance, TypeScript-first utilities with excellent tree-shaking support:

import { cloneDeep, debounce, isEqual, omit, pick, throttle } from 'es-toolkit';

// Debounce expensive operations
const debouncedSearch = debounce((query: string) => {
  return searchAPI(query);
}, 300);

// Deep equality checks with type safety
const hasChanged = !isEqual(previousState, currentState);

// Object manipulation with type preservation
const publicUser = pick(user, ['id', 'username', 'email']);
const sanitizedUser = omit(user, ['password', 'internalId']);

// Deep cloning for immutable updates
const updatedState = cloneDeep(currentState);

Key Benefits:

  • Performance: 2-3x faster than Lodash
  • Bundle Size: ~97% smaller than Lodash when tree-shaken
  • TypeScript: Native TypeScript with excellent type inference
  • Modern: Uses modern JavaScript features and ESM modules

See ADR-20250606: Migration from Lodash to ES-Toolkit for more details.

TypeScript Tools and Utilities

Type Checking

Run type checking for a project:

nx run orderbook:typecheck

Check all TypeScript projects:

yarn nx:typescript

Type Strictness

Check TypeScript strictness issues:

yarn ts:strictness:check

Fix TypeScript strictness issues:

yarn ts:strictness:fix

Excess Property Checks

Check for excess properties:

yarn ts:excess-props:check

Fix excess property issues:

yarn ts:excess-props:write

Unused Code Detection

Find unused variables and imports:

yarn ts:unused:check

Remove unused code:

yarn ts:unused:write

Troubleshooting TypeScript

Common Issues

Issue Solution
"Cannot find module" Check import path, add .js extension for ESM
"Type errors" Fix type issues or add appropriate type annotations
"No matching overload" Check function parameter types and constraints
"Property does not exist" Fix property access or add appropriate type definitions
"TypeScript version mismatch" Ensure consistent TypeScript version across packages

Type Assertions

When TypeScript can't infer a type correctly, use type assertions:

// Type assertion (use sparingly)
const user = jsonData as User;

// Type assertion with unknown intermediate
const user = (jsonData as unknown) as User;

Learn More