// Expertise in API design, database integration, and service connectivity. Activates when working with "API", "database", "webhook", "service", "integrate", "connect", or system architecture.
| name | Integration Patterns |
| description | Expertise in API design, database integration, and service connectivity. Activates when working with "API", "database", "webhook", "service", "integrate", "connect", or system architecture. |
| version | 1.0.0 |
Design and implement robust integration patterns for connecting services, databases, and external systems. This skill encompasses RESTful and GraphQL API design, database connection management, event-driven architecture, webhook processing, retry strategies, and resilient service communication patterns.
Design Resource-Oriented APIs:
Structure REST APIs following best practices with proper HTTP semantics:
// src/api/users/users.controller.ts
import { Router, Request, Response, NextFunction } from 'express';
import { UserService } from './user.service';
import { validateRequest } from '../../middleware/validation';
import { authenticate } from '../../middleware/auth';
import { rateLimit } from '../../middleware/rate-limit';
import { CreateUserDto, UpdateUserDto, UserQueryDto } from './user.dto';
export class UsersController {
public router = Router();
constructor(private userService: UserService) {
this.setupRoutes();
}
private setupRoutes(): void {
// List users with pagination and filtering
this.router.get(
'/',
authenticate,
rateLimit({ windowMs: 60000, max: 100 }),
validateRequest(UserQueryDto, 'query'),
this.listUsers.bind(this)
);
// Get single user
this.router.get(
'/:id',
authenticate,
this.getUser.bind(this)
);
// Create user
this.router.post(
'/',
rateLimit({ windowMs: 60000, max: 10 }),
validateRequest(CreateUserDto, 'body'),
this.createUser.bind(this)
);
// Update user (full update)
this.router.put(
'/:id',
authenticate,
validateRequest(UpdateUserDto, 'body'),
this.updateUser.bind(this)
);
// Partial update
this.router.patch(
'/:id',
authenticate,
validateRequest(UpdateUserDto, 'body', { partial: true }),
this.patchUser.bind(this)
);
// Delete user
this.router.delete(
'/:id',
authenticate,
this.deleteUser.bind(this)
);
}
private async listUsers(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const { page = 1, limit = 20, sort = 'createdAt', order = 'desc', search } = req.query;
const result = await this.userService.findAll({
page: Number(page),
limit: Number(limit),
sort: sort as string,
order: order as 'asc' | 'desc',
search: search as string,
});
res.status(200).json({
data: result.users,
pagination: {
page: result.page,
limit: result.limit,
total: result.total,
pages: Math.ceil(result.total / result.limit),
},
links: {
self: `/api/users?page=${result.page}&limit=${result.limit}`,
next: result.page < Math.ceil(result.total / result.limit)
? `/api/users?page=${result.page + 1}&limit=${result.limit}`
: null,
prev: result.page > 1
? `/api/users?page=${result.page - 1}&limit=${result.limit}`
: null,
},
});
} catch (error) {
next(error);
}
}
private async getUser(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const user = await this.userService.findById(req.params.id);
if (!user) {
res.status(404).json({
error: 'Not Found',
message: 'User not found',
statusCode: 404,
});
return;
}
res.status(200).json({
data: user,
links: {
self: `/api/users/${user.id}`,
posts: `/api/users/${user.id}/posts`,
comments: `/api/users/${user.id}/comments`,
},
});
} catch (error) {
next(error);
}
}
private async createUser(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const user = await this.userService.create(req.body);
res.status(201)
.location(`/api/users/${user.id}`)
.json({
data: user,
links: {
self: `/api/users/${user.id}`,
},
});
} catch (error) {
next(error);
}
}
private async updateUser(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const user = await this.userService.update(req.params.id, req.body);
if (!user) {
res.status(404).json({
error: 'Not Found',
message: 'User not found',
statusCode: 404,
});
return;
}
res.status(200).json({
data: user,
});
} catch (error) {
next(error);
}
}
private async patchUser(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const user = await this.userService.patch(req.params.id, req.body);
if (!user) {
res.status(404).json({
error: 'Not Found',
message: 'User not found',
statusCode: 404,
});
return;
}
res.status(200).json({
data: user,
});
} catch (error) {
next(error);
}
}
private async deleteUser(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const deleted = await this.userService.delete(req.params.id);
if (!deleted) {
res.status(404).json({
error: 'Not Found',
message: 'User not found',
statusCode: 404,
});
return;
}
res.status(204).send();
} catch (error) {
next(error);
}
}
}
Implement Consistent Error Handling:
Create standardized error responses across all endpoints:
// src/middleware/error-handler.ts
import { Request, Response, NextFunction } from 'express';
export class AppError extends Error {
constructor(
public statusCode: number,
public message: string,
public isOperational = true,
public errors?: Record<string, string[]>
) {
super(message);
Object.setPrototypeOf(this, AppError.prototype);
}
}
export function errorHandler(
err: Error | AppError,
req: Request,
res: Response,
next: NextFunction
): void {
if (err instanceof AppError) {
res.status(err.statusCode).json({
error: err.message,
statusCode: err.statusCode,
errors: err.errors,
path: req.path,
method: req.method,
timestamp: new Date().toISOString(),
});
return;
}
// Unhandled errors
console.error('Unhandled error:', err);
res.status(500).json({
error: 'Internal Server Error',
statusCode: 500,
message: process.env.NODE_ENV === 'development' ? err.message : 'An unexpected error occurred',
path: req.path,
method: req.method,
timestamp: new Date().toISOString(),
});
}
Design Type-Safe GraphQL Schema:
Implement GraphQL APIs with proper type definitions and resolvers:
// src/graphql/schema.ts
import { gql } from 'apollo-server-express';
export const typeDefs = gql`
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: DateTime!
updatedAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
published: Boolean!
createdAt: DateTime!
updatedAt: DateTime!
}
type Comment {
id: ID!
content: String!
author: User!
post: Post!
createdAt: DateTime!
}
type Query {
user(id: ID!): User
users(
page: Int = 1
limit: Int = 20
search: String
): UserConnection!
post(id: ID!): Post
posts(
page: Int = 1
limit: Int = 20
published: Boolean
): PostConnection!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
createPost(input: CreatePostInput!): Post!
publishPost(id: ID!): Post!
createComment(input: CreateCommentInput!): Comment!
}
type Subscription {
postCreated: Post!
commentAdded(postId: ID!): Comment!
}
input CreateUserInput {
name: String!
email: String!
password: String!
}
input UpdateUserInput {
name: String
email: String
}
input CreatePostInput {
title: String!
content: String!
published: Boolean = false
}
input CreateCommentInput {
postId: ID!
content: String!
}
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type UserEdge {
node: User!
cursor: String!
}
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type PostEdge {
node: Post!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
scalar DateTime
`;
// src/graphql/resolvers.ts
import { UserService } from '../services/user.service';
import { PostService } from '../services/post.service';
import { CommentService } from '../services/comment.service';
import { PubSub } from 'graphql-subscriptions';
const pubsub = new PubSub();
export const resolvers = {
Query: {
user: async (_parent: any, { id }: { id: string }, { services }: any) => {
return services.user.findById(id);
},
users: async (_parent: any, args: any, { services }: any) => {
const result = await services.user.findAll(args);
return {
edges: result.users.map((user: any) => ({
node: user,
cursor: Buffer.from(user.id).toString('base64'),
})),
pageInfo: {
hasNextPage: result.page * result.limit < result.total,
hasPreviousPage: result.page > 1,
startCursor: result.users.length > 0
? Buffer.from(result.users[0].id).toString('base64')
: null,
endCursor: result.users.length > 0
? Buffer.from(result.users[result.users.length - 1].id).toString('base64')
: null,
},
totalCount: result.total,
};
},
post: async (_parent: any, { id }: { id: string }, { services }: any) => {
return services.post.findById(id);
},
},
Mutation: {
createUser: async (_parent: any, { input }: any, { services }: any) => {
return services.user.create(input);
},
createPost: async (_parent: any, { input }: any, { services, user }: any) => {
if (!user) {
throw new Error('Authentication required');
}
const post = await services.post.create({
...input,
authorId: user.id,
});
pubsub.publish('POST_CREATED', { postCreated: post });
return post;
},
createComment: async (_parent: any, { input }: any, { services, user }: any) => {
if (!user) {
throw new Error('Authentication required');
}
const comment = await services.comment.create({
...input,
authorId: user.id,
});
pubsub.publish(`COMMENT_ADDED_${input.postId}`, { commentAdded: comment });
return comment;
},
},
Subscription: {
postCreated: {
subscribe: () => pubsub.asyncIterator(['POST_CREATED']),
},
commentAdded: {
subscribe: (_parent: any, { postId }: { postId: string }) =>
pubsub.asyncIterator([`COMMENT_ADDED_${postId}`]),
},
},
User: {
posts: async (user: any, _args: any, { services }: any) => {
return services.post.findByAuthorId(user.id);
},
},
Post: {
author: async (post: any, _args: any, { services }: any) => {
return services.user.findById(post.authorId);
},
comments: async (post: any, _args: any, { services }: any) => {
return services.comment.findByPostId(post.id);
},
},
Comment: {
author: async (comment: any, _args: any, { services }: any) => {
return services.user.findById(comment.authorId);
},
post: async (comment: any, _args: any, { services }: any) => {
return services.post.findById(comment.postId);
},
},
};
Implement Connection Pooling:
Configure efficient database connection management:
// src/database/connection.ts
import { Pool, PoolConfig } from 'pg';
import { createPool as createMySQLPool, PoolOptions } from 'mysql2/promise';
export class DatabaseConnection {
private static pgPool: Pool;
private static mysqlPool: any;
static initializePostgres(config: PoolConfig): Pool {
this.pgPool = new Pool({
host: config.host || process.env.DB_HOST,
port: config.port || Number(process.env.DB_PORT) || 5432,
database: config.database || process.env.DB_NAME,
user: config.user || process.env.DB_USER,
password: config.password || process.env.DB_PASSWORD,
max: 20, // Maximum pool size
min: 5, // Minimum pool size
idleTimeoutMillis: 30000, // Close idle clients after 30s
connectionTimeoutMillis: 2000, // Timeout connection attempts after 2s
maxUses: 7500, // Close connections after 7500 uses
});
// Handle pool errors
this.pgPool.on('error', (err) => {
console.error('Unexpected database error:', err);
});
return this.pgPool;
}
static initializeMySQL(config: PoolOptions): any {
this.mysqlPool = createMySQLPool({
host: config.host || process.env.DB_HOST,
port: config.port || Number(process.env.DB_PORT) || 3306,
database: config.database || process.env.DB_NAME,
user: config.user || process.env.DB_USER,
password: config.password || process.env.DB_PASSWORD,
connectionLimit: 20,
queueLimit: 0,
waitForConnections: true,
enableKeepAlive: true,
keepAliveInitialDelay: 0,
});
return this.mysqlPool;
}
static getPostgresPool(): Pool {
if (!this.pgPool) {
throw new Error('PostgreSQL pool not initialized');
}
return this.pgPool;
}
static async closeAll(): Promise<void> {
if (this.pgPool) {
await this.pgPool.end();
}
if (this.mysqlPool) {
await this.mysqlPool.end();
}
}
}
// src/repositories/user.repository.ts
import { Pool } from 'pg';
import { DatabaseConnection } from '../database/connection';
export class UserRepository {
private pool: Pool;
constructor() {
this.pool = DatabaseConnection.getPostgresPool();
}
async findById(id: string): Promise<any> {
const client = await this.pool.connect();
try {
const result = await client.query(
'SELECT * FROM users WHERE id = $1',
[id]
);
return result.rows[0];
} finally {
client.release();
}
}
async findAll(options: any): Promise<any> {
const client = await this.pool.connect();
try {
const offset = (options.page - 1) * options.limit;
let query = 'SELECT * FROM users';
let countQuery = 'SELECT COUNT(*) FROM users';
const params: any[] = [];
if (options.search) {
const searchCondition = ' WHERE name ILIKE $1 OR email ILIKE $1';
query += searchCondition;
countQuery += searchCondition;
params.push(`%${options.search}%`);
}
query += ` ORDER BY ${options.sort} ${options.order}`;
query += ` LIMIT $${params.length + 1} OFFSET $${params.length + 2}`;
params.push(options.limit, offset);
const [users, count] = await Promise.all([
client.query(query, params),
client.query(countQuery, params.length > 0 ? [params[0]] : []),
]);
return {
users: users.rows,
total: Number(count.rows[0].count),
page: options.page,
limit: options.limit,
};
} finally {
client.release();
}
}
async create(user: any): Promise<any> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
const result = await client.query(
'INSERT INTO users (name, email, password_hash) VALUES ($1, $2, $3) RETURNING *',
[user.name, user.email, user.passwordHash]
);
await client.query('COMMIT');
return result.rows[0];
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}
}
}
Implement Query Optimization:
Use prepared statements and indexes for performance:
// src/repositories/optimized-queries.ts
export class OptimizedQueryRepository {
async batchFindByIds(ids: string[]): Promise<any[]> {
// Use IN clause for batch retrieval
const client = await this.pool.connect();
try {
const result = await client.query(
'SELECT * FROM users WHERE id = ANY($1::uuid[])',
[ids]
);
return result.rows;
} finally {
client.release();
}
}
async findWithJoins(userId: string): Promise<any> {
// Optimize joins with indexes
const client = await this.pool.connect();
try {
const result = await client.query(`
SELECT
u.*,
json_agg(
json_build_object(
'id', p.id,
'title', p.title,
'createdAt', p.created_at
)
) FILTER (WHERE p.id IS NOT NULL) as posts
FROM users u
LEFT JOIN posts p ON p.author_id = u.id
WHERE u.id = $1
GROUP BY u.id
`, [userId]);
return result.rows[0];
} finally {
client.release();
}
}
}
// Create indexes for performance
// migrations/001_add_indexes.sql
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_posts_author_id ON posts(author_id);
CREATE INDEX idx_posts_created_at ON posts(created_at DESC);
CREATE INDEX idx_comments_post_id ON comments(post_id);
Implement Message Queue Integration:
Use message queues for asynchronous processing:
// src/queues/job.queue.ts
import Bull, { Queue, Job } from 'bull';
import Redis from 'ioredis';
export interface JobData {
type: string;
payload: any;
}
export class JobQueue {
private queue: Queue<JobData>;
private redis: Redis;
constructor(queueName: string) {
this.redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: Number(process.env.REDIS_PORT) || 6379,
maxRetriesPerRequest: null,
enableReadyCheck: false,
});
this.queue = new Bull<JobData>(queueName, {
redis: this.redis,
defaultJobOptions: {
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000,
},
removeOnComplete: 100,
removeOnFail: 500,
},
});
this.setupEventHandlers();
}
private setupEventHandlers(): void {
this.queue.on('error', (error) => {
console.error('Queue error:', error);
});
this.queue.on('failed', (job, error) => {
console.error(`Job ${job.id} failed:`, error);
});
this.queue.on('completed', (job) => {
console.log(`Job ${job.id} completed`);
});
}
async addJob(data: JobData, options?: Bull.JobOptions): Promise<Job<JobData>> {
return this.queue.add(data, options);
}
async process(concurrency: number, processor: (job: Job<JobData>) => Promise<any>): Promise<void> {
this.queue.process(concurrency, processor);
}
async close(): Promise<void> {
await this.queue.close();
this.redis.disconnect();
}
}
// src/workers/email.worker.ts
import { JobQueue } from '../queues/job.queue';
import { EmailService } from '../services/email.service';
export class EmailWorker {
private queue: JobQueue;
private emailService: EmailService;
constructor() {
this.queue = new JobQueue('email');
this.emailService = new EmailService();
this.start();
}
private start(): void {
this.queue.process(5, async (job) => {
const { type, payload } = job.data;
switch (type) {
case 'welcome':
await this.emailService.sendWelcomeEmail(payload);
break;
case 'password-reset':
await this.emailService.sendPasswordResetEmail(payload);
break;
case 'notification':
await this.emailService.sendNotification(payload);
break;
default:
throw new Error(`Unknown email type: ${type}`);
}
});
}
}
Implement Webhook Processing:
Handle incoming webhooks with retry logic:
// src/webhooks/webhook.handler.ts
import { Request, Response } from 'express';
import crypto from 'crypto';
export class WebhookHandler {
async handleStripeWebhook(req: Request, res: Response): Promise<void> {
const signature = req.headers['stripe-signature'] as string;
try {
// Verify webhook signature
this.verifyStripeSignature(req.rawBody, signature);
const event = req.body;
// Process event asynchronously
await this.processStripeEvent(event);
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook error:', error);
res.status(400).json({ error: 'Webhook validation failed' });
}
}
private verifyStripeSignature(payload: string, signature: string): void {
const secret = process.env.STRIPE_WEBHOOK_SECRET!;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (signature !== expectedSignature) {
throw new Error('Invalid signature');
}
}
private async processStripeEvent(event: any): Promise<void> {
switch (event.type) {
case 'payment_intent.succeeded':
await this.handlePaymentSuccess(event.data.object);
break;
case 'payment_intent.failed':
await this.handlePaymentFailure(event.data.object);
break;
case 'customer.subscription.updated':
await this.handleSubscriptionUpdate(event.data.object);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
}
}
Implement Exponential Backoff:
Handle transient failures with intelligent retry logic:
// src/utils/retry.ts
export interface RetryOptions {
maxAttempts: number;
initialDelay: number;
maxDelay: number;
backoffMultiplier: number;
retryableErrors?: string[];
}
export async function retryWithBackoff<T>(
operation: () => Promise<T>,
options: RetryOptions
): Promise<T> {
let lastError: Error;
let delay = options.initialDelay;
for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
try {
return await operation();
} catch (error: any) {
lastError = error;
// Check if error is retryable
if (options.retryableErrors &&
!options.retryableErrors.includes(error.code)) {
throw error;
}
// Don't retry on last attempt
if (attempt === options.maxAttempts) {
break;
}
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
// Calculate next delay with exponential backoff
delay = Math.min(
delay * options.backoffMultiplier,
options.maxDelay
);
}
}
throw lastError!;
}
// Usage example
async function fetchUserWithRetry(userId: string): Promise<User> {
return retryWithBackoff(
() => userService.findById(userId),
{
maxAttempts: 3,
initialDelay: 1000,
maxDelay: 10000,
backoffMultiplier: 2,
retryableErrors: ['ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND'],
}
);
}