Internal HTTP, Tiny Plugins, Shared Schemas
Microservices With Fastify
Fastify's plugin model scales out as well as up: each service is its own Fastify app, communicating over HTTP or messaging, with shared schemas for type-safety.
What you'll learn
- Define service boundaries
- Share TypeBox or zod schemas via a contracts package
- Communicate over undici, NATS, or RabbitMQ
A Fastify app is small. So if your domain has clearly separable pieces — billing, search, notifications — each becomes its own service. Each is a Fastify process; together they form your platform.
Shared Contracts
The risky part of microservices is drift between services. Share schemas through a small contracts package consumed by everyone.
// contracts/src/user.ts
import { Type, type Static } from '@sinclair/typebox'
export const User = Type.Object({
id: Type.String({ format: 'uuid' }),
email: Type.String({ format: 'email' }),
})
export type User = Static<typeof User> The users service uses User as its response schema; the billing service imports the same User for type-safe consumption.
Internal HTTP
For request/response, use HTTP between services. Pool connections with undici and add retries.
import { Pool } from 'undici'
const users = new Pool('http://users:8080', { connections: 20 })
export async function getUser(id: string): Promise<User> {
const { body, statusCode } = await users.request({ path: `/users/${id}`, method: 'GET' })
if (statusCode !== 200) throw new Error('users service ' + statusCode)
return await body.json() as User
} Async Messaging
For events (“user created”), use a broker — NATS, RabbitMQ, or Kafka. Producers publish, consumers subscribe; services stay decoupled.
import { connect } from 'nats'
const nc = await connect({ servers: app.config.NATS_URL })
app.decorate('nats', nc)
app.post('/users', async (req) => {
const user = await app.db.user.create({ data: req.body })
nc.publish('user.created', JSON.stringify(user))
return user
}) A Gateway
Front the services with a small Fastify gateway that handles auth, rate limiting, and @fastify/http-proxy routes to internal hosts. Clients see one API; you keep service boundaries clean.