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.
- <