API Gateway для микросервисов на TypeScript: полный гайд

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

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

Начать курс

Введение: почему вашему проекту нужен API Gateway?

Представьте: вы пишете стартап или корпоративное приложение на TypeScript. У вас уже есть сервис пользователей, сервис заказов и сервис уведомлений. Каждый из них живет на своём порту (или даже на своём сервере), общается по HTTP, использует свою базу данных. Всё работает, но клиент (фронтенд, мобильное приложение) начинает задыхаться: чтобы собрать страницу профиля, ему нужно сделать 4-5 запросов к разным сервисам, разобраться с разными форматами ошибок, а ещё авторизоваться в каждом.

Тут на сцену выходит API Gateway — единая точка входа для всех клиентов. Он берёт на себя маршрутизацию, аутентификацию, агрегацию данных, кэширование и даже трансформацию ответов. В этой статье мы напишем настоящий API Gateway на TypeScript с нуля, разберём паттерны и антипаттерны, и покажем, как не наступить на грабли.

Что такое API Gateway простыми словами

API Gateway — это прослойка между клиентом и вашими внутренними сервисами. Клиент (браузер, мобилка, внешний API) стучится только в Gateway. Gateway смотрит на путь запроса, проверяет токен, и перенаправляет запрос в нужный микросервис. Иногда он даже собирает ответ из нескольких сервисов в один — это называется агрегацией.

Зачем это нужно в TypeScript экосистеме? Потому что TypeScript даёт нам типы, интерфейсы и строгость, а Gateway — это идеальное место для валидации и приведения данных к единому контракту.

Архитектура: как это работает на TypeScript

Давайте спроектируем простую, но рабочую архитектуру. У нас будет:

  • Gateway (наш TypeScript сервис) — порт 3000, единственная точка входа.
  • Сервис пользователей (User Service) — порт 4001, REST API.
  • Сервис заказов (Order Service) — порт 4002, REST API.
  • Сервис уведомлений (Notification Service) — порт 4003, REST API.

Все внутренние сервисы живут в своей сети (например, Docker network) и не видны извне. Клиент общается только с Gateway.

// Структура проекта
gateway/
├── src/
│   ├── routes/
│   │   ├── userRoutes.ts
│   │   ├── orderRoutes.ts
│   │   └── notificationRoutes.ts
│   ├── middleware/
│   │   ├── authMiddleware.ts
│   │   ├── rateLimiter.ts
│   │   └── cacheMiddleware.ts
│   ├── services/
│   │   └── proxyService.ts
│   ├── types/
│   │   └── index.ts
│   └── index.ts
├── package.json
└── tsconfig.json

Пишем базовый API Gateway на TypeScript

Начнём с установки зависимостей. Нам понадобится Express (самый популярный фреймворк), http-proxy-middleware для проксирования, и несколько утилит.

npm init -y
npm install express http-proxy-middleware cors helmet express-rate-limit
npm install -D typescript @types/node @types/express ts-node nodemon

Создадим tsconfig.json:

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

Теперь напишем основной файл src/index.ts:

import express, { Request, Response } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import { createProxyMiddleware } from 'http-proxy-middleware';
import { authMiddleware } from './middleware/authMiddleware';
import { rateLimiter } from './middleware/rateLimiter';

const app = express();
const PORT = process.env.PORT || 3000;

// Глобальные middleware
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use(rateLimiter);

// Прокси для сервиса пользователей
app.use('/api/users', authMiddleware, createProxyMiddleware({
  target: 'http://user-service:4001',
  changeOrigin: true,
  pathRewrite: {
    '^/api/users': '/users'
  }
}));

// Прокси для сервиса заказов
app.use('/api/orders', authMiddleware, createProxyMiddleware({
  target: 'http://order-service:4002',
  changeOrigin: true,
  pathRewrite: {
    '^/api/orders': '/orders'
  }
}));

// Прокси для сервиса уведомлений
app.use('/api/notifications', authMiddleware, createProxyMiddleware({
  target: 'http://notification-service:4003',
  changeOrigin: true,
  pathRewrite: {
    '^/api/notifications': '/notifications'
  }
}));

// Health check
app.get('/health', (_req: Request, res: Response) => {
  res.json({ status: 'OK', timestamp: new Date().toISOString() });
});

app.listen(PORT, () => {
  console.log(`API Gateway запущен на порту ${PORT}`);
});

Обратите внимание на pathRewrite. Мы режем префикс /api/users и передаём во внутренний сервис просто /users. Это стандартная практика — Gateway выступает как "швейцарский нож" для трансформации путей.

Аутентификация: единый JWT для всех сервисов

Самая частая задача Gateway — проверка токена. Мы не хотим, чтобы каждый микросервис дёргал базу для проверки JWT. Давайте напишем middleware, который валидирует токен и прокидывает payload в заголовки.

Создаём src/middleware/authMiddleware.ts:

import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

const JWT_SECRET = process.env.JWT_SECRET || 'super-secret-key';

interface JwtPayload {
  userId: string;
  role: string;
}

export const authMiddleware = (req: Request, res: Response, next: NextFunction): void => {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    res.status(401).json({ error: 'Отсутствует токен авторизации' });
    return;
  }

  const token = authHeader.split(' ')[1];

  try {
    const decoded = jwt.verify(token, JWT_SECRET) as JwtPayload;
    
    // Прокидываем данные пользователя в заголовки для внутренних сервисов
    req.headers['x-user-id'] = decoded.userId;
    req.headers['x-user-role'] = decoded.role;
    
    next();
  } catch (error) {
    res.status(401).json({ error: 'Невалидный или просроченный токен' });
  }
};

Теперь все внутренние сервисы могут просто читать заголовки x-user-id и x-user-role — без лишних запросов к базе. Это сильно ускоряет работу и уменьшает нагрузку.

Rate Limiting: защита от ботов и DDoS

Даже самый крутой TypeScript проект может лечь от одного скрипта, который долбит API. По

Блоги

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