本文へ移動
cccskills
無料GitHub で公開日本語紹介

backend-patterns

Node.js、Express、Next.jsのAPIとデータ処理を設計・レビューするスキル。DBへの問い合わせ、キャッシュ、認証、エラー処理を整理します。

原文原文の説明を見る

Backend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes. Use when building or reviewing Node.js, Express, or Next.js API routes and their data access.

インストール方法を見る

こんなときに便利

  • API設計と業務処理・データ取得の分離
  • 重複するDB問い合わせを減らしたいとき
  • キャッシュと非同期処理の導入
  • 認証・権限・エラー処理のレビュー

日本語での紹介

できること

Node.js、Express、Next.jsのAPIや、サーバー側のデータ処理を設計・レビューします。業務上の処理とデータ取得を分ける構成、必要な項目だけを取得する問い合わせ、関連データの一括取得などの実装例を示します。キャッシュ、入力検証、認証と権限管理、エラー処理、バックグラウンド処理、ログの整理も扱います。

こんなときに便利

APIを新しく作るときや、処理が増えてコードの役割が分かりにくくなったときに向いています。データベースへの問い合わせが多い処理を見直したい場合や、複数のAPIでエラーの返し方、認証、ログ形式をそろえたい場合にも使えます。

使い方の例

  • 「このAPIの業務処理とデータ取得を分け、責任範囲を整理して」
  • 「一覧取得で関連データを何度も問い合わせているので、一括取得に見直して」
  • 「認証と権限チェック、入力検証、エラー処理をレビューして」

注意点

Node.js、Express、Next.jsのAPI routesを主な対象としています。本番APIのアクセス頻度制限には、Redisなどの共有ストア、ゲートウェイ、またはプラットフォームの制限機能を使う方針です。プロセス内だけのカウンターは本番用途に使いません。

この紹介文は、公開されている SKILL.md をもとに AI(Claude Haiku)が作成しました。正確な仕様は下の原文を確認してください。

含まれるファイル(1)

  • SKILL.md13.0 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Backend Development Patterns

Backend architecture patterns and best practices for scalable server-side applications.

When to Activate

  • Designing REST or GraphQL API endpoints
  • Implementing repository, service, or controller layers
  • Optimizing database queries (N+1, indexing, connection pooling)
  • Adding caching (Redis, in-memory, HTTP cache headers)
  • Setting up background jobs or async processing
  • Structuring error handling and validation for APIs
  • Building middleware (auth, logging, rate limiting)

API Design Patterns

RESTful API Structure

// PASS: Resource-based URLs
GET    /api/markets                 # List resources
GET    /api/markets/:id             # Get single resource
POST   /api/markets                 # Create resource
PUT    /api/markets/:id             # Replace resource
PATCH  /api/markets/:id             # Update resource
DELETE /api/markets/:id             # Delete resource

// PASS: Query parameters for filtering, sorting, pagination
GET /api/markets?status=active&sort=volume&limit=20&offset=0

Repository Pattern

// Abstract data access logic
interface MarketRepository {
  findAll(filters?: MarketFilters): Promise<Market[]>
  findById(id: string): Promise<Market | null>
  create(data: CreateMarketDto): Promise<Market>
  update(id: string, data: UpdateMarketDto): Promise<Market>
  delete(id: string): Promise<void>
}

class SupabaseMarketRepository implements MarketRepository {
  async findAll(filters?: MarketFilters): Promise<Market[]> {
    let query = supabase.from('markets').select('*')

    if (filters?.status) {
      query = query.eq('status', filters.status)
    }

    if (filters?.limit) {
      query = query.limit(filters.limit)
    }

    const { data, error } = await query

    if (error) throw new Error(error.message)
    return data
  }

  // Other methods...
}

Service Layer Pattern

// Business logic separated from data access
class MarketService {
  constructor(private marketRepo: MarketRepository) {}

  async searchMarkets(query: string, limit: number = 10): Promise<Market[]> {
    // Business logic
    const embedding = await generateEmbedding(query)
    const results = await this.vectorSearch(embedding, limit)

    // Fetch full data
    const markets = await this.marketRepo.findByIds(results.map(r => r.id))

    // Sort by similarity
    return markets.sort((a, b) => {
      const scoreA = results.find(r => r.id === a.id)?.score || 0
      const scoreB = results.find(r => r.id === b.id)?.score || 0
      return scoreA - scoreB
    })
  }

  private async vectorSearch(embedding: number[], limit: number) {
    // Vector search implementation
  }
}

Middleware Pattern

// Request/response processing pipeline
export function withAuth(handler: NextApiHandler): NextApiHandler {
  return async (req, res) => {
    const token = req.headers.authorization?.replace('Bearer ', '')

    if (!token) {
      return res.status(401).json({ error: 'Unauthorized' })
    }

    try {
      const user = await verifyToken(token)
      req.user = user
      return handler(req, res)
    } catch (error) {
      return res.status(401).json({ error: 'Invalid token' })
    }
  }
}

// Usage
export default withAuth(async (req, res) => {
  // Handler has access to req.user
})

Database Patterns

Query Optimization

// PASS: GOOD: Select only needed columns
const { data } = await supabase
  .from('markets')
  .select('id, name, status, volume')
  .eq('status', 'active')
  .order('volume', { ascending: false })
  .limit(10)

// FAIL: BAD: Select everything
const { data } = await supabase
  .from('markets')
  .select('*')

N+1 Query Prevention

// FAIL: BAD: N+1 query problem
const markets = await getMarkets()
for (const market of markets) {
  market.creator = await getUser(market.creator_id)  // N queries
}

// PASS: GOOD: Batch fetch
const markets = await getMarkets()
const creatorIds = markets.map(m => m.creator_id)
const creators = await getUsers(creatorIds)  // 1 query
const creatorMap = new Map(creators.map(c => [c.id, c]))

markets.forEach(market => {
  market.creator = creatorMap.get(market.creator_id)
})

Transaction Pattern

async function createMarketWithPosition(
  marketData: CreateMarketDto,
  positionData: CreatePositionDto
) {
  // Use Supabase transaction
  const { data, error } = await supabase.rpc('create_market_with_position', {
    market_data: marketData,
    position_data: positionData
  })

  if (error) throw new Error('Transaction failed')
  return data
}

// SQL function in Supabase
CREATE OR REPLACE FUNCTION create_market_with_position(
  market_data jsonb,
  position_data jsonb
)
RETURNS jsonb
LANGUAGE plpgsql
AS $$
BEGIN
  -- Start transaction automatically
  INSERT INTO markets VALUES (market_data);
  INSERT INTO positions VALUES (position_data);
  RETURN jsonb_build_object('success', true);
EXCEPTION
  WHEN OTHERS THEN
    -- Rollback happens automatically
    RETURN jsonb_build_object('success', false, 'error', SQLERRM);
END;
$$;

Caching Strategies

Redis Caching Layer

class CachedMarketRepository implements MarketRepository {
  constructor(
    private baseRepo: MarketRepository,
    private redis: RedisClient
  ) {}

  async findById(id: string): Promise<Market | null> {
    // Check cache first
    const cached = await this.redis.get(`market:${id}`)

    if (cached) {
      return JSON.parse(cached)
    }

    // Cache miss - fetch from database
    const market = await this.baseRepo.findById(id)

    if (market) {
      // Cache for 5 minutes
      await this.redis.setex(`market:${id}`, 300, JSON.stringify(market))
    }

    return market
  }

  async invalidateCache(id: string): Promise<void> {
    await this.redis.del(`market:${id}`)
  }
}

Cache-Aside Pattern

async function getMarketWithCache(id: string): Promise<Market> {
  const cacheKey = `market:${id}`

  // Try cache
  const cached = await redis.get(cacheKey)
  if (cached) return JSON.parse(cached)

  // Cache miss - fetch from DB
  const market = await db.markets.findUnique({ where: { id } })

  if (!market) throw new Error('Market not found')

  // Update cache
  await redis.setex(cacheKey, 300, JSON.stringify(market))

  return market
}

Error Handling Patterns

Centralized Error Handler

class ApiError extends Error {
  constructor(
    public statusCode: number,
    public message: string,
    public isOperational = true
  ) {
    super(message)
    Object.setPrototypeOf(this, ApiError.prototype)
  }
}

export function errorHandler(error: unknown, req: Request): Response {
  if (error instanceof ApiError) {
    return NextResponse.json({
      success: false,
      error: error.message
    }, { status: error.statusCode })
  }

  if (error instanceof z.ZodError) {
    return NextResponse.json({
      success: false,
      error: 'Validation failed',
      details: error.issues
    }, { status: 400 })
  }

  // Log unexpected errors
  console.error('Unexpected error:', error)

  return NextResponse.json({
    success: false,
    error: 'Internal server error'
  }, { status: 500 })
}

// Usage
export async function GET(request: Request) {
  try {
    const data = await fetchData()
    return NextResponse.json({ success: true, data })
  } catch (error) {
    return errorHandler(error, request)
  }
}

Retry with Exponential Backoff

async function fetchWithRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3
): Promise<T> {
  let lastError: Error

  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn()
    } catch (error) {
      lastError = error as Error

      if (i < maxRetries - 1) {
        // Exponential backoff: 1s, 2s, 4s
        const delay = Math.pow(2, i) * 1000
        await new Promise(resolve => setTimeout(resolve, delay))
      }
    }
  }

  throw lastError!
}

// Usage
const data = await fetchWithRetry(() => fetchFromAPI())

Authentication & Authorization

JWT Token Validation

import jwt from 'jsonwebtoken'

interface JWTPayload {
  userId: string
  email: string
  role: 'admin' | 'user'
}

export function verifyToken(token: string): JWTPayload {
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload
    return payload
  } catch (error) {
    throw new ApiError(401, 'Invalid token')
  }
}

export async function requireAuth(request: Request) {
  const token = request.headers.get('authorization')?.replace('Bearer ', '')

  if (!token) {
    throw new ApiError(401, 'Missing authorization token')
  }

  return verifyToken(token)
}

// Usage in API route
export async function GET(request: Request) {
  const user = await requireAuth(request)

  const data = await getDataForUser(user.userId)

  return NextResponse.json({ success: true, data })
}

Role-Based Access Control

type Permission = 'read' | 'write' | 'delete' | 'admin'

interface User {
  id: string
  role: 'admin' | 'moderator' | 'user'
}

const rolePermissions: Record<User['role'], Permission[]> = {
  admin: ['read', 'write', 'delete', 'admin'],
  moderator: ['read', 'write', 'delete'],
  user: ['read', 'write']
}

export function hasPermission(user: User, permission: Permission): boolean {
  return rolePermissions[user.role].includes(permission)
}

export function requirePermission(permission: Permission) {
  return (handler: (request: Request, user: User) => Promise<Response>) => {
    return async (request: Request) => {
      const user = await requireAuth(request)

      if (!hasPermission(user, permission)) {
        throw new ApiError(403, 'Insufficient permissions')
      }

      return handler(request, user)
    }
  }
}

// Usage - HOF wraps the handler
export const DELETE = requirePermission('delete')(
  async (request: Request, user: User) => {
    // Handler receives authenticated user with verified permission
    return new Response('Deleted', { status: 200 })
  }
)

Rate Limiting

Rate limiting must use a shared store such as Redis, a gateway, or the platform's native limiter. Do not use per-process in-memory counters for production APIs: they reset on deploy, split across replicas, and fail open in serverless or multi-instance environments.

Keep the backend layer responsible for choosing the integration point and error shape; use api-design for the HTTP contract and security-review for abuse case review.

Background Jobs & Queues

Simple Queue Pattern

class JobQueue<T> {
  private queue: T[] = []
  private processing = false

  async add(job: T): Promise<void> {
    this.queue.push(job)

    if (!this.processing) {
      this.process()
    }
  }

  private async process(): Promise<void> {
    this.processing = true

    while (this.queue.length > 0) {
      const job = this.queue.shift()!

      try {
        await this.execute(job)
      } catch (error) {
        console.error('Job failed:', error)
      }
    }

    this.processing = false
  }

  private async execute(job: T): Promise<void> {
    // Job execution logic
  }
}

// Usage for indexing markets
interface IndexJob {
  marketId: string
}

const indexQueue = new JobQueue<IndexJob>()

export async function POST(request: Request) {
  const { marketId } = await request.json()

  // Add to queue instead of blocking
  await indexQueue.add({ marketId })

  return NextResponse.json({ success: true, message: 'Job queued' })
}

Logging & Monitoring

Structured Logging

interface LogContext {
  userId?: string
  requestId?: string
  method?: string
  path?: string
  [key: string]: unknown
}

class Logger {
  log(level: 'info' | 'warn' | 'error', message: string, context?: LogContext) {
    const entry = {
      timestamp: new Date().toISOString(),
      level,
      message,
      ...context
    }

    console.log(JSON.stringify(entry))
  }

  info(message: string, context?: LogContext) {
    this.log('info', message, context)
  }

  warn(message: string, context?: LogContext) {
    this.log('warn', message, context)
  }

  error(message: string, error: Error, context?: LogContext) {
    this.log('error', message, {
      ...context,
      error: error.message,
      stack: error.stack
    })
  }
}

const logger = new Logger()

// Usage
export async function GET(request: Request) {
  const requestId = crypto.randomUUID()

  logger.info('Fetching markets', {
    requestId,
    method: 'GET',
    path: '/api/markets'
  })

  try {
    const markets = await fetchMarkets()
    return NextResponse.json({ success: true, data: markets })
  } catch (error) {
    logger.error('Failed to fetch markets', error as Error, { requestId })
    return NextResponse.json({ error: 'Internal error' }, { status: 500 })
  }
}

Remember: Backend patterns enable scalable, maintainable server-side applications. Choose patterns that fit your complexity level.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

accessibility

無料日本語概要

Web・iOS・Androidの画面を、読み上げやキーボード操作に対応させ、ラベル、配色、操作対象の大きさなどをWCAG 2.2に沿って設計・点検するスキル。

  • アイコンボタンの説明を付けたいとき
  • キーボード操作とモーダルの点検
  • コントラストや操作対象の大きさの確認
affaan-m/ECC27.7万2026年10月12日 更新

agent-architecture-audit

無料日本語概要

AIエージェントの不調を、指示・記憶・ツール実行・画面表示など12の層から調べるスキル。コードやログを根拠に原因を整理し、重要度順の指摘と修正案をまとめます。

  • アプリ内だけで起きる不調を調べたいとき
  • 過去の会話が混ざる原因を調査
  • ツールの未実行や実行の誤報を確認
affaan-m/ECC27.7万2026年10月12日 更新

agent-eval

無料日本語概要

実際の開発課題で複数のコーディングエージェントを比較するスキル。成功率、取得可能なAPI費用、所要時間、繰り返し実行の安定性を測り、選定や更新後の評価に使えます。

  • 実際の開発課題でエージェントを比較
  • 新しいツールやモデルの導入前評価
  • エージェント更新後の性能確認
affaan-m/ECC27.7万2026年10月12日 更新

agent-harness-construction

無料日本語概要

AIエージェントが使うツールの種類や入出力、エラーからの復帰手順を設計・見直します。文脈の情報量も整理し、作業完了率や再試行回数で改善を評価します。

  • エージェントのツールや入力形式の設計
  • ツールの結果と次の行動を明確にしたいとき
  • 安全な再試行と停止条件を定めたいとき
affaan-m/ECC27.7万2026年10月12日 更新

agent-introspection-debugging

無料日本語概要

AIエージェントが失敗や同じ操作を繰り返す原因を、エラーと実行状況から整理します。小さな復旧操作を試し、結果と根拠を引き継げる報告にまとめるスキルです。

  • エージェントの連続失敗を診断したいとき
  • 同じツール操作のループ調査
  • 情報の蓄積による出力劣化の点検
affaan-m/ECC27.7万2026年10月5日 更新

agent-introspection-debugging

無料日本語概要

AIエージェントの失敗や同じ操作の繰り返しを記録し、原因の切り分け、小さな復旧操作、結果の報告まで進める手順を示して、根拠のある再試行につなげるスキル。

  • 同じツール操作を繰り返す原因の調査
  • 会話の肥大化による品質低下の調査
  • ファイルパスや環境の食い違いの確認
affaan-m/ECC27.7万2026年10月12日 更新

affaan-m のスキルをすべて見る

このスキルの問題を報告する