lenec ru

← все посты

GraphQL vs REST: когда использовать, trade-offs, N+1 problem

17K

API без документации — источник фрустрации для разработчиков и барьер для интеграции. Устаревшая документация в Confluence или Google Docs быстро расходится с реальностью кода. OpenAPI Specification (ранее Swagger) решает эту проблему через machine-readable описание API, из которого автоматически генерируются интерактивные документы, клиентские SDK и валидаторы.

Зачем документация API

Onboarding разработчиков

Качественная документация API сокращает время интеграции с недель до часов. Разработчик видит:

  • Доступные endpoints и методы
  • Структуру request/response
  • Примеры запросов
  • Коды ошибок и их значения
  • Аутентификацию и авторизацию

Интерактивная документация с try-it-out функциональностью позволяет тестировать API прямо в браузере без написания кода.

Contract-first design

OpenAPI Specification как контракт между frontend и backend:

  1. Команды договариваются об API через OpenAPI spec
  2. Frontend разрабатывает UI на основе mock-серверов
  3. Backend реализует API согласно спецификации
  4. Автоматические тесты проверяют соответствие контракту

Contract-first подход предотвращает breaking changes и рассинхронизацию команд.

Автоматическая генерация клиентов

Из OpenAPI spec генерируются типизированные клиенты для любого языка:

openapi-generator-cli generate \
  -i openapi.yaml \
  -g typescript-axios \
  -o ./generated-client

Клиент содержит типы, методы и валидацию — разработчик получает autocomplete и type safety из коробки.

OpenAPI Specification: структура, paths, schemas, parameters

Базовая структура OpenAPI 3.0

openapi: 3.0.3
info:
  title: User Management API
  version: 1.0.0
  description: API for managing users and authentication
  contact:
    name: API Support
    email: api@example.com

servers:
  - url: https://api.example.com/v1
    description: Production server
  - url: https://staging-api.example.com/v1
    description: Staging server

paths:
  /users:
    get:
      summary: List all users
      operationId: listUsers
      tags:
        - Users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
                  pagination:
                    $ref: '#/components/schemas/Pagination'

components:
  schemas:
    User:
      type: object
      required:
        - id
        - email
        - name
      properties:
        id:
          type: string
          format: uuid
          example: "123e4567-e89b-12d3-a456-426614174000"
        email:
          type: string
          format: email
          example: "user@example.com"
        name:
          type: string
          example: "John Doe"
        createdAt:
          type: string
          format: date-time
    
    Pagination:
      type: object
      properties:
        page:
          type: integer
        limit:
          type: integer
        total:
          type: integer

Описание POST endpoint с request body

paths:
  /users:
    post:
      summary: Create a new user
      operationId: createUser
      tags:
        - Users
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
                - name
                - password
              properties:
                email:
                  type: string
                  format: email
                name:
                  type: string
                  minLength: 2
                  maxLength: 100
                password:
                  type: string
                  format: password
                  minLength: 8
            example:
              email: "newuser@example.com"
              name: "Jane Smith"
              password: "SecurePass123!"
      responses:
        '201':
          description: User created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          description: Invalid input
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: User already exists

components:
  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
        message:
          type: string
        details:
          type: array
          items:
            type: object

Аутентификация и security schemes

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key

security:
  - bearerAuth: []

paths:
  /users/me:
    get:
      summary: Get current user profile
      security:
        - bearerAuth: []
      responses:
        '200':
          description: User profile
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '401':
          description: Unauthorized

Auto-generation из кода: swagger-jsdoc, decorators в NestJS

swagger-jsdoc в Express

Генерация OpenAPI spec из JSDoc комментариев:

const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');

const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'User API',
      version: '1.0.0',
    },
    servers: [
      {
        url: 'http://localhost:3000/api',
      },
    ],
  },
  apis: ['./routes/*.js', './models/*.js'],
};

const swaggerSpec = swaggerJsdoc(options);

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));

/**
 * @openapi
 * /users:
 *   get:
 *     summary: Get all users
 *     tags:
 *       - Users
 *     parameters:
 *       - in: query
 *         name: page
 *         schema:
 *           type: integer
 *         description: Page number
 *     responses:
 *       200:
 *         description: List of users
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items:
 *                 $ref: '#/components/schemas/User'
 */
app.get('/api/users', async (req, res) => {
  const users = await User.findAll();
  res.json(users);
});

/**
 * @openapi
 * components:
 *   schemas:
 *     User:
 *       type: object
 *       required:
 *         - email
 *         - name
 *       properties:
 *         id:
 *           type: string
 *           format: uuid
 *         email:
 *           type: string
 *           format: email
 *         name:
 *           type: string
 */

Decorators в NestJS

NestJS автоматически генерирует OpenAPI spec из TypeScript decorators:

import { Controller, Get, Post, Body, Query } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger';

class CreateUserDto {
  @ApiProperty({ example: 'user@example.com' })
  email: string;

  @ApiProperty({ example: 'John Doe', minLength: 2 })
  name: string;

  @ApiProperty({ example: 'SecurePass123!', minLength: 8 })
  password: string;
}

class UserResponseDto {
  @ApiProperty({ format: 'uuid' })
  id: string;

  @ApiProperty()
  email: string;

  @ApiProperty()
  name: string;

  @ApiProperty({ format: 'date-time' })
  createdAt: Date;
}

@ApiTags('users')
@Controller('users')
export class UsersController {
  @Get()
  @ApiOperation({ summary: 'Get all users' })
  @ApiQuery({ name: 'page', required: false, type: Number })
  @ApiQuery({ name: 'limit', required: false, type: Number })
  @ApiResponse({ status: 200, type: [UserResponseDto] })
  async findAll(
    @Query('page') page: number = 1,
    @Query('limit') limit: number = 20,
  ): Promise<UserResponseDto[]> {
    return this.usersService.findAll(page, limit);
  }

  @Post()
  @ApiOperation({ summary: 'Create a new user' })
  @ApiResponse({ status: 201, type: UserResponseDto })
  @ApiResponse({ status: 400, description: 'Invalid input' })
  async create(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
    return this.usersService.create(createUserDto);
  }
}

Настройка Swagger в main.ts:

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('User Management API')
    .setDescription('API for managing users')
    .setVersion('1.0')
    .addBearerAuth()
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api-docs', app, document);

  await app.listen(3000);
}
bootstrap();

Interactive docs: Swagger UI, ReDoc, try-it-out функциональность

Swagger UI

Swagger UI — интерактивная документация с возможностью тестирования API:

const swaggerUi = require('swagger-ui-express');
const YAML = require('yamljs');

const swaggerDocument = YAML.load('./openapi.yaml');

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, {
  customCss: '.swagger-ui .topbar { display: none }',
  customSiteTitle: 'User API Documentation',
}));

Swagger UI предоставляет:

  • Список всех endpoints с группировкой по tags
  • Детальное описание параметров и схем
  • Try-it-out кнопку для выполнения запросов
  • Примеры request/response
  • Авторизацию через UI (Bearer token, API key)

ReDoc — альтернативный рендерер

ReDoc — более чистый и читаемый UI для документации:

const redoc = require('redoc-express');

app.get('/docs', redoc({
  title: 'User API Documentation',
  specUrl: '/openapi.yaml',
  redocOptions: {
    theme: {
      colors: {
        primary: {
          main: '#3498db'
        }
      }
    }
  }
}));

ReDoc лучше подходит для публичной документации (без try-it-out), Swagger UI — для внутренней разработки.

Кастомизация Swagger UI

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, {
  explorer: true,
  customCss: `
    .swagger-ui .topbar { background-color: #2c3e50; }
    .swagger-ui .info .title { color: #3498db; }
  `,
  customSiteTitle: 'My API Docs',
  customfavIcon: '/favicon.ico',
  swaggerOptions: {
    persistAuthorization: true,
    displayRequestDuration: true,
    filter: true,
    tryItOutEnabled: true,
  }
}));

Versioning документации: отдельные спецификации для v1/v2, changelog

Структура для нескольких версий

docs/
  ├── openapi-v1.yaml
  ├── openapi-v2.yaml
  └── changelog.md

routes/
  ├── v1/
  │   └── users.js
  └── v2/
      └── users.js

Настройка маршрутов для разных версий:

const swaggerV1 = YAML.load('./docs/openapi-v1.yaml');
const swaggerV2 = YAML.load('./docs/openapi-v2.yaml');

app.use('/api-docs/v1', swaggerUi.serve);
app.get('/api-docs/v1', swaggerUi.setup(swaggerV1, { customSiteTitle: 'API v1' }));

app.use('/api-docs/v2', swaggerUi.serveFiles(swaggerV2));
app.get('/api-docs/v2', swaggerUi.setup(swaggerV2, { customSiteTitle: 'API v2' }));

Changelog в OpenAPI

Документирование изменений между версиями:

info:
  title: User Management API
  version: 2.0.0
  description: |
    API for managing users and authentication.
    
    ## Changelog
    
    ### v2.0.0 (2026-05-26)
    - **BREAKING**: Changed `/users` response structure
    - Added pagination metadata
    - Deprecated `GET /user/:id` in favor of `GET /users/:id`
    - Added `PATCH /users/:id` for partial updates
    
    ### v1.0.0 (2026-01-15)
    - Initial release

Deprecation warnings

paths:
  /user/{id}:
    get:
      deprecated: true
      summary: Get user by ID (deprecated)
      description: |
        **This endpoint is deprecated and will be removed in v3.0.0.**
        Use `GET /users/{id}` instead.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string

Best practices и рекомендации

  • Примеры для всех endpoints — добавляйте реалистичные примеры request/response для быстрого понимания.
  • Описание ошибок — документируйте все возможные коды ошибок с примерами.
  • Валидация спецификации — используйте swagger-cli validate openapi.yaml для проверки корректности.
  • CI/CD интеграция — автоматически генерируйте и публикуйте документацию при каждом деплое.
  • Contract testing — используйте инструменты типа Dredd или Prism для проверки соответствия API спецификации.
  • Не дублируйте информацию — используйте $ref для переиспользования схем.
  • Семантическое версионирование — следуйте SemVer для версий API (major.minor.patch).

Итоги

OpenAPI Specification — стандарт документирования REST API, обеспечивающий machine-readable описание endpoints, схем и параметров. Auto-generation из кода через swagger-jsdoc или NestJS decorators гарантирует актуальность документации. Swagger UI и ReDoc предоставляют интерактивные документы с try-it-out функциональностью для быстрого тестирования. Versioning через отдельные спецификации и changelog позволяет управлять эволюцией API без breaking changes. Contract-first подход с OpenAPI ускоряет разработку, снижает количество багов и улучшает developer experience для всех потребителей API.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.