Expert guidance for building and maintaining federated GraphQL microservices using Bun, Apollo Federation, PostgreSQL, Redis, and comprehensive observability patterns.
Expert guidance for building and maintaining production-ready federated GraphQL microservices using Bun.sh runtime, Apollo Server, and Apollo Federation v2. This skill covers architecture patterns, development workflows, testing strategies, and operational best practices.
This skill provides comprehensive instructions for working with a federated GraphQL microservices architecture featuring:
When working with this architecture, understand the service composition:
1. **Gateway Service (port 4000)**: Apollo Gateway that composes the supergraph from all subgraphs with retry logic and health checks
2. **Users Service (port 4001)**: Manages user accounts with JWT auth and subscriptions
3. **Products Service (port 4002)**: Product catalog with DataLoader and caching
4. **Orders Service (port 4003)**: Order management that extends User entity and references Products
Follow these patterns when implementing federation:
The project uses filesystem-based auto-discovery for services:
When setting up the project for the first time:
```bash
bun install # Install dependencies
bun run docker:dev # Start PostgreSQL and Redis containers
bun run setup # Create databases and run migrations
bun run seed # Seed sample data
```
For regular development work:
```bash
bun run dev # Start all services with hot reload
bun run dev:gateway # Start gateway only
bun run dev:users # Start specific service
```
**IMPORTANT**: Always run these before committing:
```bash
bun run lint # Check code style with Biome
bun run lint:fix # Auto-fix style issues
bun run typecheck # TypeScript type checking
```
When modifying GraphQL schemas in service TypeScript files, follow this exact workflow:
1. Make schema changes in `services/[service]/src/index.ts`
2. Run `bun run schema:update` which executes three steps:
- Extracts schemas from TypeScript files
- Cleans schemas by removing custom directives
- Runs codegen for TypeScript types
This is critical because schema changes must be properly extracted and types regenerated for the federation to work correctly.
When creating a new service:
1. Create service directory: `mkdir -p services/newservice/src`
2. Copy `package.json` from existing service and update name and port
3. Implement schema with federation directives (`@key`, `@external`, `@shareable`)
4. Service will be auto-discovered by all scripts (no manual registration)
5. Optionally add Prisma schema in `services/newservice/prisma/schema.prisma`
6. Run `bun run setup` to create database if using Prisma
When extending an entity defined in another service:
```typescript
// In extending service (e.g., Orders extending User)
const typeDefs = gql`
extend schema @link(
url: "https://specs.apollo.dev/federation/v2.0",
import: ["@key", "@external"]
)
extend type User @key(fields: "id") {
id: ID! @external
orders: [Order!]!
}
type Order @key(fields: "id") {
id: ID!
userId: ID!
user: User!
}
`;
// Implement reference resolver
const resolvers = {
User: {
__resolveReference: async (user: { id: string }, context) => {
// Return user data or minimal representation
return { id: user.id };
},
orders: async (user: { id: string }, _args, context) => {
// Fetch orders for this user
return context.prisma.order.findMany({ where: { userId: user.id } });
},
},
};
```
When implementing real-time subscriptions:
1. Add subscription to schema:
```typescript
type Subscription {
productUpdated(productId: ID): Product!
orderStatusChanged(userId: ID!): Order!
}
```
2. Implement subscription resolver:
```typescript
Subscription: {
productUpdated: {
subscribe: (_, { productId }, context) => {
return context.pubsub.asyncIterator(['PRODUCT_UPDATED']);
},
resolve: (payload, { productId }) => {
// Filter events if productId provided
if (productId && payload.productUpdated.id !== productId) {
return null;
}
return payload.productUpdated;
}
}
}
```
3. Publish events in mutations:
```typescript
// In mutation resolver
const updatedProduct = await context.prisma.product.update({ ... });
await context.pubsub.publish('PRODUCT_UPDATED', { productUpdated: updatedProduct });
await context.cache.invalidatePattern(`product:${id}:*`);
```
When multiple services need the same type (e.g., pagination types):
```typescript
// In each service that needs the type
extend schema @link(
url: "https://specs.apollo.dev/federation/v2.0",
import: ["@key", "@shareable"]
)
type PageInfo @shareable {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
```
The `@shared/utils` package provides common functionality:
```typescript
import {
discoverServices, // Auto-discover all services
getAllServiceInfo, // Get service configs (name, port, url, path)
checkServiceHealth, // Health check for service URL
getServiceDatabaseUrl, // Get Postgres URL for service
exportSchema, // Export GraphQL schema
logSuccess, logError // Consistent logging
} from '@shared/utils';
// Example: Auto-discover services
const services = await discoverServices();
// Example: Get service information
const allServices = getAllServiceInfo();
const usersService = allServices.users; // { name, port, url, path }
// Example: Check if service is running
const isRunning = await checkServiceHealth('http://localhost:4001/graphql');
```
```bash
bun test # Run all tests
bun test services/users # Test specific service
bun test --watch # Watch mode
bun test services/users/src/index.test.ts # Specific file
bun test -t "should create user" # Tests matching pattern
```
All tests follow this pattern with mocked dependencies:
```typescript
import { describe, test, expect, mock } from 'bun:test';
import { graphql } from 'graphql';
// Mock all external dependencies
const mockPrisma = {
user: {
findUnique: mock(() => Promise.resolve({ id: '1', email: '[email protected]' })),
create: mock((args) => Promise.resolve({ id: '1', ...args.data })),
},
};
const mockCacheService = {
get: mock(() => Promise.resolve(null)),
set: mock(() => Promise.resolve()),
};
const mockContext = {
prisma: mockPrisma,
cache: mockCacheService,
userId: '1',
};
// Test GraphQL operations directly
describe('User Queries', () => {
test('should fetch user by id', async () => {
const query = `query GetUser($id: ID!) { user(id: $id) { id email } }`;
const result = await graphql({
schema,
source: query,
variableValues: { id: '1' },
contextValue: mockContext,
});
expect(result.errors).toBeUndefined();
expect(result.data?.user).toBeDefined();
});
});
```
The architecture uses RS256 JWT tokens with separate keys for access and refresh:
```typescript
const typeDefs = gql`
type Query {
me: User! @auth
allUsers: [User!]! @auth(requires: ADMIN)
publicData: String! @public
}
`;
```
Use these consistent cache key patterns:
Always invalidate cache on mutations:
```typescript
// In mutation resolver
const updatedUser = await context.prisma.user.update({ ... });
await context.cache.invalidate(`user:${id}`);
// Or pattern-based invalidation
await context.cache.invalidatePattern(`orders:user:${userId}:*`);
```
OpenTelemetry is configured for:
All logs include correlation IDs for request tracing across services. Use the shared logging utilities:
```typescript
import { logSuccess, logError, logWarning, logInfo, logStep } from '@shared/utils';
logStep('Processing order...');
logSuccess('Order created successfully');
logError('Failed to process payment');
```
Follow these style rules enforced by Biome:
All scripts use auto-discovery instead of hardcoded service lists:
```typescript
// Preferred approach
const services = await discoverServices(); // Auto-discovers from filesystem
const serviceNames = await getServiceNames(); // Just the names
// Get comprehensive service information
const allServices = getAllServiceInfo();
// Returns: { users: { name, port, url, path }, products: {...}, ... }
```
All scripts follow this consistent error handling:
```typescript
async function main() {
try {
logStep('Starting process...');
// ... do work ...
logSuccess('Process completed!');
} catch (error) {
logError(`Process failed: ${error}`);
process.exit(1);
}
}
main().catch((error) => {
logError(`Script failed: ${error}`);
process.exit(1);
});
```
```
services/
gateway/src/index.ts # Apollo Gateway
users/src/
index.ts # JWT auth, user management
types.ts # TypeScript types
subscriptions.ts # Subscription resolvers
products/src/ # Product catalog
orders/src/ # Order management
[service]/
prisma/ # Database schema
generated/prisma/ # Generated Prisma client
shared/
auth/ # JWT utilities
cache/ # Redis caching
config/ # Zod env validation
errors/ # Error handling
graphql/ # Federation directives
health/ # Health checks
logging/ # Structured logging
observability/ # OpenTelemetry
pubsub/ # Redis pub/sub
query-complexity/ # Query complexity analysis
rate-limit/ # Rate limiting
utils/ # Shared utilities
validation/ # Input validation
scripts/
dev.ts # Development server
build.ts # Production build
setup-db.ts # Database initialization
export-schema.ts # Schema export
generate-docs.ts # API documentation
update-schemas.ts # Schema extraction and codegen
```
| Command | Purpose |
|---------|---------|
| `bun run dev` | Start all services with hot reload |
| `bun run schema:update` | Extract schemas, clean, and run codegen (CRITICAL after schema changes) |
| `bun run lint && bun run typecheck` | Code quality checks (run before commit) |
| `bun test --watch` | Run tests in watch mode |
| `bun run setup` | Create databases and run migrations |
| `bun run docker:dev` | Start PostgreSQL and Redis |
| `bun run docs:generate` | Generate API documentation |
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/graphql-microservices-architecture/raw