Files
claude-skills-reference/engineering-team/fullstack-engineer/references/architecture_patterns.md
Reza Rezvani 0c39593cba feat: add engineering team skills with fullstack-engineer package
Add comprehensive fullstack engineering skill package:

Fullstack Engineer:
- Code quality analyzer (Python tool)
- Fullstack scaffolder for rapid project setup (Python tool)
- Project scaffolder with best practices (Python tool)
- Architecture patterns reference (MVC, microservices, event-driven)
- Development workflows (Git, CI/CD, testing)
- Tech stack guide (frontend, backend, database, DevOps)

Includes packaged .zip archive for easy distribution and
comprehensive roadmap for future engineering skills.

This expands the library to 9 production-ready skills across
4 domains: Marketing, C-Level, Product Team, and Engineering.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 15:24:51 +02:00

1335 lines
33 KiB
Markdown

# Fullstack Architecture Patterns
## System Architecture Patterns
### 1. Monolithic Architecture
```
┌─────────────────────────────────┐
│ Frontend (Next.js) │
├─────────────────────────────────┤
│ Backend API (Node.js) │
├─────────────────────────────────┤
│ Database (PostgreSQL) │
└─────────────────────────────────┘
```
**When to Use:**
- Small to medium projects
- Rapid prototyping
- Single team ownership
- Simple deployment requirements
**Implementation:**
```typescript
// Single repository structure
project/
src/
client/ # Frontend code
server/ # Backend code
shared/ # Shared types/utils
database/ # Migrations/models
package.json # Single package.json
```
### 2. Microservices Architecture
```
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Web │ │ Mobile │ │ Admin │
│ Frontend │ │ App │ │ Portal │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
┌────┴────────────┴────────────┴─────┐
│ API Gateway │
├─────────┬──────────┬────────────┬──┤
│ Auth │ Product │ Order │ │
│ Service │ Service │ Service │ │
├─────────┼──────────┼────────────┤ │
│ User │ Product │ Order │ │
│ DB │ DB │ DB │ │
└─────────┴──────────┴────────────┘ │
```
**When to Use:**
- Large scale applications
- Multiple teams
- Independent scaling needs
- Complex business domains
**Implementation:**
```yaml
# docker-compose.yml for local development
version: '3.8'
services:
api-gateway:
build: ./gateway
ports: ["4000:4000"]
auth-service:
build: ./services/auth
environment:
- DB_HOST=auth-db
product-service:
build: ./services/product
environment:
- DB_HOST=product-db
order-service:
build: ./services/order
environment:
- DB_HOST=order-db
```
### 3. Serverless Architecture
```
┌────────────────────┐
│ CloudFront CDN │
├────────────────────┤
│ S3 Static Site │
├────────────────────┤
│ API Gateway │
├──────┬──────┬──────┤
│Lambda│Lambda│Lambda│
├──────┴──────┴──────┤
│ DynamoDB │
└────────────────────┘
```
**When to Use:**
- Variable/unpredictable traffic
- Cost optimization
- Minimal ops overhead
- Event-driven workflows
**Implementation:**
```typescript
// serverless.yml
service: my-app
provider:
name: aws
runtime: nodejs18.x
functions:
getUsers:
handler: handlers/users.get
events:
- http:
path: users
method: get
createUser:
handler: handlers/users.create
events:
- http:
path: users
method: post
```
### 4. Event-Driven Architecture
```
┌─────────────┐ Events ┌─────────────┐
│ Producer │────────────────▶│ Event Bus │
└─────────────┘ └──────┬──────┘
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Consumer A │ │ Consumer B │ │ Consumer C │
└─────────────┘ └─────────────┘ └─────────────┘
```
**Implementation:**
```typescript
// Event Bus with Redis Pub/Sub
import { createClient } from 'redis';
class EventBus {
private publisher;
private subscriber;
constructor() {
this.publisher = createClient();
this.subscriber = createClient();
}
async publish(event: string, data: any) {
await this.publisher.publish(event, JSON.stringify(data));
}
async subscribe(event: string, handler: (data: any) => void) {
await this.subscriber.subscribe(event);
this.subscriber.on('message', (channel, message) => {
if (channel === event) {
handler(JSON.parse(message));
}
});
}
}
// Usage
const eventBus = new EventBus();
// Publisher
await eventBus.publish('user.created', {
id: '123',
email: 'user@example.com'
});
// Subscriber
eventBus.subscribe('user.created', async (data) => {
// Send welcome email
// Update analytics
// Sync with CRM
});
```
## Frontend Architecture Patterns
### 1. Component-Based Architecture
```typescript
// Atomic Design Pattern
components/
atoms/ # Basic building blocks
Button/
Input/
Label/
molecules/ # Simple groups
FormField/
SearchBar/
Card/
organisms/ # Complex components
Header/
UserForm/
ProductGrid/
templates/ # Page templates
DashboardLayout/
AuthLayout/
pages/ # Actual pages
Dashboard/
Login/
```
### 2. State Management Patterns
#### Zustand (Recommended for React)
```typescript
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
interface UserState {
user: User | null;
isLoading: boolean;
error: string | null;
login: (credentials: LoginDto) => Promise<void>;
logout: () => void;
}
export const useUserStore = create<UserState>()(
devtools(
persist(
(set, get) => ({
user: null,
isLoading: false,
error: null,
login: async (credentials) => {
set({ isLoading: true, error: null });
try {
const user = await api.login(credentials);
set({ user, isLoading: false });
} catch (error) {
set({ error: error.message, isLoading: false });
}
},
logout: () => {
set({ user: null });
},
}),
{ name: 'user-storage' }
)
)
);
```
#### Context + Reducer Pattern
```typescript
interface State {
user: User | null;
theme: 'light' | 'dark';
}
type Action =
| { type: 'SET_USER'; payload: User }
| { type: 'TOGGLE_THEME' };
const AppContext = createContext<{
state: State;
dispatch: Dispatch<Action>;
}>({} as any);
function appReducer(state: State, action: Action): State {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload };
case 'TOGGLE_THEME':
return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
default:
return state;
}
}
export function AppProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(appReducer, initialState);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
}
```
### 3. Data Fetching Patterns
#### TanStack Query (React Query)
```typescript
// hooks/useUser.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export function useUser(userId: string) {
return useQuery({
queryKey: ['user', userId],
queryFn: () => api.getUser(userId),
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
});
}
export function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: UpdateUserDto) => api.updateUser(data),
onSuccess: (data, variables) => {
// Invalidate and refetch
queryClient.invalidateQueries(['user', variables.id]);
// Or update cache directly
queryClient.setQueryData(['user', variables.id], data);
},
});
}
```
#### GraphQL with Apollo Client
```typescript
// apollo-client.ts
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
const httpLink = createHttpLink({
uri: process.env.NEXT_PUBLIC_GRAPHQL_URL,
});
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('token');
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
};
});
export const apolloClient = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
// Usage in component
import { useQuery, gql } from '@apollo/client';
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
posts {
id
title
}
}
}
`;
function UserProfile({ userId }: { userId: string }) {
const { loading, error, data } = useQuery(GET_USER, {
variables: { id: userId },
});
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <Profile user={data.user} />;
}
```
## Backend Architecture Patterns
### 1. Clean Architecture (Hexagonal)
```
┌─────────────────────────────────────────┐
│ Presentation │
│ (Controllers, GraphQL) │
├─────────────────────────────────────────┤
│ Application │
│ (Use Cases, DTOs) │
├─────────────────────────────────────────┤
│ Domain │
│ (Entities, Business Logic) │
├─────────────────────────────────────────┤
│ Infrastructure │
│ (Database, External Services) │
└─────────────────────────────────────────┘
```
**Implementation:**
```typescript
// Domain Layer - Pure business logic
export class User {
constructor(
private id: string,
private email: string,
private passwordHash: string
) {}
validatePassword(password: string): boolean {
return bcrypt.compareSync(password, this.passwordHash);
}
changeEmail(newEmail: string): void {
if (!this.isValidEmail(newEmail)) {
throw new Error('Invalid email');
}
this.email = newEmail;
}
private isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
}
// Application Layer - Use cases
export class AuthenticateUserUseCase {
constructor(
private userRepository: IUserRepository,
private tokenService: ITokenService
) {}
async execute(email: string, password: string): Promise<AuthResult> {
const user = await this.userRepository.findByEmail(email);
if (!user || !user.validatePassword(password)) {
throw new UnauthorizedError('Invalid credentials');
}
const token = this.tokenService.generate(user);
return { user, token };
}
}
// Infrastructure Layer - Concrete implementations
export class PostgresUserRepository implements IUserRepository {
async findByEmail(email: string): Promise<User | null> {
const result = await db.query(
'SELECT * FROM users WHERE email = $1',
[email]
);
if (result.rows.length === 0) return null;
return new User(
result.rows[0].id,
result.rows[0].email,
result.rows[0].password_hash
);
}
}
// Presentation Layer - HTTP/GraphQL
export class AuthController {
constructor(private authenticateUser: AuthenticateUserUseCase) {}
async login(req: Request, res: Response) {
try {
const { email, password } = req.body;
const result = await this.authenticateUser.execute(email, password);
res.json({ token: result.token });
} catch (error) {
res.status(401).json({ error: error.message });
}
}
}
```
### 2. Repository Pattern
```typescript
// Generic Repository Interface
interface IRepository<T> {
findById(id: string): Promise<T | null>;
findAll(): Promise<T[]>;
create(entity: T): Promise<T>;
update(id: string, entity: Partial<T>): Promise<T>;
delete(id: string): Promise<void>;
}
// Specific Repository Interface
interface IUserRepository extends IRepository<User> {
findByEmail(email: string): Promise<User | null>;
findByRole(role: UserRole): Promise<User[]>;
}
// Implementation
class UserRepository implements IUserRepository {
constructor(private db: Knex) {}
async findById(id: string): Promise<User | null> {
const data = await this.db('users').where({ id }).first();
return data ? this.mapToEntity(data) : null;
}
async findByEmail(email: string): Promise<User | null> {
const data = await this.db('users').where({ email }).first();
return data ? this.mapToEntity(data) : null;
}
async create(user: User): Promise<User> {
const [id] = await this.db('users').insert(this.mapToDb(user)).returning('id');
return { ...user, id };
}
private mapToEntity(data: any): User {
return new User(data);
}
private mapToDb(user: User): any {
return {
email: user.email,
password_hash: user.passwordHash,
created_at: user.createdAt,
};
}
}
```
### 3. Service Layer Pattern
```typescript
// Business logic separated from controllers
export class UserService {
constructor(
private userRepo: IUserRepository,
private emailService: IEmailService,
private cacheService: ICacheService
) {}
async register(dto: RegisterUserDto): Promise<User> {
// Validate
await this.validateRegistration(dto);
// Create user
const passwordHash = await bcrypt.hash(dto.password, 10);
const user = await this.userRepo.create({
...dto,
passwordHash,
isVerified: false,
});
// Send verification email
await this.emailService.sendVerificationEmail(user);
// Cache user
await this.cacheService.set(`user:${user.id}`, user, 3600);
return user;
}
private async validateRegistration(dto: RegisterUserDto): Promise<void> {
const existing = await this.userRepo.findByEmail(dto.email);
if (existing) {
throw new ConflictError('Email already registered');
}
if (dto.password.length < 8) {
throw new ValidationError('Password too short');
}
}
}
```
## Database Patterns
### 1. Database Migration Pattern
```typescript
// migrations/20240101_create_users_table.ts
export async function up(knex: Knex): Promise<void> {
await knex.schema.createTable('users', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table.string('email').unique().notNullable();
table.string('password_hash').notNullable();
table.jsonb('profile');
table.timestamp('created_at').defaultTo(knex.fn.now());
table.timestamp('updated_at').defaultTo(knex.fn.now());
table.index('email');
table.index('created_at');
});
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTable('users');
}
```
### 2. Query Builder Pattern
```typescript
class UserQueryBuilder {
private query: Knex.QueryBuilder;
constructor(private db: Knex) {
this.query = this.db('users');
}
withPosts(): this {
this.query.leftJoin('posts', 'users.id', 'posts.user_id')
.select('posts.*');
return this;
}
whereActive(): this {
this.query.where('users.is_active', true);
return this;
}
createdAfter(date: Date): this {
this.query.where('users.created_at', '>', date);
return this;
}
paginate(page: number, limit: number): this {
this.query.limit(limit).offset((page - 1) * limit);
return this;
}
async execute(): Promise<User[]> {
const results = await this.query;
return results.map(r => new User(r));
}
}
// Usage
const users = await new UserQueryBuilder(db)
.withPosts()
.whereActive()
.createdAfter(new Date('2024-01-01'))
.paginate(1, 20)
.execute();
```
### 3. Unit of Work Pattern
```typescript
class UnitOfWork {
private operations: Array<() => Promise<any>> = [];
constructor(private db: Knex) {}
registerCreate<T>(repo: IRepository<T>, entity: T): void {
this.operations.push(() => repo.create(entity));
}
registerUpdate<T>(repo: IRepository<T>, id: string, data: Partial<T>): void {
this.operations.push(() => repo.update(id, data));
}
registerDelete<T>(repo: IRepository<T>, id: string): void {
this.operations.push(() => repo.delete(id));
}
async commit(): Promise<void> {
const trx = await this.db.transaction();
try {
for (const operation of this.operations) {
await operation();
}
await trx.commit();
this.operations = [];
} catch (error) {
await trx.rollback();
throw error;
}
}
rollback(): void {
this.operations = [];
}
}
// Usage
const uow = new UnitOfWork(db);
uow.registerCreate(userRepo, newUser);
uow.registerUpdate(profileRepo, userId, { bio: 'Updated bio' });
uow.registerDelete(postRepo, postId);
await uow.commit(); // All operations in single transaction
```
## Security Patterns
### 1. Authentication & Authorization
```typescript
// JWT Authentication Middleware
export const authenticate = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
throw new UnauthorizedError('No token provided');
}
const payload = jwt.verify(token, process.env.JWT_SECRET!) as JwtPayload;
// Check if token is blacklisted
const isBlacklisted = await redis.get(`blacklist:${token}`);
if (isBlacklisted) {
throw new UnauthorizedError('Token revoked');
}
// Attach user to request
req.user = await userService.findById(payload.sub);
if (!req.user) {
throw new UnauthorizedError('User not found');
}
next();
} catch (error) {
res.status(401).json({ error: 'Authentication failed' });
}
};
// Role-based Authorization
export const authorize = (...roles: UserRole[]) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
// Usage
router.get('/admin/users',
authenticate,
authorize(UserRole.ADMIN),
adminController.getUsers
);
```
### 2. Input Validation Pattern
```typescript
import { z } from 'zod';
// Define schemas
const CreateUserSchema = z.object({
email: z.string().email(),
password: z.string().min(8).regex(/^(?=.*[A-Z])(?=.*\d)/,
'Password must contain uppercase and number'),
name: z.string().min(2).max(50),
age: z.number().int().positive().max(120).optional(),
role: z.enum(['user', 'admin']).default('user'),
});
// Validation middleware
export const validate = <T>(schema: z.ZodSchema<T>) => {
return async (req: Request, res: Response, next: NextFunction) => {
try {
req.body = await schema.parseAsync(req.body);
next();
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({
error: 'Validation failed',
details: error.errors,
});
} else {
next(error);
}
}
};
};
// Usage
router.post('/users',
validate(CreateUserSchema),
userController.create
);
```
### 3. Rate Limiting Pattern
```typescript
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
// Basic rate limiter
export const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP',
standardHeaders: true,
legacyHeaders: false,
});
// Strict rate limiter for auth endpoints
export const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
skipSuccessfulRequests: true, // Don't count successful requests
store: new RedisStore({
client: redis,
prefix: 'rl:auth:',
}),
});
// Dynamic rate limiting based on user tier
export const dynamicLimiter = async (
req: Request,
res: Response,
next: NextFunction
) => {
const limits = {
free: 10,
pro: 100,
enterprise: 1000,
};
const userTier = req.user?.tier || 'free';
const limit = limits[userTier];
const key = `rl:${req.user?.id || req.ip}:${Date.now() / 60000 | 0}`;
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, 60); // 1 minute window
}
if (current > limit) {
return res.status(429).json({
error: 'Rate limit exceeded',
retryAfter: 60,
});
}
res.setHeader('X-RateLimit-Limit', limit.toString());
res.setHeader('X-RateLimit-Remaining', (limit - current).toString());
next();
};
```
## Testing Patterns
### 1. Unit Testing
```typescript
// user.service.test.ts
describe('UserService', () => {
let userService: UserService;
let mockUserRepo: jest.Mocked<IUserRepository>;
let mockEmailService: jest.Mocked<IEmailService>;
beforeEach(() => {
mockUserRepo = {
create: jest.fn(),
findByEmail: jest.fn(),
// ... other methods
};
mockEmailService = {
sendVerificationEmail: jest.fn(),
};
userService = new UserService(mockUserRepo, mockEmailService);
});
describe('register', () => {
it('should create a new user and send verification email', async () => {
const dto: RegisterUserDto = {
email: 'test@example.com',
password: 'Password123!',
name: 'Test User',
};
const expectedUser = {
id: '123',
...dto,
passwordHash: 'hashed',
isVerified: false,
};
mockUserRepo.findByEmail.mockResolvedValue(null);
mockUserRepo.create.mockResolvedValue(expectedUser);
const result = await userService.register(dto);
expect(mockUserRepo.findByEmail).toHaveBeenCalledWith(dto.email);
expect(mockUserRepo.create).toHaveBeenCalledWith(
expect.objectContaining({
email: dto.email,
name: dto.name,
})
);
expect(mockEmailService.sendVerificationEmail).toHaveBeenCalledWith(expectedUser);
expect(result).toEqual(expectedUser);
});
it('should throw error if email already exists', async () => {
mockUserRepo.findByEmail.mockResolvedValue({ id: '123' } as User);
await expect(
userService.register({
email: 'existing@example.com',
password: 'Password123!',
name: 'Test',
})
).rejects.toThrow('Email already registered');
});
});
});
```
### 2. Integration Testing
```typescript
// api.integration.test.ts
import request from 'supertest';
import { app } from '../src/app';
import { db } from '../src/db';
describe('User API Integration', () => {
beforeEach(async () => {
await db.migrate.latest();
await db.seed.run();
});
afterEach(async () => {
await db.migrate.rollback();
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
const response = await request(app)
.post('/api/users')
.send({
email: 'newuser@example.com',
password: 'Password123!',
name: 'New User',
})
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(String),
email: 'newuser@example.com',
name: 'New User',
});
// Verify in database
const user = await db('users')
.where({ email: 'newuser@example.com' })
.first();
expect(user).toBeDefined();
expect(user.is_verified).toBe(false);
});
it('should return 400 for invalid input', async () => {
const response = await request(app)
.post('/api/users')
.send({
email: 'invalid-email',
password: '123', // Too short
})
.expect(400);
expect(response.body).toHaveProperty('error');
expect(response.body.details).toBeInstanceOf(Array);
});
});
});
```
### 3. E2E Testing (Cypress)
```typescript
// cypress/e2e/user-registration.cy.ts
describe('User Registration Flow', () => {
beforeEach(() => {
cy.task('db:seed');
cy.visit('/register');
});
it('should successfully register a new user', () => {
// Fill form
cy.get('[data-testid="email-input"]').type('newuser@example.com');
cy.get('[data-testid="password-input"]').type('Password123!');
cy.get('[data-testid="confirm-password-input"]').type('Password123!');
cy.get('[data-testid="name-input"]').type('New User');
// Submit
cy.get('[data-testid="register-button"]').click();
// Verify redirect
cy.url().should('include', '/verify-email');
cy.contains('Please check your email').should('be.visible');
// Verify email was sent (using mailhog or similar)
cy.task('mail:check', 'newuser@example.com').then((email) => {
expect(email.subject).to.equal('Verify your email');
expect(email.html).to.include('verification link');
});
});
it('should show validation errors', () => {
cy.get('[data-testid="register-button"]').click();
cy.contains('Email is required').should('be.visible');
cy.contains('Password is required').should('be.visible');
// Test password requirements
cy.get('[data-testid="password-input"]').type('weak');
cy.contains('Password must be at least 8 characters').should('be.visible');
});
});
```
## Performance Optimization Patterns
### 1. Caching Strategies
```typescript
// Multi-layer caching
class CacheService {
private memoryCache = new Map<string, { data: any; expires: number }>();
constructor(private redis: Redis) {}
async get<T>(key: string): Promise<T | null> {
// L1: Memory cache
const memory = this.memoryCache.get(key);
if (memory && memory.expires > Date.now()) {
return memory.data;
}
// L2: Redis cache
const cached = await this.redis.get(key);
if (cached) {
const data = JSON.parse(cached);
// Populate memory cache
this.memoryCache.set(key, {
data,
expires: Date.now() + 60000, // 1 minute
});
return data;
}
return null;
}
async set<T>(key: string, value: T, ttl: number): Promise<void> {
// Set in both caches
this.memoryCache.set(key, {
data: value,
expires: Date.now() + (ttl * 1000),
});
await this.redis.setex(key, ttl, JSON.stringify(value));
}
async invalidate(pattern: string): Promise<void> {
// Clear memory cache
for (const key of this.memoryCache.keys()) {
if (key.includes(pattern)) {
this.memoryCache.delete(key);
}
}
// Clear Redis cache
const keys = await this.redis.keys(`*${pattern}*`);
if (keys.length > 0) {
await this.redis.del(...keys);
}
}
}
// Cache-aside pattern
async function getUserWithCache(userId: string): Promise<User> {
const cacheKey = `user:${userId}`;
// Try cache first
let user = await cache.get<User>(cacheKey);
if (!user) {
// Cache miss - fetch from database
user = await userRepository.findById(userId);
if (user) {
// Cache for 1 hour
await cache.set(cacheKey, user, 3600);
}
}
return user;
}
```
### 2. Database Query Optimization
```typescript
// N+1 Query Prevention
// Bad - N+1 queries
async function getUsersWithPostsBad(): Promise<User[]> {
const users = await db('users').select('*');
for (const user of users) {
user.posts = await db('posts').where({ user_id: user.id });
}
return users;
}
// Good - Single query with join
async function getUsersWithPostsGood(): Promise<User[]> {
const results = await db('users')
.leftJoin('posts', 'users.id', 'posts.user_id')
.select(
'users.*',
db.raw('COALESCE(json_agg(posts.*) FILTER (WHERE posts.id IS NOT NULL), \'[]\') as posts')
)
.groupBy('users.id');
return results.map(r => ({
...r,
posts: JSON.parse(r.posts),
}));
}
// DataLoader for batching
import DataLoader from 'dataloader';
const userLoader = new DataLoader(async (userIds: string[]) => {
const users = await db('users').whereIn('id', userIds);
// DataLoader expects results in same order as input
const userMap = new Map(users.map(u => [u.id, u]));
return userIds.map(id => userMap.get(id) || null);
});
// Usage in GraphQL resolver
const resolvers = {
Post: {
author: (post: Post) => userLoader.load(post.user_id),
},
};
```
### 3. API Response Optimization
```typescript
// Pagination
interface PaginationParams {
page: number;
limit: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
}
interface PaginatedResponse<T> {
data: T[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
};
}
async function paginate<T>(
query: Knex.QueryBuilder,
params: PaginationParams
): Promise<PaginatedResponse<T>> {
const { page, limit, sortBy = 'created_at', sortOrder = 'desc' } = params;
// Get total count
const [{ count }] = await query.clone().count('* as count');
const total = parseInt(count as string, 10);
// Apply pagination
const data = await query
.orderBy(sortBy, sortOrder)
.limit(limit)
.offset((page - 1) * limit);
return {
data,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
hasNext: page * limit < total,
hasPrev: page > 1,
},
};
}
// Field selection (GraphQL-like)
async function getUsers(fields?: string[]): Promise<User[]> {
const query = db('users');
if (fields && fields.length > 0) {
// Only select requested fields
query.select(fields.filter(f => allowedFields.includes(f)));
} else {
// Default fields (exclude sensitive data)
query.select('id', 'name', 'email', 'created_at');
}
return query;
}
// Response compression
import compression from 'compression';
app.use(compression({
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
},
threshold: 1024, // Only compress responses > 1kb
}));
```
## Deployment Patterns
### 1. Blue-Green Deployment
```yaml
# kubernetes/blue-green-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: app-service
spec:
selector:
app: myapp
version: blue # Switch between blue/green
ports:
- port: 80
targetPort: 3000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-blue
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: blue
template:
metadata:
labels:
app: myapp
version: blue
spec:
containers:
- name: app
image: myapp:1.0.0
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-green
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: green
template:
metadata:
labels:
app: myapp
version: green
spec:
containers:
- name: app
image: myapp:2.0.0
```
### 2. Canary Deployment
```typescript
// Feature flags for canary releases
import { FeatureFlag } from './feature-flag-service';
@FeatureFlag('new-checkout-flow', 0.1) // 10% of users
async function checkout(req: Request, res: Response) {
if (req.featureFlags?.['new-checkout-flow']) {
return newCheckoutFlow(req, res);
}
return oldCheckoutFlow(req, res);
}
// Traffic splitting with nginx
```
### 3. Rolling Deployment
```yaml
# Kubernetes rolling update
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Max pods above desired replicas
maxUnavailable: 1 # Max pods that can be unavailable
template:
spec:
containers:
- name: app
image: myapp:2.0.0
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
```