← Back to TypeScript Course | Chapter 28: Real World Projects | Lesson 12 of 14

Microservices with TypeScript

Microservices divide an application into independently deployable services. TypeScript can provide clear contracts for requests, responses, configuration, and shared domain models.
Syntax
typescript
// shared/events.ts
export interface EventName {
    property: type;
}

// each service imports the same shared shape
import { EventName } from "shared";

Core Concept

In a TypeScript microservices architecture, each service can have its own independent tsconfig.json and dependencies, but shared request/response contracts between services still benefit from a common typed schema so they don't drift apart silently.

Example: Core Concept

typescript
interface OrderCreatedEvent {
  orderId: string;
  total: number;
}
// Each service has its own tsconfig, but shares this event's shape.
const event: OrderCreatedEvent = { orderId: "1", total: 50 };
console.log(event);

Basic Setup

A basic setup often uses a shared-types package (or generated types from a schema like Protobuf/OpenAPI) published to a private registry, imported by every service that needs to call or be called by another.

Example: Basic Setup

typescript
// shared-types package published to a private registry,
// imported by every service that produces/consumes these events.
interface OrderCreatedEvent { orderId: string; }
console.log("A shared-types package prevents contract drift between services");

Typed Example

A typed example: a shared interface OrderCreatedEvent { orderId: string; userId: string; total: number } is used both by the service that publishes the event and every service that subscribes to and processes it.

Example: Typed Example

typescript
interface OrderCreatedEvent {
  orderId: string;
  userId: string;
  total: number;
}
function publishOrderCreated(event: OrderCreatedEvent) {
  console.log("Publishing:", event);
}
publishOrderCreated({ orderId: "1", userId: "u1", total: 99 });

Project Usage

In a real project, versioning these shared inter-service types carefully matters more than in a monolith, since two services might deploy on different schedules and temporarily run against slightly different versions of a shared contract.

Example: Project Usage

typescript
interface OrderCreatedEventV2 {
  orderId: string;
  total: number;
  currency: string; // added in v2
}
console.log("Services on different deploy schedules may see different versions");

Best Practices

Validate incoming inter-service messages at runtime (not just compile time) at each service boundary, since TypeScript's types vanish at runtime and can't protect a service from a malformed message sent by another service running an older version.

Example: Best Practices

typescript
interface OrderCreatedEvent { orderId: string; total: number; }
function isOrderCreatedEvent(value: any): value is OrderCreatedEvent {
  return typeof value?.orderId === "string" && typeof value?.total === "number";
}
console.log(isOrderCreatedEvent({ orderId: "1", total: 10 }));
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Changing a shared event interface in one service without updating the others, which breaks them at runtime.
  2. Sharing a large package of types so all services depend on each other, creating tight coupling.
  3. Trusting TypeScript types across a network boundary, when messages must still be validated at runtime.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.