Backend Architecture Patterns
Улучшите разработку бэкенда с Claude Code. Освойте паттерны Node.js, Express и Next.js для проектирования API, оптимизации баз данных и безопасности.
Этот навык предоставляет Claude специализированные знания для проектирования надежных серверных приложений. Он даёт структурированные рекомендации по внедрению шаблонов Repository и Service для разделения бизнес-логики и доступа к данным, оптимизации производительности базы данных за счёт предотвращения N+1 запросов, а также защиты API с помощью JWT и управления доступом на основе ролей (RBAC). Строите ли вы высоконагруженный REST API или сложный бэкенд на Next.js — этот навык гарантирует, что ваш код будет соответствовать поддерживаемым, безопасным и производительным отраслевым стандартам.
Ключевые особенности
Варианты использования
| name | backend-patterns |
|---|---|
| description | Node.js, Express ve Next.js API routes için backend mimari kalıpları, API tasarımı, veritabanı optimizasyonu ve sunucu tarafı en iyi uygulamalar. |
| origin | ECC |
Ölçeklenebilir sunucu tarafı uygulamalar için backend mimari kalıpları ve en iyi uygulamalar.
- REST veya GraphQL API endpoint'leri tasarlarken
- Repository, service veya controller katmanları uygularken
- Veritabanı sorgularını optimize ederken (N+1, indeksleme, bağlantı havuzu)
- Önbellekleme eklerken (Redis, in-memory, HTTP cache başlıkları)
- Arka plan işleri veya async işleme ayarlarken
- API'ler için hata yönetimi ve doğrulama yapılandırırken
- Middleware oluştururken (auth, logging, rate limiting)
// PASS: Kaynak tabanlı URL'ler
GET /api/markets # Kaynakları listele
GET /api/markets/:id # Tek kaynak getir
POST /api/markets # Kaynak oluştur
PUT /api/markets/:id # Kaynağı değiştir (tam)
PATCH /api/markets/:id # Kaynağı güncelle (kısmi)
DELETE /api/markets/:id # Kaynağı sil
// PASS: Filtreleme, sıralama, sayfalama için query parametreleri
GET /api/markets?status=active&sort=volume&limit=20&offset=0// Veri erişim mantığını soyutla
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
}
// Diğer metodlar...
}// İş mantığı veri erişiminden ayrılmış
class MarketService {
constructor(private marketRepo: MarketRepository) {}
async searchMarkets(query: string, limit: number = 10): Promise<Market[]> {
// İş mantığı
const embedding = await generateEmbedding(query)
const results = await this.vectorSearch(embedding, limit)
// Tam veriyi getir
const markets = await this.marketRepo.findByIds(results.map(r => r.id))
// Benzerliğe göre sırala
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 arama implementasyonu
}
}// Request/response işleme hattı
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' })
}
}
}
// Kullanım
export default withAuth(async (req, res) => {
// Handler req.user'a erişebilir
})// PASS: İYİ: Sadece gerekli sütunları seç
const { data } = await supabase
.from('markets')
.select('id, name, status, volume')
.eq('status', 'active')
.order('volume', { ascending: false })
.limit(10)
// FAIL: KÖTÜ: Her şeyi seç
const { data } = await supabase
.from('markets')
.select('*')// FAIL: KÖTÜ: N+1 sorgu problemi
const markets = await getMarkets()
for (const market of markets) {
market.creator = await getUser(market.creator_id) // N sorgu
}
// PASS: İYİ: Toplu getirme
const markets = await getMarkets()
const creatorIds = markets.map(m => m.creator_id)
const creators = await getUsers(creatorIds) // 1 sorgu
const creatorMap = new Map(creators.map(c => [c.id, c]))
markets.forEach(market => {
market.creator = creatorMap.get(market.creator_id)
})async function createMarketWithPosition(
marketData: CreateMarketDto,
positionData: CreatePositionDto
) {
// Supabase transaction kullan
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
}
// Supabase'de SQL fonksiyonu
CREATE OR REPLACE FUNCTION create_market_with_position(
market_data jsonb,
position_data jsonb
)
RETURNS jsonb
LANGUAGE plpgsql
AS $$
BEGIN
-- Transaction otomatik başlar
INSERT INTO markets VALUES (market_data);
INSERT INTO positions VALUES (position_data);
RETURN jsonb_build_object('success', true);
EXCEPTION
WHEN OTHERS THEN
-- Rollback otomatik olur
RETURN jsonb_build_object('success', false, 'error', SQLERRM);
END;
$$;class CachedMarketRepository implements MarketRepository {
constructor(
private baseRepo: MarketRepository,
private redis: RedisClient
) {}
async findById(id: string): Promise<Market | null> {
// Önce önbelleği kontrol et
const cached = await this.redis.get(`market:${id}`)
if (cached) {
return JSON.parse(cached)
}
// Cache miss - veritabanından getir
const market = await this.baseRepo.findById(id)
if (market) {
// 5 dakika önbellekle
await this.redis.setex(`market:${id}`, 300, JSON.stringify(market))
}
return market
}
async invalidateCache(id: string): Promise<void> {
await this.redis.del(`market:${id}`)
}
}async function getMarketWithCache(id: string): Promise<Market> {
const cacheKey = `market:${id}`
// Önbelleği dene
const cached = await redis.get(cacheKey)
if (cached) return JSON.parse(cached)
// Cache miss - DB'den getir
const market = await db.markets.findUnique({ where: { id } })
if (!market) throw new Error('Market not found')
// Önbelleği güncelle
await redis.setex(cacheKey, 300, JSON.stringify(market))
return market
}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.errors
}, { status: 400 })
}
// Beklenmeyen hataları logla
console.error('Unexpected error:', error)
return NextResponse.json({
success: false,
error: 'Internal server error'
}, { status: 500 })
}
// Kullanım
export async function GET(request: Request) {
try {
const data = await fetchData()
return NextResponse.json({ success: true, data })
} catch (error) {
return errorHandler(error, request)
}
}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!
}
// Kullanım
const data = await fetchWithRetry(() => fetchFromAPI())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)
}
// API route'unda kullanım
export async function GET(request: Request) {
const user = await requireAuth(request)
const data = await getDataForUser(user.userId)
return NextResponse.json({ success: true, data })
}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)
}
}
}
// Kullanım - HOF handler'ı sarar
export const DELETE = requirePermission('delete')(
async (request: Request, user: User) => {
// Handler doğrulanmış yetki ile kullanıcı alır
return new Response('Deleted', { status: 200 })
}
)class RateLimiter {
private requests = new Map<string, number[]>()
async checkLimit(
identifier: string,
maxRequests: number,
windowMs: number
): Promise<boolean> {
const now = Date.now()
const requests = this.requests.get(identifier) || []
// Pencere dışındaki eski istekleri kaldır
const recentRequests = requests.filter(time => now - time < windowMs)
if (recentRequests.length >= maxRequests) {
return false // Rate limit aşıldı
}
// Mevcut isteği ekle
recentRequests.push(now)
this.requests.set(identifier, recentRequests)
return true
}
}
const limiter = new RateLimiter()
export async function GET(request: Request) {
const ip = request.headers.get('x-forwarded-for') || 'unknown'
const allowed = await limiter.checkLimit(ip, 100, 60000) // 100 req/dak
if (!allowed) {
return NextResponse.json({
error: 'Rate limit exceeded'
}, { status: 429 })
}
// İstekle devam et
}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> {
// İş yürütme mantığı
}
}
// Market indeksleme için kullanım
interface IndexJob {
marketId: string
}
const indexQueue = new JobQueue<IndexJob>()
export async function POST(request: Request) {
const { marketId } = await request.json()
// Bloke etmek yerine kuyruğa ekle
await indexQueue.add({ marketId })
return NextResponse.json({ success: true, message: 'Job queued' })
}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()
// Kullanım
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 })
}
}Unutmayın: Backend kalıpları ölçeklenebilir, sürdürülebilir sunucu tarafı uygulamalar sağlar. Karmaşıklık seviyenize uyan kalıpları seçin.
🧱 Что это
Backend Architecture Patterns — это справочный навык (skill) для разработчиков, создающих серверную часть на Node.js, Express и Next.js API Routes. Он собирает в одном месте проверенные архитектурные шаблоны и лучшие практики: от проектирования REST API и организации слоёв (Repository, Service, Middleware) до оптимизации базы данных, кэширования, обработки ошибок, аутентификации и фоновых задач. Skill помогает писать масштабируемый, поддерживаемый и безопасный серверный код, не изобретая велосипед.
⚙️ Как работает
📐 API Design Patterns
Skill предлагает ресурсно-ориентированные URL для REST API:
GET /api/markets— список ресурсовGET /api/markets/:id— один ресурсPOST /api/markets— созданиеPUT /api/markets/:id— полная заменаPATCH /api/markets/:id— частичное обновлениеDELETE /api/markets/:id— удаление
Для фильтрации, сортировки и пагинации используются query-параметры: ?status=active&sort=volume&limit=20&offset=0.
🏗️ Repository Pattern
Repository абстрагирует доступ к данным. Вы определяете интерфейс (например, MarketRepository с методами findAll, findById, create, update, delete), а затем реализуете его для конкретного хранилища — Supabase, PostgreSQL, MongoDB и т.д. Это позволяет легко менять БД или добавлять кэширование без изменения бизнес-логики.
Пример интерфейса:
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`
}
🧠 Service Layer Pattern
Service содержит бизнес-логику и отделён от доступа к данным. Он принимает репозиторий через конструктор (dependency injection) и реализует сложные операции, например, семантический поиск с векторным эмбеддингом:
class MarketService {
constructor(private marketRepo: MarketRepository) {}
async searchMarkets(query: string, limit = 10): Promise<Market[]> {
const embedding = await generateEmbedding(query)
const results = await this.vectorSearch(embedding, limit)
const markets = await this.marketRepo.findByIds(results.map(r => r.id))
return markets.sort((a, b) => { /* сортировка по релевантности */ })
}
}
🔗 Middleware Pattern
Middleware — это цепочка обработчиков запроса/ответа. Skill показывает, как создать withAuth для проверки JWT-токена и прокидывать пользователя в req.user. Аналогично можно добавить логирование, rate limiting, CORS и т.д.
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 {
req.user = await verifyToken(token)
return handler(req, res)
} catch {
return res.status(401).json({ error: 'Invalid token' })
}
}
}
🗄️ Database Patterns
Skill охватывает ключевые приёмы работы с БД:
- Оптимизация запросов: выбирать только нужные колонки (
select('id, name, status, volume')), а неselect('*'). - Предотвращение N+1: вместо цикла с запросом к каждому связанному объекту — один запрос с
INи сборка мапы. - Транзакции: через
supabase.rpcс SQL-функцией, где BEGIN/COMMIT/ROLLBACK обрабатываются на стороне БД.
⚡ Caching Strategies
Cache-Aside — самый популярный паттерн: сначала проверяем Redis, при промахе читаем из БД и кладём в кэш с TTL (например, 5 минут). Skill показывает реализацию CachedMarketRepository, который оборачивает любой репозиторий и добавляет кэширование.
async findById(id: string): Promise<Market | null> {
const cached = await this.redis.get(`market:${id}`)
if (cached) return JSON.parse(cached)
const market = await this.baseRepo.findById(id)
if (market) await this.redis.setex(`market:${id}`, 300, JSON.stringify(market))
return market
}
🚨 Error Handling Patterns
- Централизованный обработчик: класс
ApiErrorсstatusCodeиisOperational. ФункцияerrorHandlerловитApiError,ZodError(валидация) и неожиданные ошибки, логирует их и возвращает единообразный JSON-ответ. - Retry with Exponential Backoff: универсальная функция
fetchWithRetry, которая повторяет вызов до 3 раз с задержкой 1с, 2с, 4с.
🔐 Authentication & Authorization
- JWT Verification: функция
verifyTokenпроверяет токен с помощью библиотекиjsonwebtokenи секрета изprocess.env.JWT_SECRET. - Role-Based Access Control (RBAC): маппинг ролей (
admin,moderator,user) на разрешения (read,write,delete,admin). ФункцияrequirePermissionвозвращает HOF, который оборачивает handler и проверяет права.
🛡️ Rate Limiting
Простой in-memory rate limiter на Map<string, number[]>. Проверяет количество запросов от одного IP за временное окно (например, 100 запросов в минуту). При превышении возвращает 429.
📦 Background Jobs & Queues
Базовый JobQueue на массиве: задачи добавляются в очередь и обрабатываются последовательно в фоне. Подходит для нетяжёлых операций (индексация, отправка уведомлений). Для продакшена skill рекомендует использовать RabbitMQ, Bull или аналоги.
📝 Logging & Monitoring
Структурированное логирование через класс Logger с уровнями info, warn, error. Каждая запись — JSON с timestamp, уровнем, сообщением и контекстом (userId, requestId, method, path). Это упрощает интеграцию с системами мониторинга (ELK, Datadog).
🎯 Когда использовать
- Вы проектируете REST или GraphQL API и хотите следовать единообразным соглашениям.
- Внедряете слоистую архитектуру (Repository → Service → Controller) для разделения ответственности.
- Оптимизируете запросы к БД: боретесь с N+1, добавляете индексы, пул соединений.
- Добавляете кэширование (Redis, in-memory, HTTP-заголовки).
- Настраиваете фоновые задачи и асинхронную обработку.
- Конфигурируете обработку ошибок, валидацию и middleware (auth, logging, rate limiting).
💡 Важно знать
- Skill не привязан к конкретному фреймворку — примеры на Next.js API Routes легко адаптируются под Express, Fastify или NestJS.
- Паттерны можно комбинировать: например,
CachedMarketRepositoryоборачиваетSupabaseMarketRepository, аMarketServiceиспользует любой репозиторий через интерфейс. - Для продакшена in-memory rate limiter и очередь на массиве стоит заменить на Redis-решения (например,
ioredis+bull). - Все примеры кода — на TypeScript, с акцентом на типизацию и безопасность.
Выбирайте паттерны, соответствующие уровню сложности вашего проекта, и не усложняйте там, где достаточно простого решения.
Может ли этот навык помочь с безопасностью API?
Безусловно. Он включает лучшие практики проверки JWT-токенов, контроля доступа на основе ролей (RBAC) и реализации ограничителей запросов.
Какие фреймворки поддерживает этот навык?
Данный навык оптимизирован для сред Node.js, в частности для Express.js и маршрутов API Next.js.
Предоставляет ли он шаблоны обработки ошибок?
Включает шаблоны для централизованного управления ошибками, валидации через Zod и надежную логику повторных попыток с экспоненциальной задержкой.
Помогает ли он с производительностью базы данных?
Да, он предлагает конкретные шаблоны для предотвращения проблемы N+1 запросов, реализации пула соединений и управления сложными SQL-транзакциями.
Синхронизируйте навыки с Claude Cowork, Claude Code, Codex и другими.
Установите одной командой.
npx skillfish add affaan-m/everything-claude-code backend-patternsИсточник: https://mcpmarket.com/tools/skills/backend-architecture-patterns-1777750040626
Комментарии
Комментариев пока нет. Будьте первым.