2025-09-04
Zod ve OpenAPI ile Type-Safe AWS Lambda API'leri
'Basit' bir API değişikliği bir kurumsal müşteri entegrasyonunu nasıl bozdu, dokümantasyon drift'i neden gerçek sorunlara yol açar ve Zod schema'larından otomatik OpenAPI spec'i üreten pratik bir sistem.
API dokümantasyon drift’i hakkında acı bir ders: Kullanıcı API’sine opsiyonel bir alan eklemek ama OpenAPI spec’ini güncellememek, bir kurumsal müşterinin entegrasyonunu bir gecede bozdu. Onların code generation pipeline’ı eski şemayı bekleyen TypeScript arayüzleri üretti. Onların kod generation pipeline’ı eski şemayı bekleyen TypeScript arayüzleri üretti, yüzlerce başarısız kullanıcı kaydı ve önemli gelir kaybına neden oldu.
Bu olay API dokümantasyonunun sadece nice-to-have olmadığını gösterdi – kritik iş altyapısıdır. Bu yaklaşım Zod şemalarından otomatik olarak OpenAPI spec’leri üreten sistemlerin yeniden oluşturulmasını içerir; tek doğruluk kaynağı daha güvenli API evrimi sağlar. CDK ile production stack örneği aşağıda.
Kritik Ders: Dokümantasyon Kayması Neden İşleri Öldürür
Olayımızdan önce, klasik serverless API geliştirme kabusumuz vardı – dört farklı doğruluk kaynağı birbirinden sapıyordu:
// 1. TypeScript arayüzleri (geliştiricilerin API'nin ne yaptığını düşündüğü)
interface CreateUserRequest {
email: string;
username: string;
age?: number;
// Bu alanı ekledim...
company?: string;
}
// 2. OpenAPI spec (müşterilerin kod ürettiği)
const openApiSpec = {
paths: {
'/users': {
post: {
requestBody: {
// Ama bunu güncellemeyi unuttum
schema: {
type: 'object',
properties: {
email: { type: 'string' },
username: { type: 'string' },
age: { type: 'number' }
// Eksik: company alanı
}
}
}
}
}
}
};
// 3. Lambda validation (çalışma zamanında gerçekte ne doğrulanıyor)
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
const body = JSON.parse(event.body || '{}');
// Manuel validation - sync dışı kalmaya meyilli
if (!body.email || !body.username) {
return { statusCode: 400, body: 'Missing required fields' };
}
// Yeni company alanını kullanıyorum ama dokümante etmemiş
const user = await createUser({
email: body.email,
username: body.username,
age: body.age,
company: body.company, // Bu müşteri entegrasyonunu bozdu
});
return { statusCode: 201, body: JSON.stringify(user) };
};
// 4. Test cases (umutla güncel olanlar)
describe('POST /users', () => {
it('should create user', async () => {
const response = await request(app)
.post('/users')
.send({
email: '[email protected]',
username: 'testuser',
age: 25
// Company alanını test etmeyi unuttum
});
expect(response.status).toBe(201);
});
});
Bu dört kaynağın sync’te kalması zordu. Her yeni özellik release’inde hangi müşteri entegrasyonunun etkileneceğini önceden tahmin etmek güçtü. Kod generation pipeline’ları eski OpenAPI spec’lerine dayandığı için, dokümantasyon drift’i doğrudan production hatalarına dönüştü.
Çözüm: Zod-First API Development
Tek Doğruluk Kaynağı ilkesinden hareketle, bu yaklaşım Zod şemalarını kesin API sözleşmesi olarak kullanıyor ve otomatik olarak üretiyor:
- Compile-time TypeScript tipleri (interface drift artık yok)
- OpenAPI 3.0 spesifikasyonları (her zaman senkron)
- Runtime validation (veritabanına ulaşmadan önce hatalı veriyi yakala)
- Yapılandırılmış hata yanıtları (istemciler tam olarak neyin yanlış gittiğini bilir)
- Veritabanı migration’ları (özel araçlarla)
Production’da gözlemlenen faydalar:
- Schema drift’inden azalan entegrasyon hataları
- Manuel spec bakımına daha az zaman
- Doğru, otomatik üretilmiş SDK’larla daha sorunsuz müşteri onboarding’i
- Daha az destek sorusuna yol açan net hata mesajları
İşte mimari:
Temel: Schema Drift’i Önleyen Altyapı
Güvenli API evrimini destekleyen kurulum:
# Temel bağımlılıklar
npm install zod @anatine/zod-openapi @asteasolutions/zod-to-openapi
npm install uuid @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
npm install --save-dev @types/aws-lambda @types/uuid
# Production monitoring için
npm install @aws-lambda-powertools/logger @aws-lambda-powertools/tracer @aws-lambda-powertools/metrics
Compile Time’da Hataları Yakalayan Type System
TypeScript ve Zod entegrasyonu, schema değişikliklerini compile time’da yakalar.
API Sözleşmelerini Doğrulayan Handler Wrapper
Handler wrapper’ı request validasyonu ve hata yönetimini merkezileştirir. TypeScript tipleri, Lambda validation, OpenAPI spec ve test case’ler artık aynı schema’dan türetiliyor – tek yerde değişiklik, her yerde güncelleme.
// schemas/user.ts - TEK doğruluk kaynağı
import { z } from 'zod';
export const CreateUserRequestSchema = z.object({
email: z.string().email('Geçerli email gerekli'),
username: z.string().min(3, 'Username en az 3 karakter olmalı').max(20),
age: z.number().int().min(13).max(120).optional(),
company: z.string().min(2).max(100).optional(), // Yeni alan - bir yerde tanımla, her yerde güncel
});
export const UserResponseSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
username: z.string(),
age: z.number().optional(),
company: z.string().optional(),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
});
export type CreateUserRequest = z.infer<typeof CreateUserRequestSchema>;
export type UserResponse = z.infer<typeof UserResponseSchema>;
Production CDK Stack’i
İşte gerçek CDK kodumuzu içeren stack (hiçbir güzelleştirme yok):
// lib/api-stack.ts
import { Stack, StackProps, Duration } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { RestApi, LambdaIntegration, RequestValidator, Model, JsonSchemaType } from 'aws-cdk-lib/aws-apigateway';
import { Table, AttributeType, BillingMode } from 'aws-cdk-lib/aws-dynamodb';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
export class TypeSafeApiStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// DynamoDB table
const usersTable = new Table(this, 'UsersTable', {
partitionKey: { name: 'id', type: AttributeType.STRING },
billingMode: BillingMode.PAY_PER_REQUEST,
pointInTimeRecovery: true,
});
// Lambda handlers - her endpoint için ayrı fonksiyon (monolith degil)
const createUserHandler = new NodejsFunction(this, 'CreateUserHandler', {
entry: 'src/handlers/users/create.ts',
runtime: Runtime.NODEJS_20_X,
timeout: Duration.seconds(10),
memorySize: 512,
environment: {
USERS_TABLE_NAME: usersTable.tableName,
NODE_ENV: 'production',
},
bundling: {
externalModules: ['@aws-sdk/*'],
minify: true,
},
});
const getUserHandler = new NodejsFunction(this, 'GetUserHandler', {
entry: 'src/handlers/users/get.ts',
runtime: Runtime.NODEJS_20_X,
timeout: Duration.seconds(5),
memorySize: 256, // Read operation için daha az memory
environment: {
USERS_TABLE_NAME: usersTable.tableName,
},
bundling: {
externalModules: ['@aws-sdk/*'],
minify: true,
},
});
const listUsersHandler = new NodejsFunction(this, 'ListUsersHandler', {
entry: 'src/handlers/users/list.ts',
runtime: Runtime.NODEJS_20_X,
timeout: Duration.seconds(15),
memorySize: 1024, // List operation potansiyel olarak daha fazla data
environment: {
USERS_TABLE_NAME: usersTable.tableName,
},
bundling: {
externalModules: ['@aws-sdk/*'],
minify: true,
},
});
// DynamoDB izinleri
usersTable.grantReadWriteData(createUserHandler);
usersTable.grantReadData(getUserHandler);
usersTable.grantReadData(listUsersHandler);
// API Gateway
const api = new RestApi(this, 'TypeSafeApi', {
restApiName: 'TypeSafe User API',
description: 'Zod-validated API with auto-generated OpenAPI',
defaultCorsPreflightOptions: {
allowOrigins: ['*'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization'],
},
});
// Request validation models - Zod şemalarından generate edildi
const createUserModel = new Model(this, 'CreateUserModel', {
restApi: api,
modelName: 'CreateUserRequest',
contentType: 'application/json',
schema: {
type: JsonSchemaType.OBJECT,
required: ['email', 'username'],
properties: {
email: {
type: JsonSchemaType.STRING,
format: 'email',
},
username: {
type: JsonSchemaType.STRING,
minLength: 3,
maxLength: 20,
},
age: {
type: JsonSchemaType.INTEGER,
minimum: 13,
maximum: 120,
},
company: {
type: JsonSchemaType.STRING,
minLength: 2,
maxLength: 100,
},
},
},
});
// Request validator
const requestValidator = new RequestValidator(this, 'RequestValidator', {
restApi: api,
validateRequestBody: true,
validateRequestParameters: true,
});
// Users resource
const usersResource = api.root.addResource('users');
// POST /users
usersResource.addMethod('POST', new LambdaIntegration(createUserHandler), {
requestValidator,
requestModels: {
'application/json': createUserModel,
},
});
// GET /users
usersResource.addMethod('GET', new LambdaIntegration(listUsersHandler));
// GET /users/{id}
const userResource = usersResource.addResource('{id}');
userResource.addMethod('GET', new LambdaIntegration(getUserHandler), {
requestValidator,
requestParameters: {
'method.request.path.id': true,
},
});
}
}
Type-Safe Lambda Handlers
Lambda Handler Implementasyonları
// src/handlers/users/create.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';
import { marshall } from '@aws-sdk/util-dynamodb';
import { CreateUserRequestSchema, UserResponseSchema } from '../../schemas/user';
import { randomUUID } from 'crypto';
const dynamoClient = new DynamoDBClient({});
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try {
console.log('Create user request:', {
body: event.body,
headers: event.headers,
});
// 1. Parse ve validate request body - Zod otomatik type safety sağlıyor
const body = JSON.parse(event.body || '{}');
const validatedData = CreateUserRequestSchema.parse(body);
// 2. User object oluştur
const user = {
id: randomUUID(),
...validatedData,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
// 3. DynamoDB'ye kaydet
await dynamoClient.send(new PutItemCommand({
TableName: process.env.USERS_TABLE_NAME!,
Item: marshall(user),
ConditionExpression: 'attribute_not_exists(id)', // Duplicate prevention
}));
// 4. Response'u validate et - bu bile type-safe
const validatedResponse = UserResponseSchema.parse(user);
return {
statusCode: 201,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify(validatedResponse),
};
} catch (error) {
console.error('Create user error:', error);
// Zod validation hatalarını özel olarak işle
if (error instanceof z.ZodError) {
return {
statusCode: 400,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
error: 'Validation failed',
details: error.errors.map(err => ({
field: err.path.join('.'),
message: err.message,
})),
}),
};
}
// DynamoDB conditional check failures
if (error.name === 'ConditionalCheckFailedException') {
return {
statusCode: 409,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
error: 'User already exists',
}),
};
}
// Generic server error
return {
statusCode: 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
error: 'Internal server error',
}),
};
}
};
// src/handlers/users/get.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { DynamoDBClient, GetItemCommand } from '@aws-sdk/client-dynamodb';
import { unmarshall } from '@aws-sdk/util-dynamodb';
import { UserResponseSchema } from '../../schemas/user';
import { z } from 'zod';
const dynamoClient = new DynamoDBClient({});
// Path parameter validation
const GetUserParamsSchema = z.object({
id: z.string().uuid('Valid UUID required'),
});
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try {
// Path parameter'ları validate et
const params = GetUserParamsSchema.parse({
id: event.pathParameters?.id,
});
// DynamoDB'den user'ı getir
const result = await dynamoClient.send(new GetItemCommand({
TableName: process.env.USERS_TABLE_NAME!,
Key: marshall({ id: params.id }),
}));
if (!result.Item) {
return {
statusCode: 404,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
error: 'User not found',
}),
};
}
// Response'u validate et
const user = unmarshall(result.Item);
const validatedUser = UserResponseSchema.parse(user);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify(validatedUser),
};
} catch (error) {
console.error('Get user error:', error);
if (error instanceof z.ZodError) {
return {
statusCode: 400,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
error: 'Invalid parameters',
details: error.errors,
}),
};
}
return {
statusCode: 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
error: 'Internal server error',
}),
};
}
};
OpenAPI Generation
OpenAPI Spec Generation
En güzel kısım - Zod şemalarından otomatik OpenAPI spec generation:
// scripts/generate-openapi.ts
import { CreateUserRequestSchema, UserResponseSchema } from '../src/schemas/user';
import { zodToJsonSchema } from 'zod-to-json-schema';
import fs from 'fs';
const generateOpenApiSpec = () => {
const spec = {
openapi: '3.0.0',
info: {
title: 'TypeSafe User API',
version: '1.0.0',
description: 'Zod-validated API with automatic OpenAPI generation',
},
servers: [
{
url: process.env.API_URL || 'https://api.example.com',
description: 'Production server',
},
],
paths: {
'/users': {
post: {
summary: 'Create a new user',
requestBody: {
required: true,
content: {
'application/json': {
schema: zodToJsonSchema(CreateUserRequestSchema, 'CreateUserRequest'),
},
},
},
responses: {
'201': {
description: 'User created successfully',
content: {
'application/json': {
schema: zodToJsonSchema(UserResponseSchema, 'UserResponse'),
},
},
},
'400': {
description: 'Validation error',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
error: { type: 'string' },
details: {
type: 'array',
items: {
type: 'object',
properties: {
field: { type: 'string' },
message: { type: 'string' },
},
},
},
},
},
},
},
},
},
},
get: {
summary: 'List all users',
responses: {
'200': {
description: 'List of users',
content: {
'application/json': {
schema: {
type: 'array',
items: zodToJsonSchema(UserResponseSchema, 'UserResponse'),
},
},
},
},
},
},
},
'/users/{id}': {
get: {
summary: 'Get user by ID',
parameters: [
{
name: 'id',
in: 'path',
required: true,
schema: {
type: 'string',
format: 'uuid',
},
},
],
responses: {
'200': {
description: 'User details',
content: {
'application/json': {
schema: zodToJsonSchema(UserResponseSchema, 'UserResponse'),
},
},
},
'404': {
description: 'User not found',
},
},
},
},
},
components: {
schemas: {
CreateUserRequest: zodToJsonSchema(CreateUserRequestSchema, 'CreateUserRequest'),
UserResponse: zodToJsonSchema(UserResponseSchema, 'UserResponse'),
},
},
};
// OpenAPI spec'ini dosyaya yaz
fs.writeFileSync('openapi.json', JSON.stringify(spec, null, 2));
console.log('OpenAPI spec generated: openapi.json');
return spec;
};
// CI/CD pipeline'da çalıştır
if (require.main === module) {
generateOpenApiSpec();
}
Testing Strategy
Unit ve Entegrasyon Testleri
Type safety testleri de kapsar:
// tests/handlers/users.test.ts
import { handler as createUserHandler } from '../../src/handlers/users/create';
import { handler as getUserHandler } from '../../src/handlers/users/get';
import { CreateUserRequestSchema } from '../../src/schemas/user';
describe('Users API', () => {
describe('POST /users', () => {
it('should create user with valid data', async () => {
const validRequest = {
email: '[email protected]',
username: 'testuser',
age: 25,
company: 'Test Corp',
};
// Bu static olarak type-safe olduğundan emin ol
const _typeCheck: typeof validRequest = CreateUserRequestSchema.parse(validRequest);
const response = await createUserHandler({
body: JSON.stringify(validRequest),
httpMethod: 'POST',
path: '/users',
headers: {},
pathParameters: null,
queryStringParameters: null,
requestContext: {} as any,
resource: '',
stageVariables: null,
isBase64Encoded: false,
});
expect(response.statusCode).toBe(201);
const responseBody = JSON.parse(response.body);
expect(responseBody).toHaveProperty('id');
expect(responseBody.email).toBe(validRequest.email);
expect(responseBody.username).toBe(validRequest.username);
});
it('should reject invalid email', async () => {
const invalidRequest = {
email: 'not-an-email', // Invalid!
username: 'testuser',
};
const response = await createUserHandler({
body: JSON.stringify(invalidRequest),
httpMethod: 'POST',
path: '/users',
// ... diğer event properties
} as any);
expect(response.statusCode).toBe(400);
const responseBody = JSON.parse(response.body);
expect(responseBody.error).toBe('Validation failed');
expect(responseBody.details).toContainEqual(
expect.objectContaining({
field: 'email',
message: expect.stringContaining('email'),
})
);
});
});
});
Production’da Öğrenilen Dersler
1. Schema Evolution
// Yeni alanlar ekleme - backward compatible
const CreateUserRequestSchemaV2 = CreateUserRequestSchema.extend({
// Yeni optional alanlar sorun değil
phoneNumber: z.string().optional(),
preferences: z.object({
newsletter: z.boolean(),
notifications: z.boolean(),
}).optional(),
});
// Breaking changes için versioning
const CreateUserRequestSchemaV3 = z.object({
// Required alan eklemek breaking change
email: z.string().email(),
username: z.string().min(3),
fullName: z.string().min(1), // Yeni required field - V3
});
2. Error Handling Best Practices
// src/utils/error-handler.ts
export const handleApiError = (error: unknown): APIGatewayProxyResult => {
console.error('API Error:', error);
// Zod validation errors
if (error instanceof z.ZodError) {
return {
statusCode: 400,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error: 'Validation failed',
details: error.errors.map(err => ({
field: err.path.join('.'),
message: err.message,
received: err.received,
})),
}),
};
}
// AWS errors
if (error && typeof error === 'object' && 'name' in error) {
switch (error.name) {
case 'ConditionalCheckFailedException':
return {
statusCode: 409,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Resource already exists' }),
};
case 'ResourceNotFoundException':
return {
statusCode: 404,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Resource not found' }),
};
}
}
// Generic server error - production'da stack trace'i logla ama müşteriye gösterme
return {
statusCode: 500,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Internal server error' }),
};
};
3. Performance Optimization
// Schema'ları global scope'ta tanımla - cold start performansı için
const SCHEMAS = {
createUser: CreateUserRequestSchema,
user: UserResponseSchema,
} as const;
// Runtime'da validation cache'le
const validationCache = new Map<string, any>();
export const validateWithCache = <T>(schema: z.ZodSchema<T>, data: unknown): T => {
const cacheKey = JSON.stringify(data);
if (validationCache.has(cacheKey)) {
return validationCache.get(cacheKey);
}
const result = schema.parse(data);
validationCache.set(cacheKey, result);
return result;
};
Deployment and CI/CD
GitHub Actions Workflow
# .github/workflows/deploy.yml
name: Deploy API
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Type check
run: npm run typecheck
- name: Generate OpenAPI spec
run: npm run generate:openapi
- name: Upload OpenAPI spec
uses: actions/upload-artifact@v3
with:
name: openapi-spec
path: openapi.json
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Deploy CDK
run: npx cdk deploy --require-approval never
Monitoring and Observability
Yapılandırılmış logging ve tracing ekle:
// lib/api/observability.ts
import { Tracer } from '@aws-lambda-powertools/tracer';
import { Logger } from '@aws-lambda-powertools/logger';
import { Metrics } from '@aws-lambda-powertools/metrics';
const tracer = new Tracer();
const logger = new Logger();
const metrics = new Metrics();
export function createObservableHandler<T extends HandlerConfig<any, any, any, any>>(
config: T,
handler: HandlerFunction<T>
) {
const baseHandler = createHandler(config, handler);
return tracer.captureLambdaHandler(
logger.injectLambdaContext(
metrics.logMetrics(baseHandler)
)
);
}
// Kullanım
export const handler = createObservableHandler({
body: CreateUserRequestSchema,
response: UserResponseSchema
}, async ({ body }, context) => {
logger.info('Creating user', { email: body.email });
// Custom metric ekle
metrics.addMetric('UserCreated', 'Count', 1);
// Trace annotation ekle
tracer.putAnnotation('userEmail', body.email);
// Business logic...
});
Best Practices
1. Schema Versioning
// lib/api/schemas/v1/user.ts
export const UserSchemaV1 = z.object({
// V1 schema
});
// lib/api/schemas/v2/user.ts
export const UserSchemaV2 = UserSchemaV1.extend({
// V2 additions
preferences: PreferencesSchema
});
// Handler with version support
export const handler = createHandler({
headers: z.object({
'api-version': z.enum(['v1', 'v2']).default('v2')
}),
body: z.union([UserSchemaV1, UserSchemaV2]),
response: z.union([UserResponseV1, UserResponseV2])
}, async ({ headers, body }) => {
if (headers['api-version'] === 'v1') {
return handleV1(body);
}
return handleV2(body);
});
2. Error Recovery
export const resilientHandler = createHandler({
// ... config
}, async ({ body }, context) => {
// Circuit breaker pattern
const breaker = new CircuitBreaker(dynamoClient.send, {
timeout: 3000,
errorThresholdPercentage: 50,
resetTimeout: 30000
});
try {
return await breaker.fire(new PutCommand({
TableName: TABLE_NAME,
Item: user
}));
} catch (error) {
// Fallback to SQS
await sqsClient.send(new SendMessageCommand({
QueueUrl: DLQ_URL,
MessageBody: JSON.stringify({ user, error: error.message })
}));
throw new Error('Service temporarily unavailable');
}
});
3. Security Headers
export function addSecurityHeaders(response: APIGatewayProxyResultV2): APIGatewayProxyResultV2 {
return {
...response,
headers: {
...response.headers,
'Strict-Transport-Security': 'max-age=63072000; includeSubDomains; preload',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Content-Security-Policy': "default-src 'none'; frame-ancestors 'none'"
}
};
}
Sonuç
Zod’un runtime validation’ını OpenAPI generation ile birleştirerek, type-safe bir serverless API oluşturuluyor:
- Manuel senkronizasyonu ortadan kaldırıyor - tipler, validation ve dokümantasyon arasında
- Hataları compile time’da yakalar - tam TypeScript entegrasyonu ile
- Runtime’da validate eder - detaylı hata mesajları ile
- Doğru dokümantasyonu otomatik üretir
- Verimli scale eder - AWS Lambda ve CDK ile
Bu yaklaşım, API development’ı hataya açık manuel koordinasyondan otomatik bir sürece dönüştürür. Şemalar tek doğruluk kaynağı haline gelir ve serverless stack’inin her katmanında tutarlılık sağlar.
Sonraki Adımlar
- Authentication ekle - AWS Cognito veya custom JWT validation ile
- Caching implement et - API Gateway caching veya ElastiCache ile
- WebSocket desteği ekle - real-time özellikler için
- AWS X-Ray entegrasyonu - distributed tracing için
- API versioning kur - stage variables ile
- Contract testing ekle - Pact veya benzeri araçlarla
Oluşturduğumuz temel, modern API development’ın karmaşıklığını ele alırken serverless’i çekici kılan basitliği koruyor. Happy building!
İlgili yazılar
AWS CDK, DynamoDB ve Lambda ile production-grade link kısaltıcı kurulumu. Gerçek mimari kararlar, ilk kurulum ve büyük ölçekte URL kısaltıcıları inşa etmenin dersleri.
Yönlendirme motoru, analytics toplama ve API Gateway konfigürasyonu. Günlük milyonlarca yönlendirmeyi işlemenin gerçek performans optimizasyonları ve debugging stratejileri.
Amazon Cognito'nun gelişmiş özellikleri üzerine kapsamlı teknik kılavuz: özel authentication akışları, federation pattern'leri, multi-tenancy mimarileri, migration stratejileri ve production-grade güvenlik implementasyonu.
AWS Lambda, API Gateway, DynamoDB ve Step Functions için hızlı geri bildirim ve production güvenilirliği sağlayan kapsamlı bir test stratejisi oluşturmayı öğrenin.
AWS CDK, Lambda ve GitHub Actions kullanarak otomatik preview ortamları oluşturmayı öğrenin - sorunsuz PR test ve inceleme süreçleri için