Serverless функции на TypeScript: AWS Lambda от А до Я

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Serverless функции на TypeScript: Полное руководство по AWS Lambda

Serverless-архитектура произвела революцию в подходе к разработке бэкенда. Вместо управления серверами разработчики могут сосредоточиться исключительно на бизнес-логике. AWS Lambda — один из самых популярных сервисов для запуска serverless функций. В этом руководстве мы разберем создание, настройку и деплой Lambda-функций на TypeScript — от базовых концепций до production-решений.

Почему TypeScript для AWS Lambda?

TypeScript добавляет статическую типизацию в JavaScript, что критически важно для serverless-функций:

  • Безопасность типов — предотвращает ошибки на этапе компиляции
  • Автодополнение — IDE подсказывает методы AWS SDK
  • Читаемость — интерфейсы и типы документируют структуру данных
  • Масштабируемость — легче поддерживать код с ростом проекта

Настройка проекта

1. Инициализация и установка зависимостей

Создайте новый проект и установите необходимые пакеты:

mkdir my-lambda-project
cd my-lambda-project
npm init -y

npm install @types/aws-lambda aws-sdk
npm install -D typescript ts-node @types/node

2. Конфигурация TypeScript

Создайте файл tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Создание первой Lambda-функции

Базовая структура обработчика

Создайте файл src/handler.ts:

import { Handler, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

interface UserData {
  userId: string;
  name: string;
  email: string;
}

export const handler: Handler<APIGatewayProxyEvent, APIGatewayProxyResult> = async (event) => {
  try {
    // Парсинг тела запроса
    const body: UserData = JSON.parse(event.body || '{}');
    
    // Валидация
    if (!body.userId || !body.name) {
      return {
        statusCode: 400,
        headers: {
          'Content-Type': 'application/json',
          'Access-Control-Allow-Origin': '*'
        },
        body: JSON.stringify({ error: 'Missing required fields' })
      };
    }

    // Бизнес-логика
    const result = {
      id: body.userId,
      displayName: body.name.toUpperCase(),
      createdAt: new Date().toISOString()
    };

    return {
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
      },
      body: JSON.stringify(result)
    };
  } catch (error) {
    console.error('Error processing request:', error);
    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Internal server error' })
    };
  }
};

Работа с окружением и секретами

Используйте переменные окружения для конфигурации:

import { Handler } from 'aws-lambda';

interface Config {
  DB_TABLE: string;
  MAX_RETRIES: number;
}

export const handler: Handler = async (event) => {
  const config: Config = {
    DB_TABLE: process.env.DB_TABLE || 'default-table',
    MAX_RETRIES: parseInt(process.env.MAX_RETRIES || '3', 10)
  };

  // Используйте AWS Secrets Manager или Parameter Store
  // для чувствительных данных
  return {
    statusCode: 200,
    body: JSON.stringify({ table: config.DB_TABLE })
  };
};

Продвинутые техники

1. Обработка ошибок и логирование

Создайте централизованный обработчик ошибок:

// src/utils/error-handler.ts
export class AppError extends Error {
  constructor(
    public statusCode: number,
    public message: string,
    public details?: unknown
  ) {
    super(message);
    this.name = 'AppError';
  }
}

export function formatError(error: unknown) {
  if (error instanceof AppError) {
    return {
      statusCode: error.statusCode,
      body: JSON.stringify({
        error: error.message,
        details: error.details
      })
    };
  }

  console.error('Unhandled error:', error);
  return {
    statusCode: 500,
    body: JSON.stringify({ error: 'Internal server error' })
  };
}

2. Middleware для повторяющихся задач

// src/middleware/cors.ts
import { Handler } from 'aws-lambda';

type Middleware = (handler: Handler) => Handler;

export const withCORS: Middleware = (handler) => {
  return async (event, context, callback) => {
    const response = await handler(event, context, callback);
    
    return {
      ...response,
      headers: {
        ...response.headers,
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE',
        'Access-Control-Allow-Headers': 'Content-Type,Authorization'
      }
    };
  };
};

3. Работа с DynamoDB

// src/services/database.ts
import { DynamoDB } from 'aws-sdk';

const dynamoDb = new DynamoDB.DocumentClient();
const TABLE_NAME = process.env.TABLE_NAME || 'Users';

export interface User {
  userId: string;
  name: string;
  email: string;
  createdAt: string;
}

export class UserRepository {
  async create(user: User): Promise<User> {
    const params = {
      TableName: TABLE_NAME,
      Item: user,
      ConditionExpression: 'attribute_not_exists(userId)'
    };

    await dynamoDb.put(params).promise();
    return user;
  }

  async getById(userId: string): Promise<User | null> {
    const params = {
      TableName: TABLE_NAME,
      Key: { userId }
    };

    const result = await dynamoDb.get(params).promise();
    return (result.Item as User) || null;
  }

  async update(userId: string, updates: Partial<User>): Promise<User> {
    const updateExpression = Object.keys(updates)
      .map(key => `#${key} = :${key}`)
      .join(', ');

    const expressionAttributeNames = Object.keys(updates)
      .reduce((acc, key) => ({ ...acc, [`#${key}`]: key }), {});

    const expressionAttributeValues = Object.entries(updates)
      .reduce((acc, [key, value]) => ({ ...acc, [`:${key}`]: value }), {});                

Блоги

Рекомендаций по книгам