Skip to content
~/sph.sh

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:

typescript
// 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:

bash
# Temel bağımlılıklarnpm install zod @anatine/zod-openapi @asteasolutions/zod-to-openapinpm install uuid @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodbnpm install --save-dev @types/aws-lambda @types/uuid
# Production monitoring içinnpm 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.

typescript
// 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):

typescript
// lib/api-stack.tsimport { 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ı

typescript
// src/handlers/users/create.tsimport { 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',      }),    };  }};
typescript
// src/handlers/users/get.tsimport { 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 validationconst 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:

typescript
// scripts/generate-openapi.tsimport { 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ırif (require.main === module) {  generateOpenApiSpec();}

Testing Strategy

Unit ve Entegrasyon Testleri

Type safety testleri de kapsar:

typescript
// tests/handlers/users.test.tsimport { 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

typescript
// Yeni alanlar ekleme - backward compatibleconst 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 versioningconst 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

typescript
// src/utils/error-handler.tsexport 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

typescript
// Schema'ları global scope'ta tanımla - cold start performansı içinconst SCHEMAS = {  createUser: CreateUserRequestSchema,  user: UserResponseSchema,} as const;
// Runtime'da validation cache'leconst 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

yaml
# .github/workflows/deploy.ymlname: 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:

typescript
// lib/api/observability.tsimport { 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ımexport 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

typescript
// lib/api/schemas/v1/user.tsexport const UserSchemaV1 = z.object({  // V1 schema});
// lib/api/schemas/v2/user.tsexport const UserSchemaV2 = UserSchemaV1.extend({  // V2 additions  preferences: PreferencesSchema});
// Handler with version supportexport 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

typescript
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

typescript
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