Apollo Server для JavaScript (Node.js): полное руководство с примерами

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

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

Начать курс

Apollo Server для JavaScript (Node.js): полное руководство с примерами

Apollo Server — это мощная и гибкая библиотека для создания GraphQL-серверов на Node.js. Она является частью экосистемы Apollo GraphQL и предоставляет разработчикам удобный инструментарий для построения производительных, масштабируемых и легко поддерживаемых API. Если вы знакомы с REST, то GraphQL можно представить как альтернативу, где клиент сам решает, какие данные ему нужны, а сервер отдаёт ровно то, что было запрошено. Apollo Server берёт на себя всю рутину: парсинг запросов, валидацию схемы, выполнение resolvers и форматирование ответа.

Зачем нужен Apollo Server? Во-первых, он значительно упрощает разработку GraphQL-серверов. Вам не нужно писать парсеры, валидаторы или заботиться о безопасности (защита от SQL-инъекций, ограничение глубины запроса и т.д.) — всё это уже встроено. Во-вторых, Apollo Server поддерживает множество интеграций: с Express, Fastify, Koa, AWS Lambda, Azure Functions и другими платформами. Это означает, что вы можете развернуть сервер где угодно — от простого локального приложения до serverless-функции. В-третьих, библиотека активно развивается, имеет большое сообщество и отличную документацию, что делает её стандартом де-факто для GraphQL в Node.js.

В этой статье мы подробно разберём установку, основные методы, напишем несколько рабочих примеров и дадим советы по производительности. Материал будет полезен как новичкам, только начинающим знакомство с GraphQL, так и опытным разработчикам, желающим углубить свои знания об Apollo Server.

1. Установка

Для работы с Apollo Server вам потребуется Node.js версии 14 или выше. Установка производится через npm (или yarn). Основной пакет — @apollo/server. Также вам понадобится graphql — это ядро языка GraphQL, без которого Apollo Server не сможет работать.

npm install @apollo/server graphql

Если вы планируете использовать Apollo Server с Express (самый популярный вариант), установите дополнительно:

npm install express cors body-parser
npm install @as-integrations/express

Для работы с TypeScript (рекомендуется) установите типы:

npm install --save-dev typescript @types/node @types/express

2. Быстрый старт — минимальный рабочий пример

Давайте создадим простейший GraphQL-сервер, который будет приветствовать пользователя. Создайте файл index.js и добавьте следующий код:

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

// 1. Определяем схему (typeDefs)
const typeDefs = `#graphql
  type Query {
    hello: String
  }
`;

// 2. Определяем resolvers
const resolvers = {
  Query: {
    hello: () => 'Привет, мир! Это Apollo Server!',
  },
};

// 3. Создаём экземпляр сервера
const server = new ApolloServer({
  typeDefs,
  resolvers,
});

// 4. Запускаем сервер
const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});

console.log(`🚀 Сервер запущен по адресу: ${url}`);

Запустите сервер командой node index.js (если используете ES-модули, добавьте "type": "module" в package.json). Откройте браузер и перейдите по адресу http://localhost:4000. Вы увидите интерфейс Apollo Sandbox, где можете выполнить запрос:

query {
  hello
}

Ответ должен быть: { "data": { "hello": "Привет, мир! Это Apollo Server!" } }. Поздравляю, ваш первый GraphQL-сервер работает!

3. Основные методы/функции/классы

Рассмотрим ключевые элементы Apollo Server. Для каждого приведём сигнатуру, краткое описание и пример.

3.1. new ApolloServer(config)

Сигнатура: new ApolloServer(config: ApolloServerOptions)

Что делает: Создаёт экземпляр сервера. Принимает объект конфигурации, где обязательны поля typeDefs и resolvers.

const server = new ApolloServer({
  typeDefs: `type Query { ping: String }`,
  resolvers: { Query: { ping: () => 'pong' } },
});

3.2. startStandaloneServer(server, options)

Сигнатура: startStandaloneServer(server: ApolloServer, options?: ListenOptions): Promise<{url: string}>

Что делает: Запускает сервер в автономном режиме (без Express). Удобно для быстрого старта и тестирования.

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});

3.3. server.executeOperation(request)

Сигнатура: server.executeOperation(request: GraphQLRequest): Promise<GraphQLResponse>

Что делает: Позволяет выполнить GraphQL-запрос программно, без HTTP. Полезно для тестирования.

const response = await server.executeOperation({
  query: 'query { hello }',
});
console.log(response.body.singleResult.data); // { hello: 'Привет, мир!' }

3.4. server.start()

Сигнатура: server.start(): Promise<void>

Что делает: Инициализирует сервер (подготавливает схему, плагины). Должен быть вызван перед использованием executeOperation или интеграцией с Express.

await server.start();

3.5. use(plugin) (через конфигурацию)

Сигнатура: В конфиге: plugins: ApolloServerPlugin[]

Что делает: Добавляет плагины для расширения функциональности (логирование, мониторинг, кэширование).

import { ApolloServerPluginInlineTrace } from '@apollo/server/plugin/inlineTrace';

const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [ApolloServerPluginInlineTrace()],
});

3.6. context (в конфигурации)

Сигнатура: context?: (contextParams: ContextParams) => Promise<TContext> | TContext

Что делает: Функция, которая создаёт контекст для каждого запроса. Обычно используется для передачи данных аутентификации, подключения к БД и т.д.

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: async ({ req }) => {
    const token = req.headers.authorization || '';
    const user = await getUserFromToken(token);
    return { user };
  },
});

3.7. formatError (в конфигурации)

Сигнатура: formatError?: (formattedError: GraphQLFormattedError, error: GraphQLError) => GraphQLFormattedError

Что делает: Позволяет кастомизировать формат ошибок перед отправкой клиенту. Полезно для скрытия деталей реализации.

const server = new ApolloServer({
  typeDefs,
  resolvers,
  formatError: (formattedError) => {
    return {
      message: formattedError.message,
      code: formattedError.extensions?.code || 'INTERNAL_ERROR',
    };
  },
});

3.8. introspection (в конфигурации)

Сигнатура: introspection?: boolean

Что делает: Включает или отключает интроспекцию схемы (возможность запрашивать описание схемы). По умолчанию true. В production рекомендуется отключать.

const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: process.env.NODE_ENV !== 'production',
});

3.9. includeStacktraceInErrorResponses (в конфигурации)

Сигнатура: includeStacktraceInErrorResponses?: boolean

Что делает: Определяет, включать ли стектрейс в ответы с ошибками. По умолчанию true в dev и false в production.

const server = new ApolloServer({
  typeDefs,
  resolvers,
  includeStacktraceInErrorResponses: false,
});

3.10. ApolloServerPluginDrainHttpServer

Сигнатура: ApolloServerPluginDrainHttpServer(options: { httpServer: HttpServer }): ApolloServerPlugin

Что делает: Плагин для корректного завершения работы HTTP-сервера (graceful shutdown). Обязателен при использовании с Express.

import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import http from 'http';

const httpServer = http.createServer();
const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});

4. Реальный практический пример (100-200 строк кода)

Создадим полноценный сервер для управления списком задач (Todo List) с использованием Express, хранением данных в памяти и поддержкой мутаций.

import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import express from 'express';
import http from 'http';
import cors from 'cors';
import bodyParser from 'body-parser';

// Временное хранилище
let todos = [
  { id: '1', title: 'Изучить GraphQL', completed: false },
  { id: '2', title: 'Написать статью', completed: true },
];

// Схема
const typeDefs = `#graphql
  type Todo {
    id: ID!
    title: String!
    completed: Boolean!
  }

  type Query {
    todos: [Todo!]!
    todo(id: ID!): Todo
  }

  type Mutation {
    addTodo(title: String!): Todo!
    toggleTodo(id: ID!): Todo!
    deleteTodo(id: ID!): Boolean!
  }
`;

// Resolvers
const resolvers = {
  Query: {
    todos: () => todos,
    todo: (_, { id }) => todos.find(t => t.id === id),
  },
  Mutation: {
    addTodo: (_, { title }) => {
      const newTodo = {
        id: String(todos.length + 1),
        title,
        completed: false,
      };
      todos.push(newTodo);
      return newTodo;
    },
    toggleTodo: (_, { id }) => {
      const todo = todos.find(t => t.id === id);
      if (!todo) throw new Error('Todo not found');
      todo.completed = !todo.completed;
      return todo;
    },
    deleteTodo: (_, { id }) => {
      const index = todos.findIndex(t => t.id === id);
      if (index === -1) return false;
      todos.splice(index, 1);
      return true;
    },
  },
};

// Создаём Express-приложение
const app = express();
const httpServer = http.createServer(app);

// Создаём Apollo Server
const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
  introspection: true,
});

// Запускаем сервер
await server.start();

// Подключаем middleware
app.use(
  '/graphql',
  cors(),
  bodyParser.json(),
  expressMiddleware(server, {
    context: async ({ req }) => {
      // Здесь можно добавить аутентификацию
      return { authToken: req.headers.authorization };
    },
  }),
);

// Запускаем HTTP-сервер
const PORT = 4000;
httpServer.listen(PORT, () => {
  console.log(`🚀 Сервер запущен на http://localhost:${PORT}/graphql`);
});

Теперь вы можете выполнять запросы через Apollo Sandbox или любой GraphQL-клиент. Пример мутации:

mutation {
  addTodo(title: "Купить молоко") {
    id
    title
    completed
  }
}

5. Советы по производительности и типичные ошибки

Советы по производительности:

  • Используйте DataLoader для батчинга и кэширования запросов к БД. Это предотвращает проблему N+1, когда для каждого элемента списка выполняется отдельный запрос.
  • Отключайте интроспекцию в production: introspection: false. Это не только ускоряет ответ, но и повышает безопасность.
  • Используйте персистентные запросы (Persisted Queries) — это позволяет сократить размер запроса и ускорить его обработку.
  • Оптимизируйте resolvers: избегайте дорогих операций в синхронных resolvers, используйте асинхронные вызовы, где это возможно.
  • Настройте логирование и мониторинг с помощью плагинов (например, Apollo Studio) для выявления узких мест.
  • Используйте сжатие ответов (gzip/brotli) на уровне HTTP-сервера.

Типичные ошибки:

  • Ошибка: "Cannot query field ... on type ..." — означает, что поле не определено в схеме. Проверьте typeDefs.
  • <

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