Собираем сервер на Apollo Server 4
Apollo Server 4 — популярный способ поднять GraphQL-сервер на Node.js. Нужны всего две вещи: схема (typeDefs) и резолверы — остальное библиотека берёт на себя.
Схема плюс резолверы равно сервер. Apollo Server 4 убирает весь обвес и оставляет только эти две сущности — и работающий эндпоинт.
Мы разобрали схему и резолверы по отдельности — теперь соберём их в живой сервер. Самый распространённый инструмент в экосистеме Node.js — Apollo Server 4. Сначала ставим пакеты:
npm install @apollo/server graphql
Дальше нужны две части. typeDefs — схема на SDL в виде строки. resolvers — карта функций, которую мы изучили в прошлом уроке. Передаём обе в конструктор ApolloServer:
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
// 1. Схема
const typeDefs = `#graphql
type Book { title: String!, author: String! }
type Query { books: [Book!]! }
`;
// 2. Данные + резолверы
const books = [
{ title: "Чистый код", author: "Мартин" },
{ title: "Грокаем алгоритмы", author: "Бхаргава" },
];
const resolvers = {
Query: { books: () => books },
};
// 3. Сервер
const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
});
console.log("Сервер готов на " + url);
Запускаем — и на http://localhost:4000 открывается интерактивная песочница (Apollo Sandbox), где можно писать запросы и сразу видеть ответы. Это и есть весь минимальный сервер.
Что нового в версии 4
Apollo Server 4 — это переработка предыдущих версий. Ключевые отличия, которые стоит знать: единый пакет @apollo/server вместо россыпи старых; функция startStandaloneServer для быстрого старта без ручной настройки Express; и обязательно — context создаётся на каждый запрос функцией context. Именно сюда кладут текущего пользователя и загрузчики данных:
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => {
const user = await getUserFromToken(req.headers.authorization);
return { user, db }; // попадёт в третий аргумент резолверов
},
});
Как работает под капотом
При старте Apollo берёт typeDefs, парсит SDL и строит исполняемую схему, привязывая к ней резолверы. На каждый HTTP-запрос он: вызывает функцию context (создаёт свежий контекст), парсит и валидирует запрос по схеме, выполняет резолверы и сериализует результат в конверт { data, errors }. Смоделируем мини-«движок Apollo» на JS:
function makeServer(resolvers) {
return {
execute(operation, field, args, makeContext) {
const context = makeContext(); // свежий контекст на запрос
const fn = resolvers[operation][field];
try {
return { data: { [field]: fn(null, args, context) } };
} catch (e) {
return { data: { [field]: null }, errors: [{ message: e.message }] };
}
}
};
}
const server = makeServer({
Query: { books: (_p, _a, ctx) => ctx.db.books }
});
const res = server.execute("Query", "books", {},
() => ({ db: { books: ["Чистый код", "Грокаем алгоритмы"] } }));
console.log(JSON.stringify(res));
Попробуй сам ▶ — выброси ошибку внутри резолвера (throw new Error("нет доступа")) и увидишь, как она окажется в errors, а поле станет null. Ровно это делает Apollo.
Частые ошибки
- Глобальный context. Контекст создаётся на каждый запрос неспроста — туда кладут per-request вещи (пользователь, загрузчики). Глобальный shared-объект приведёт к утечкам данных между запросами.
- Несоответствие схемы и резолверов. Поле есть в
typeDefs, но нет данных/резолвера — получишь null или ошибку. Держи их синхронно. - Оставить introspection и Sandbox в проде. Удобно при разработке, но в продакшене это раскрывает всю схему — об этом в уроке про безопасность.
Best practices
- Держи схему в отдельных
.graphql-файлах, а резолверы — по модулям, повторяющим типы; так сервер растёт без хаоса. - Всё «на запрос» (пользователь, БД-клиент, DataLoader) создавай в функции
context, а не в глобальной области. - Для продакшена подключай Apollo к Express/Fastify и закрывай Sandbox/introspection, а не используй standalone «как есть».
Итоги
Apollo Server 4 поднимает GraphQL-сервер из двух частей: typeDefs (схема на SDL) и resolvers (карта функций). startStandaloneServer даёт быстрый старт и Sandbox, а функция context создаёт свежий контекст на каждый запрос — туда кладут пользователя и загрузчики. Дальше используем context, чтобы решить, кому что показывать — авторизация в резолверах.