AWS CDK – Budujemy REST API od zera do produkcji

AWS CDK pozwala zarządzać infrastrukturą w AWS używając TypeScript zamiast YAML-a. W tym tutorialu zbudujesz od podstaw działające REST API z Lambda, DynamoDB i API Gateway. Pełny, gotowy do użycia kod i deployment w mniej niż godzinę.

1. Wprowadzenie

AWS Cloud Development Kit (CDK) to framework od Amazon Web Services, który pozwala definiować infrastrukturę chmurową przy użyciu prawdziwych języków programowania takich jak TypeScript, Python, Java czy C#. W przeciwieństwie do deklaratywnych szablonów CloudFormation czy Terraform HCL, CDK daje programistom pełną moc języków programowania – pętle, warunki, klasy i funkcje.

W tym artykule stworzymy kompletną, production-ready aplikację serverless: REST API z obsługą CRUD dla listy zadań (To-Do List), wykorzystując Lambda, DynamoDB i API Gateway.

1.1. Dlaczego CDK zamiast CloudFormation?

  • Możliwość używania pętli, warunków i funkcji – DRY principle w praktyce
  • Type safety i autouzupełnianie w IDE – mniej błędów, szybszy development
  • Reużywalne komponenty (Constructs) – buduj własne biblioteki infrastruktury
  • Integracja z ekosystemem języka (npm, pip) – łatwe zarządzanie zależnościami
  • Szybszy development – mniej boilerplate code

2. Wymagania Wstępne

Przed rozpoczęciem upewnij się, że masz zainstalowane:

  • Node.js 18.x lub nowszy
  • AWS CLI skonfigurowane z credentials
  • AWS CDK CLI: npm install -g aws-cdk
  • Podstawowa znajomość TypeScript i AWS (Lambda, DynamoDB, API Gateway)

3. Inicjalizacja Projektu CDK

Stwórzmy nowy projekt CDK używając TypeScript:

mkdir my-todo-api && cd my-todo-api
cdk init app --language typescript

Następnie zainstaluj potrzebne pakiety:

npm install @aws-cdk/aws-lambda-nodejs
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
npm install -D @types/aws-lambda

CDK utworzy podstawową strukturę projektu:

Plik/Katalog Opis
lib/ Główny kod CDK – tutaj definiujemy stacki
bin/ Entry point aplikacji CDK
cdk.json Konfiguracja CDK i context values
lambda/ Kod Lambda functions (utworzymy własny)

📝 Ważne: Nazwa projektu, którą podasz w mkdir, określi nazwy plików. Dla my-todo-api będziesz miał lib/my-todo-api-stack.ts i klasę MyTodoApiStack. CDK automatycznie konwertuje kebab-case na PascalCase.

4. Architektura Aplikacji

Nasza aplikacja będzie składać się z następujących komponentów:

  1. DynamoDB Table – przechowywanie zadań
  2. Lambda Functions – logika biznesowa (CRUD operations)
  3. API Gateway REST API – endpoint HTTP
  4. IAM Roles – uprawnienia z least privilege principle
  5. CloudWatch Logs – monitoring i debugging

5. Implementacja Lambda Function

Najpierw stwórzmy katalog dla kodu Lambda i zaimplementujmy CRUD operations. Utwórz plik lambda/todo-handler.ts:

import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, PutCommand, GetCommand,
         ScanCommand, DeleteCommand } from '@aws-sdk/lib-dynamodb';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
const tableName = process.env.TABLE_NAME!;

interface Todo {
  id: string;
  title: string;
  completed: boolean;
  createdAt: string;
}

export const handler = async (event: APIGatewayProxyEvent):
  Promise<APIGatewayProxyResult> => {
  const method = event.httpMethod;
  const path = event.path;

  try {
    if (method === 'GET' && path === '/todos') {
      return await getAllTodos();
    }
    if (method === 'POST' && path === '/todos') {
      return await createTodo(JSON.parse(event.body || '{}'));
    }
    if (method === 'GET' && path.startsWith('/todos/')) {
      const id = path.split('/')[2];
      return await getTodo(id);
    }
    if (method === 'DELETE' && path.startsWith('/todos/')) {
      const id = path.split('/')[2];
      return await deleteTodo(id);
    }
    return { statusCode: 404, body: JSON.stringify({ error: 'Not found' }) };
  } catch (error) {
    console.error(error);
    return { statusCode: 500, body: JSON.stringify({ error: 'Internal error' }) };
  }
};

Funkcje pomocnicze dla operacji CRUD:

async function createTodo(body: Partial<Todo>) {
  const todo: Todo = {
    id: Date.now().toString(),
    title: body.title || '',
    completed: false,
    createdAt: new Date().toISOString()
  };
  await docClient.send(new PutCommand({ TableName: tableName, Item: todo }));
  return { statusCode: 201, body: JSON.stringify(todo) };
}

async function getAllTodos() {
  const result = await docClient.send(new ScanCommand({ TableName: tableName }));
  return { statusCode: 200, body: JSON.stringify(result.Items) };
}

async function getTodo(id: string) {
  const result = await docClient.send(
    new GetCommand({ TableName: tableName, Key: { id } })
  );
  if (!result.Item) {
    return { statusCode: 404, body: JSON.stringify({ error: 'Not found' }) };
  }
  return { statusCode: 200, body: JSON.stringify(result.Item) };
}

async function deleteTodo(id: string) {
  await docClient.send(new DeleteCommand({ TableName: tableName, Key: { id } }));
  return { statusCode: 204, body: '' };
}

6. Definicja CDK Stack

Teraz zdefiniujmy infrastrukturę w pliku lib/my-todo-api-stack.ts. Zastąp całą zawartość tego pliku poniższym kodem:

import * as cdk from 'aws-cdk-lib';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Construct } from 'constructs';

export class MyTodoApiStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // DynamoDB Table
    const todosTable = new dynamodb.Table(this, 'TodosTable', {
      partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      encryption: dynamodb.TableEncryption.AWS_MANAGED,
      pointInTimeRecovery: true
    });

    // Lambda Function
    const todoHandler = new NodejsFunction(this, 'TodoHandler', {
      runtime: lambda.Runtime.NODEJS_20_X,
      entry: 'lambda/todo-handler.ts',
      handler: 'handler',
      timeout: cdk.Duration.seconds(10),
      environment: { TABLE_NAME: todosTable.tableName }
    });

    // Grant permissions
    todosTable.grantReadWriteData(todoHandler);

    // API Gateway
    const api = new apigateway.RestApi(this, 'TodoApi', {
      restApiName: 'Todo Service',
      description: 'Todo API with CRUD operations',
      deployOptions: {
        stageName: 'prod',
        loggingLevel: apigateway.MethodLoggingLevel.INFO
      }
    });

    const integration = new apigateway.LambdaIntegration(todoHandler);
    const todos = api.root.addResource('todos');
    todos.addMethod('GET', integration);
    todos.addMethod('POST', integration);

    const todo = todos.addResource('{id}');
    todo.addMethod('GET', integration);
    todo.addMethod('DELETE', integration);

    // Outputs
    new cdk.CfnOutput(this, 'ApiUrl', {
      value: api.url,
      description: 'API Gateway endpoint URL'
    });
  }
}

💡 Tip: Zwróć uwagę jak CDK automatycznie tworzy IAM roles i zarządza uprawnieniami poprzez metodę grantReadWriteData(). W CloudFormation wymagałoby to wielu linii kodu!

7. Deployment do AWS

Przed pierwszym deploymentem musisz wykonać bootstrap AWS environment:

cdk bootstrap

Następnie możesz zdeployować aplikację:

cdk synth      # Generuje CloudFormation template
cdk diff       # Pokazuje różnice przed deploymentem
cdk deploy     # Deployuje do AWS

Po zakończeniu deployu CDK wyświetli URL API Gateway. Jeśli nie widzisz URL-a, użyj:

aws cloudformation describe-stacks \
  --stack-name MyTodoApiStack \
  --query "Stacks[0].Outputs[?OutputKey=='ApiUrl'].OutputValue" \
  --output text

Lub sprawdź w AWS Console → CloudFormation → Twój stack → zakładka „Outputs”.

8. Testowanie API

Możesz przetestować API używając curl lub Postman:

# Tworzenie nowego todo
curl -X POST https://xxx.execute-api.region.amazonaws.com/prod/todos \
  -H 'Content-Type: application/json' \
  -d '{"title":"Nauczyć się AWS CDK"}'

# Lista wszystkich todo
curl https://xxx.execute-api.region.amazonaws.com/prod/todos

# Pobranie konkretnego todo
curl https://xxx.execute-api.region.amazonaws.com/prod/todos/123

# Usunięcie todo
curl -X DELETE https://xxx.execute-api.region.amazonaws.com/prod/todos/123

9. Production-Ready Best Practices

9.1. Environment Variables i Context

W pliku cdk.json dodaj context values dla różnych środowisk:

{
  "context": {
    "dev": { "removalPolicy": "DESTROY" },
    "prod": { "removalPolicy": "RETAIN" }
  }
}

9.2. Monitoring i Alarmy

Dodaj CloudWatch alarmy do Lambda:

import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';

const alarm = new cloudwatch.Alarm(this, 'LambdaErrorAlarm', {
  metric: todoHandler.metricErrors(),
  threshold: 5,
  evaluationPeriods: 1,
  alarmDescription: 'Alert when Lambda errors exceed threshold'
});

9.3. Security Best Practices

  • Użyj AWS Secrets Manager dla wrażliwych danych
  • Włącz encryption at rest dla DynamoDB
  • Dodaj CORS configuration do API Gateway
  • Użyj API Keys lub AWS Cognito dla autentykacji
  • Włącz X-Ray tracing dla debugowania

9.4. Cost Optimization

  • Użyj PAY_PER_REQUEST billing mode dla DynamoDB (płacisz tylko za to czego używasz)
  • Ustaw odpowiedni timeout dla Lambda (nie więcej niż potrzeba)
  • Rozważ Lambda Powertools dla lepszej observability
  • Dodaj tagging strategy do wszystkich resources dla cost allocation

10. Czyszczenie Zasobów

Aby usunąć wszystkie zasoby utworzone przez CDK:

cdk destroy

⚠️ Uwaga: CDK automatycznie usunie wszystkie zasoby w odpowiedniej kolejności, respektując zależności między nimi. Upewnij się, że chcesz usunąć dane z DynamoDB!

11. Podsumowanie

W tym artykule stworzyliśmy kompletną, production-ready aplikację serverless używając AWS CDK. Nauczyliśmy się:

  • Jak inicjalizować projekt CDK i organizować kod
  • Definiowania infrastruktury w TypeScript z pełnym type safety
  • Integracji Lambda, DynamoDB i API Gateway
  • Best practices dla produkcji (security, monitoring, cost optimization)
  • Deployowania i testowania aplikacji w AWS

AWS CDK to potężne narzędzie, które łączy moc języków programowania z zarządzaniem infrastrukturą. Kod z tego artykułu stanowi solidną bazę do budowania bardziej złożonych aplikacji serverless.

Miłego kodowania z AWS CDK! 🚀

Related Posts