All TypeScript templates

Node.js Microservice Patterns

Service boundaries, event-driven communication, retries, idempotency.

DevZone Tools1,060 copiesUpdated Apr 14, 2026Node.jsTypeScript
# CLAUDE.md — Node.js Microservice Patterns

## Boundaries

- One service per **bounded context**, not per table or per resource.
- A service owns its data. No service reads another service's database directly — always through an API.
- If two services constantly co-deploy and co-change, they're one service.

## Communication

- **Sync** (HTTP/gRPC) for queries that need an immediate answer.
- **Async** (events, queues) for state propagation — orders → inventory, user-created → email-welcome.
- Default to async. Sync calls between services create cascade failures.

## Idempotency

- Every mutation accepts an idempotency key. Same key = same response, even on retry.
- Implement with a key→result table indexed on the key. TTL old keys.
- Without idempotency, retries (yours or the client's) duplicate work. There will be retries.

## Events

- Schema-first. Use **Avro**, **Protobuf**, or at least a **JSON Schema** registry.
- Events are immutable facts: `OrderPlaced`, `PaymentSettled`. Past tense.
- Emit on commit, not on intent. Outbox pattern: write the event row in the same DB transaction as the state change; a separate process publishes from the outbox.

## Failure handling

- Retries are bounded: 3–5 attempts with exponential backoff and jitter.
- After max retries, send to a **dead-letter queue**. Alert on DLQ growth.
- Circuit breakers (`opossum` or hand-rolled) on every cross-service call. Fail fast when downstream is unhealthy.

## Observability

- **Distributed tracing** with OpenTelemetry. Propagate `traceparent` across HTTP, queues, and async hops.
- Structured logs with `trace_id` and `span_id` on every line.
- Per-service metrics: requests/sec, p99 latency, error rate, queue depth.

## Versioning

- API versioning in the URL or header. `v1` is forever; `v2` is additive at first.
- Producer compatibility for events: only **add** fields, never remove or rename. Consumers tolerate unknown fields.

## Service mesh / gateway

- An API gateway terminates TLS, does auth, and routes. Services trust the gateway's auth claims.
- Mesh (Istio, Linkerd) for mutual TLS and traffic policies. Don't reinvent in app code.

## Local development

- One docker-compose per service group. New devs `docker compose up`.
- Stub external services with **wiremock** or **mockoon**.
- Don't run all services on every laptop. Run the ones you change; talk to a shared dev environment for the rest.

## Don't

- Don't share databases across services to "save effort". You're trading short-term convenience for long-term coupling.
- Don't expose internal IDs across service boundaries. Use stable UUIDs.
- Don't synchronously call N services to assemble a response. Aggregate via materialized views or a BFF.
- Don't deploy services together as a unit. The whole point is independent rollout.

Other TypeScript templates