AWS Lambda Middleware ile Middy - Temiz Kod ve En İyi Uygulamalar
Middy'nin middleware kalıplarıyla Lambda geliştirmesini nasıl dönüştürdüğünü, tekrarlayan şablonlardan temiz, sürdürülebilir serverless fonksiyonlara geçişi keşfedin
Lambda handler’ları zamanla aynı girişi biriktirir: JSON body’yi parse et, doğrula, hataları yakala, CORS ve güvenlik header’larını ekle. Her endpoint bunu yeniden yazar ve her kopya bir öncekinden biraz uzaklaşır.
Middy, API Gateway arkasındaki Node.js handler’ları için makul bir varsayılan. Handler’ı birbirine geçen middleware katmanlarıyla sarar; parse, doğrulama, hata biçimlendirme ve header’lar tek bir ortak zincirde toplanır, her fonksiyonda yeniden yazılmaz. Bedeli fazladan bir bağımlılık ve bir miktar cold start yükü, bu yüzden bazı fonksiyon tipleri Middy’siz daha iyi çalışır.
Middy Nedir?#
Middy’yi Express veya Koa’daki middleware sistemi gibi düşünebilirsin; farkı, özellikle AWS Lambda için tasarlanmış olması. Business logic’in merkezde oturduğu, etrafını sıkıcı ama gerekli işleri halleden yeniden kullanılabilir middleware’lerin çevrelediği soğan katmanı yaklaşımını benimsiyor.
Her şeyi handler fonksiyonuna tıkıştırmak yerine, Middy temiz, odaklanmış fonksiyonlar compose etmene izin veriyor:
// Middy'siz - Eski yöntem
export const handler = async (event: APIGatewayProxyEvent) => {
try {
// JSON body'yi parse et
let body
try {
body = JSON.parse(event.body || '{}')
} catch (e) {
return {
statusCode: 400,
headers: { 'Access-Control-Allow-Origin': '*' },
body: JSON.stringify({ error: 'Invalid JSON' })
}
}
// Input'u validate et
if (!body.name || typeof body.name !== 'string') {
return {
statusCode: 400,
headers: { 'Access-Control-Allow-Origin': '*' },
body: JSON.stringify({ error: 'Name is required' })
}
}
// Security header'ları ekle
const headers = {
'Access-Control-Allow-Origin': '*',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY'
}
// Nihayet, business logic'in
const greeting = `Hello, ${body.name}!`
return {
statusCode: 200,
headers,
body: JSON.stringify({ message: greeting })
}
} catch (error) {
console.error('Error:', error)
return {
statusCode: 500,
headers: { 'Access-Control-Allow-Origin': '*' },
body: JSON.stringify({ error: 'Internal server error' })
}
}
}
// Middy ile - Temiz ve odaklanmış
import middy from '@middy/core'
import httpJsonBodyParser from '@middy/http-json-body-parser'
import httpErrorHandler from '@middy/http-error-handler'
import httpCors from '@middy/http-cors'
import httpSecurityHeaders from '@middy/http-security-headers'
import validator from '@middy/validator'
import { transpileSchema } from '@middy/validator/transpile'
// Saf business logic
const baseHandler = async (event: APIGatewayProxyEvent) => {
const { name } = event.body as unknown as { name: string }
return {
statusCode: 200,
body: JSON.stringify({
message: `Hello, ${name}!`,
timestamp: new Date().toISOString()
})
}
}
const schema = {
type: 'object',
properties: {
body: {
type: 'object',
properties: {
name: { type: 'string', minLength: 1, maxLength: 100 }
},
required: ['name']
}
}
}
export const handler = middy(baseHandler)
.use(httpJsonBodyParser())
.use(validator({ eventSchema: transpileSchema(schema) }))
.use(httpCors({ origin: '*' }))
.use(httpSecurityHeaders())
.use(httpErrorHandler())
İkinci sürümde business logic öne çıkıyor, HTTP tarafındaki işler ise bütün fonksiyonların paylaştığı middleware’lere kalıyor.
Temel Middy Middleware’leri#
Lambda geliştirirken en çok işe yarayan middleware’ler şunlar:
HTTP Temelleri#
import httpJsonBodyParser from '@middy/http-json-body-parser' // JSON body'leri parse eder
import httpErrorHandler from '@middy/http-error-handler' // Error'ları HTTP response'lara dönüştürür
import httpEventNormalizer from '@middy/http-event-normalizer' // API Gateway event'lerini normalize eder
import httpResponseSerializer from '@middy/http-response-serializer' // Response serialization'ı halleder
Güvenlik ve CORS#
import httpSecurityHeaders from '@middy/http-security-headers' // Güvenlik header'ları ekler
import httpCors from '@middy/http-cors' // CORS'u halleder
Validation#
import validator from '@middy/validator' // JSON Schema validation
AWS Servis Entegrasyonları#
import ssm from '@middy/ssm' // AWS Systems Manager parametreleri
import secretsManager from '@middy/secrets-manager' // AWS Secrets Manager
import warmup from '@middy/warmup' // Lambda warmup handling
Örnek: Kullanıcı Kayıt API’si#
Parçalar bir kayıt endpoint’inde şöyle birleşiyor: input doğrulanıyor, header’lar ekleniyor ve domain hataları HTTP yanıtına dönüşüyor:
import middy from '@middy/core'
import httpJsonBodyParser from '@middy/http-json-body-parser'
import httpErrorHandler from '@middy/http-error-handler'
import httpSecurityHeaders from '@middy/http-security-headers'
import httpCors from '@middy/http-cors'
import validator from '@middy/validator'
import { transpileSchema } from '@middy/validator/transpile'
import { createError } from '@middy/util'
interface UserRegistration {
email: string
password: string
firstName: string
lastName: string
}
const registerUser = async (event: APIGatewayProxyEvent) => {
const userData = event.body as unknown as UserRegistration
// Kullanıcının daha önce var olup olmadığını kontrol et
const existingUser = await getUserByEmail(userData.email)
if (existingUser) {
throw createError(409, 'User already exists', {
type: 'UserAlreadyExists'
})
}
// Yeni kullanıcı oluştur
const hashedPassword = await hashPassword(userData.password)
const newUser = await createUser({
...userData,
password: hashedPassword
})
// Hoşgeldin email'i gönder (fire and forget)
sendWelcomeEmail(newUser.email, newUser.firstName).catch(
error => console.error('Failed to send welcome email:', error)
)
return {
statusCode: 201,
body: JSON.stringify({
id: newUser.id,
email: newUser.email,
firstName: newUser.firstName,
lastName: newUser.lastName,
createdAt: newUser.createdAt
})
}
}
const registrationSchema = {
type: 'object',
properties: {
body: {
type: 'object',
properties: {
email: {
type: 'string',
format: 'email',
maxLength: 254
},
password: {
type: 'string',
minLength: 8,
maxLength: 128,
pattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]'
},
firstName: {
type: 'string',
minLength: 1,
maxLength: 50
},
lastName: {
type: 'string',
minLength: 1,
maxLength: 50
}
},
required: ['email', 'password', 'firstName', 'lastName']
}
}
}
export const handler = middy(registerUser)
.use(httpJsonBodyParser())
.use(validator({ eventSchema: transpileSchema(registrationSchema) }))
.use(httpCors({
origin: process.env.ALLOWED_ORIGINS?.split(',') ?? ['http://localhost:3000'],
credentials: true
}))
.use(httpSecurityHeaders({
strictTransportSecurity: {
maxAge: 31536000,
includeSubDomains: true
}
}))
.use(httpErrorHandler({
logger: console.error
}))
Bu tek middleware chain’i hallediyor:
- Error handling ile JSON parsing
- Kapsamlı input validation (password complexity dahil)
- Configurable origin’lerle CORS header’ları
- Koruma için security header’ları
- Proper HTTP error response’ları
- Request logging
Özel Middleware Yazma#
Bazen uygulamana özel bir şeye ihtiyacın olur. Pattern’i bir kez anladığında custom middleware yazmak oldukça kolay:
import { MiddlewareObj } from '@middy/core'
interface RequestTimingOptions {
logSlowRequests?: boolean
slowRequestThreshold?: number
}
export const requestTiming = (
options: RequestTimingOptions = {}
): MiddlewareObj => {
const { logSlowRequests = true, slowRequestThreshold = 1000 } = options
return {
before: async (request) => {
// Timing'i başlat
request.internal = request.internal || {}
request.internal.startTime = Date.now()
},
after: async (request) => {
if (request.internal?.startTime) {
const duration = Date.now() - request.internal.startTime
// Response'a timing header'ı ekle
if (request.response && typeof request.response === 'object') {
const response = request.response as any
response.headers = {
...response.headers,
'X-Execution-Time': duration.toString()
}
}
// Yavaş request'leri logla
if (logSlowRequests && duration > slowRequestThreshold) {
console.warn(`Slow request detected: ${duration}ms`, {
functionName: request.context.functionName,
requestId: request.context.awsRequestId,
duration
})
}
}
},
onError: async (request) => {
if (request.internal?.startTime) {
const duration = Date.now() - request.internal.startTime
console.error(`Request failed after ${duration}ms`, {
error: request.error?.message,
duration,
requestId: request.context.awsRequestId
})
}
}
}
}
// Kullanım
export const handler = middy(baseHandler)
.use(requestTiming({ slowRequestThreshold: 500 }))
.use(httpJsonBodyParser())
.use(httpErrorHandler())
Bu custom middleware, response’lara execution timing ekliyor ve yavaş request’leri otomatik olarak logluyor. Pattern basit: before handler’dan önce, after başarılı dönüşten sonra, onError ise hata durumunda çalışır.
Production’da İşe Yarayan Kalıplar#
Fonksiyon sayısı arttıkça middleware zincirini öngörülebilir tutan birkaç alışkanlık var:
1. Sıralama Önemli#
Middleware çalışma sırası kritik. Yanlış sıralama subtle bug’lara yol açar:
// Yanlış sıralama - validator body parsing'den önce çalışıyor
export const handler = middy(baseHandler)
.use(validator({ eventSchema: schema })) // Bu fail olacak!
.use(httpJsonBodyParser())
.use(httpErrorHandler())
// Doğru sıralama
export const handler = middy(baseHandler)
.use(httpJsonBodyParser()) // Önce parse et
.use(validator({ eventSchema: schema })) // Sonra validate et
.use(httpErrorHandler()) // Son olarak error'ları handle et
2. Type Safety Şart#
Her zaman proper TypeScript type’ları kullan:
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'
const typedHandler = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
// TypeScript error'ları compile time'da yakalayacak
const body = event.body as unknown as UserRegistration
// ... geri kalan logic
}
3. Error Handling Stratejisi#
Domain-specific error class’ları oluştur:
class BusinessLogicError extends Error {
statusCode: number
constructor(message: string, statusCode = 400) {
super(message)
this.statusCode = statusCode
this.name = 'BusinessLogicError'
}
}
// Handler'larda kullan
if (!isValidBusinessRule(data)) {
throw new BusinessLogicError('Invalid business data', 422)
}
4. Security Header’ları Standart Olmalı#
Security header’larını atlama. Standart bir konfigürasyon:
.use(httpSecurityHeaders({
contentTypeOptions: { action: 'nosniff' },
frameOptions: { action: 'deny' },
contentSecurityPolicy: { 'default-src': "'self'" },
strictTransportSecurity: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}))
5. Configuration Data’yı Cache’le#
Sık çağrılan fonksiyonlar için pahalı configuration’ları cache’le:
.use(ssm({
fetchData: {
dbConfig: '/myapp/database/config',
apiKeys: '/myapp/external/api-keys'
},
cacheExpiry: 5 * 60 * 1000, // 5 dakika
setToContext: true
}))
Middy Fonksiyonlarını Test Etme#
Middy’nin en büyük avantajlarından biri test edilebilirliği artırması. Business logic’i middleware zincirinden ayrı test edebiliyorsun:
// Saf business logic'i test et
describe('User Registration Logic', () => {
test('should create new user with valid data', async () => {
const mockEvent = {
body: {
email: 'test@example.com',
password: 'SecurePass123!',
firstName: 'John',
lastName: 'Doe'
}
} as unknown as APIGatewayProxyEvent
// Core handler'ı doğrudan test et
const result = await registerUser(mockEvent)
expect(result.statusCode).toBe(201)
const responseBody = JSON.parse(result.body)
expect(responseBody.email).toBe('test@example.com')
expect(responseBody.password).toBeUndefined()
})
})
// Tam middleware chain'ini test et
describe('User Registration API', () => {
test('should handle invalid JSON', async () => {
const event = {
body: 'invalid json',
headers: { 'content-type': 'application/json' }
} as any
const result = await handler(event, {} as any)
expect(result.statusCode).toBe(400)
})
test('should validate required fields', async () => {
const event = {
body: JSON.stringify({
email: 'test@example.com'
// Gerekli alanlar eksik
}),
headers: { 'content-type': 'application/json' }
} as any
const result = await handler(event, {} as any)
expect(result.statusCode).toBe(400)
})
})
Middy’nin Uygun Olmadığı Durumlar#
Middy her zaman doğru seçim değil. Şu durumlarda kullanma:
- Ultra düşük gecikmeli fonksiyonlar: her milisaniyenin sayıldığı yerler
- Tek amaçlı utility’ler: içinde neredeyse hiç logic yok
- Bellek kısıtlı ortamlar: bundle boyutu kritikse
- Framework-agnostic kütüphaneler: explicit composition tercih ediliyorsa
Kaçınılması Gereken Yaygın Tuzaklar#
Şu yaygın sorunlara dikkat et:
- Basit fonksiyonları over-engineer etme - Her Lambda middleware’e ihtiyaç duymaz
- Middleware sıralamasını göz ardı etme - Önce parse, sonra validate, en son business logic
- Cold start’larda heavy middleware’ler - Initialization overhead’ına dikkat et
- Sensitive data loglama - Input/output logging middleware’i ile dikkatli ol
- Configuration cache’lememe - External data için built-in caching kullan
Başlarken#
Kurulum için gereken paketler:
# Core paket
npm install @middy/core
# Temel middleware'ler
npm install @middy/http-json-body-parser @middy/http-error-handler @middy/validator
# Güvenlik ve CORS
npm install @middy/http-cors @middy/http-security-headers
# Performance yardımcıları
npm install @middy/do-not-wait-for-empty-event-loop @middy/warmup
# AWS servis entegrasyonları
npm install @middy/ssm @middy/secrets-manager
Önce parser, validator ve error handler’ı ekle; handler’ın şekli oturduktan sonra zinciri genişletirsin. Ortak bir girişi olmayan bir fonksiyonu ise düz bırakmak daha iyi.
Sonraki Adımlar#
Middy çoğu HTTP handler’ı için fazlasıyla yeterli. 2. Bölüm, ölçek büyüdüğünde ortaya çıkan sınırları ve multi-tenant konfigürasyon ile daha dar performance bütçeleri için custom bir middleware framework’ün nasıl kurulduğunu anlatıyor.
Sonraki bölümde:
- Scale’de ortaya çıkan performance darboğazları
- Multi-tenant uygulamalar için dynamic middleware oluşturma
- Custom framework design pattern’leri
- Middy’den custom çözümlere migration stratejileri
- Middy ile elle yazılmış zincir arasındaki performance trade-off’ları
Kaynaklar#
- Middy.js - Resmi Dokümantasyon (yeni sekmede açılır) - Temel kavramlar, resmi paketler ve yaşam döngüsü kancaları dahil tam middleware motoru dokümantasyonu
- Middy - Resmi Middleware’ler (yeni sekmede açılır) - Validator, http-error-handler ve AWS servis entegrasyonları dahil resmi olarak bakımı yapılan middleware paketlerinin katalogu
- Middy - Başlarken (yeni sekmede açılır) - Lambda handler’larını sarmaya ve middleware eklemeye pratik giriş
- Node.js ile Lambda Fonksiyonları Oluşturma - AWS Lambda (yeni sekmede açılır) - Node.js Lambda çalışma zamanı, handler modeli ve desteklenen özellikler için resmi AWS referansı
- AWS Lambda için Powertools (TypeScript) (yeni sekmede açılır) - Middy tabanlı handler’ları tamamlayan yapılandırılmış loglama, izleme, metrik ve doğrulama için AWS araç seti
- middy/core - npm (yeni sekmede açılır) - Sürüm geçmişi, kurulum talimatları ve haftalık indirme istatistikleri içeren npm paket sayfası
AWS Lambda Middleware Uzmanlığı
Middy temellerinden production ölçeği Lambda uygulamaları için özel middleware framework'leri oluşturmaya
Bu serideki tüm yazılar
İlgili yazılar
Agent geliştirmek için TypeScript SDK karşılaştırması: Vercel AI SDK, OpenAI Agents SDK ve AWS Bedrock entegrasyonu, kod örnekleri ve karar frameworkleri ile.
typescript · ai-tools · serverless +4
Middy builder pattern, Zod validation, feature flags ve secrets management ile sürdürülebilir, type-safe Lambda middleware nasıl inşa edilir öğren.
lambda · middleware · typescript +7
Bir Lambda filosu Middy'nin statik middleware modelini ne zaman aşar, projeye özel bir motor istek başına konfigürasyonu nasıl çözer, bakımı neye mal olur
lambda · middleware · performance +6
AWS CDK, DynamoDB ve Lambda ile production-grade link kısaltıcı kurulumu. Mimari kararlar, proje yapısı ve ölçekte ayakta kalan şema seçimleri.
aws-cdk · lambda · dynamodb +5
İç servis katmanı kurmadan önce kurup kurmayacağınıza karar verin. Katmanın çağrı başına maliyeti, VPC Lattice'in kazandığı hacim ve direct invoke'un hâlâ kazandığı an.
aws · aws-cdk · lambda +4