backend-api-patterns

This skill provides backend and API implementation patterns for building robust, scalable services.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "backend-api-patterns" with this command: npx skills add duyet/claude-plugins/duyet-claude-plugins-backend-api-patterns

This skill provides backend and API implementation patterns for building robust, scalable services.

When to Invoke This Skill

Automatically activate for:

  • API endpoint implementation

  • Database operations and queries

  • Authentication and authorization

  • Caching and performance optimization

  • Service architecture design

API Design Patterns

Consistent Response Structure

// Standard API response envelope interface ApiResponse<T> { data?: T; error?: { code: string; message: string; details?: Record<string, unknown>; }; meta?: { pagination?: { page: number; pageSize: number; total: number; totalPages: number; }; timestamp?: string; requestId?: string; }; }

// Success response helper function success<T>(data: T, meta?: ApiResponse<T>['meta']): ApiResponse<T> { return { data, meta }; }

// Error response helper function error( code: string, message: string, details?: Record<string, unknown> ): ApiResponse<never> { return { error: { code, message, details } }; }

// Paginated response helper function paginated<T>( data: T[], page: number, pageSize: number, total: number ): ApiResponse<T[]> { return { data, meta: { pagination: { page, pageSize, total, totalPages: Math.ceil(total / pageSize), }, }, }; }

Route Handler Pattern

// Generic handler wrapper with error handling type Handler<T> = ( req: Request, context: { params: Record<string, string> } ) => Promise<T>;

function createHandler<T>(handler: Handler<T>) { return async (req: Request, context: { params: Record<string, string> }) => { const requestId = crypto.randomUUID();

try {
  const result = await handler(req, context);
  return Response.json(success(result, { requestId }));
} catch (err) {
  if (err instanceof AppError) {
    return Response.json(
      error(err.code, err.message),
      { status: err.statusCode }
    );
  }

  console.error(`[${requestId}] Unexpected error:`, err);
  return Response.json(
    error('INTERNAL_ERROR', 'An unexpected error occurred'),
    { status: 500 }
  );
}

}; }

// Usage export const GET = createHandler(async (req, { params }) => { const user = await userService.findById(params.id); if (!user) throw new NotFoundError('User', params.id); return user; });

Service Layer Pattern

Repository Pattern

interface Repository<T, ID = string> { findById(id: ID): Promise<T | null>; findMany(options: FindOptions<T>): Promise<T[]>; count(filter?: Partial<T>): Promise<number>; create(data: CreateInput<T>): Promise<T>; update(id: ID, data: UpdateInput<T>): Promise<T>; delete(id: ID): Promise<void>; }

interface FindOptions<T> { filter?: Partial<T>; orderBy?: keyof T; orderDir?: 'asc' | 'desc'; limit?: number; offset?: number; }

type CreateInput<T> = Omit<T, 'id' | 'createdAt' | 'updatedAt'>; type UpdateInput<T> = Partial<Omit<T, 'id' | 'createdAt' | 'updatedAt'>>;

// Implementation class UserRepository implements Repository<User> { constructor(private db: Database) {}

async findById(id: string): Promise<User | null> { return this.db.query.users.findFirst({ where: eq(users.id, id), }); }

async findMany(options: FindOptions<User>): Promise<User[]> { const { filter, orderBy, orderDir = 'asc', limit, offset } = options;

return this.db.query.users.findMany({
  where: filter ? this.buildWhere(filter) : undefined,
  orderBy: orderBy ? (orderDir === 'asc' ? asc : desc)(users[orderBy]) : undefined,
  limit,
  offset,
});

}

// ... other methods }

Service with Business Logic

class UserService { constructor( private userRepo: Repository<User>, private cache: Cache, private eventBus: EventBus ) {}

async getUser(id: string): Promise<User> { // Check cache first const cached = await this.cache.get<User>(user:${id}); if (cached) return cached;

// Fetch from database
const user = await this.userRepo.findById(id);
if (!user) throw new NotFoundError('User', id);

// Cache for future requests
await this.cache.set(`user:${id}`, user, { ttl: 3600 });

return user;

}

async createUser(input: CreateUserInput): Promise<User> { // Validate const existing = await this.userRepo.findMany({ filter: { email: input.email }, limit: 1, }); if (existing.length > 0) { throw new ValidationError('Email already exists', { email: 'Already in use' }); }

// Hash password
const hashedPassword = await hashPassword(input.password);

// Create user
const user = await this.userRepo.create({
  ...input,
  password: hashedPassword,
});

// Emit event for side effects
await this.eventBus.emit('user.created', { userId: user.id });

return user;

}

async updateUser(id: string, input: UpdateUserInput): Promise<User> { const user = await this.userRepo.update(id, input);

// Invalidate cache
await this.cache.delete(`user:${id}`);

return user;

} }

Authentication Patterns

JWT with Refresh Tokens

interface TokenPair { accessToken: string; // Short-lived: 15 minutes refreshToken: string; // Long-lived: 7 days }

interface TokenPayload { sub: string; // User ID email: string; roles: string[]; type: 'access' | 'refresh'; }

class AuthService { constructor( private userRepo: Repository<User>, private tokenRepo: Repository<RefreshToken>, private jwtSecret: string ) {}

async login(email: string, password: string): Promise<TokenPair> { const user = await this.userRepo.findMany({ filter: { email }, limit: 1, });

if (!user[0] || !await verifyPassword(password, user[0].password)) {
  throw new UnauthorizedError('Invalid credentials');
}

return this.generateTokenPair(user[0]);

}

async refresh(refreshToken: string): Promise<TokenPair> { // Verify token const payload = this.verifyToken(refreshToken); if (payload.type !== 'refresh') { throw new UnauthorizedError('Invalid token type'); }

// Check if token is revoked
const stored = await this.tokenRepo.findById(refreshToken);
if (!stored || stored.revoked) {
  throw new UnauthorizedError('Token revoked');
}

// Get user and generate new tokens
const user = await this.userRepo.findById(payload.sub);
if (!user) throw new UnauthorizedError('User not found');

// Revoke old refresh token
await this.tokenRepo.update(refreshToken, { revoked: true });

return this.generateTokenPair(user);

}

private generateTokenPair(user: User): TokenPair { const accessToken = jwt.sign( { sub: user.id, email: user.email, roles: user.roles, type: 'access' }, this.jwtSecret, { expiresIn: '15m' } );

const refreshToken = jwt.sign(
  { sub: user.id, type: 'refresh' },
  this.jwtSecret,
  { expiresIn: '7d' }
);

return { accessToken, refreshToken };

}

private verifyToken(token: string): TokenPayload { try { return jwt.verify(token, this.jwtSecret) as TokenPayload; } catch { throw new UnauthorizedError('Invalid or expired token'); } } }

Middleware Pattern

type Middleware = (req: Request, next: () => Promise<Response>) => Promise<Response>;

// Auth middleware function authMiddleware(requiredRoles?: string[]): Middleware { return async (req, next) => { const token = req.headers.get('Authorization')?.replace('Bearer ', '');

if (!token) {
  return Response.json(
    error('UNAUTHORIZED', 'No token provided'),
    { status: 401 }
  );
}

try {
  const payload = verifyToken(token);

  if (requiredRoles?.length &#x26;&#x26; !requiredRoles.some(r => payload.roles.includes(r))) {
    return Response.json(
      error('FORBIDDEN', 'Insufficient permissions'),
      { status: 403 }
    );
  }

  // Attach user to request context
  (req as any).user = payload;

  return next();
} catch {
  return Response.json(
    error('UNAUTHORIZED', 'Invalid or expired token'),
    { status: 401 }
  );
}

}; }

// Rate limiting middleware function rateLimitMiddleware(limit: number, windowMs: number): Middleware { const requests = new Map<string, { count: number; resetAt: number }>();

return async (req, next) => { const ip = req.headers.get('x-forwarded-for') || 'unknown'; const now = Date.now();

const record = requests.get(ip);

if (!record || record.resetAt &#x3C; now) {
  requests.set(ip, { count: 1, resetAt: now + windowMs });
  return next();
}

if (record.count >= limit) {
  return Response.json(
    error('RATE_LIMITED', 'Too many requests'),
    { status: 429 }
  );
}

record.count++;
return next();

}; }

Database Patterns

Query Optimization

// Avoid N+1 queries with eager loading async function getUsersWithOrders(): Promise<UserWithOrders[]> { // BAD: N+1 queries const users = await db.query.users.findMany(); for (const user of users) { user.orders = await db.query.orders.findMany({ where: eq(orders.userId, user.id), }); }

// GOOD: Single query with join return db.query.users.findMany({ with: { orders: true, }, }); }

// Pagination with cursor async function paginateUsers(cursor?: string, limit = 20): Promise<{ users: User[]; nextCursor: string | null; }> { const users = await db.query.users.findMany({ where: cursor ? gt(users.id, cursor) : undefined, orderBy: asc(users.id), limit: limit + 1, // Fetch one extra to check for next page });

const hasMore = users.length > limit; const data = hasMore ? users.slice(0, -1) : users;

return { users: data, nextCursor: hasMore ? data[data.length - 1].id : null, }; }

Transaction Pattern

async function transferFunds( fromId: string, toId: string, amount: number ): Promise<void> { await db.transaction(async (tx) => { // Lock rows for update const from = await tx.query.accounts.findFirst({ where: eq(accounts.id, fromId), for: 'update', });

if (!from || from.balance &#x3C; amount) {
  throw new ValidationError('Insufficient funds', {});
}

// Debit source account
await tx.update(accounts)
  .set({ balance: from.balance - amount })
  .where(eq(accounts.id, fromId));

// Credit destination account
await tx.update(accounts)
  .set({ balance: sql`${accounts.balance} + ${amount}` })
  .where(eq(accounts.id, toId));

// Log transaction
await tx.insert(transactions).values({
  fromId,
  toId,
  amount,
  type: 'transfer',
});

}); }

Caching Patterns

Cache-Aside Pattern

class CachedUserService { constructor( private userRepo: Repository<User>, private cache: Cache ) {}

async getUser(id: string): Promise<User | null> { const cacheKey = user:${id};

// Try cache first
const cached = await this.cache.get&#x3C;User>(cacheKey);
if (cached) return cached;

// Fetch from database
const user = await this.userRepo.findById(id);

// Cache the result (including null to prevent cache stampede)
if (user) {
  await this.cache.set(cacheKey, user, { ttl: 3600 });
} else {
  await this.cache.set(cacheKey, null, { ttl: 60 }); // Short TTL for negative cache
}

return user;

}

async updateUser(id: string, data: UpdateUserInput): Promise<User> { const user = await this.userRepo.update(id, data);

// Invalidate cache
await this.cache.delete(`user:${id}`);

return user;

} }

Request Deduplication

class RequestDeduplicator { private pending = new Map<string, Promise<unknown>>();

async dedupe<T>(key: string, fetcher: () => Promise<T>): Promise<T> { // Return existing request if in flight const existing = this.pending.get(key); if (existing) return existing as Promise<T>;

// Start new request
const promise = fetcher().finally(() => {
  this.pending.delete(key);
});

this.pending.set(key, promise);
return promise;

} }

// Usage const deduplicator = new RequestDeduplicator();

async function getUser(id: string): Promise<User> { return deduplicator.dedupe(user:${id}, () => userRepo.findById(id)); }

Best Practices Checklist

  • Use consistent API response envelope

  • Implement proper error hierarchy and handling

  • Separate concerns: routes → services → repositories

  • Use transactions for multi-step operations

  • Implement caching with proper invalidation

  • Avoid N+1 queries with eager loading

  • Use cursor-based pagination for large datasets

  • Implement rate limiting and request deduplication

  • Validate inputs at API boundaries

  • Log with structured data and request IDs

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

react-nextjs-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

gemini-prompting

No summary provided by upstream source.

Repository SourceNeeds Review
General

frontend-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

transparency

No summary provided by upstream source.

Repository SourceNeeds Review