Как создать GraphQL API с MongoDB и Apollo Server

Зачем выбирать GraphQL вместо REST
REST остаётся популярным, но его фиксированная структура эндпоинтов часто приводит к избыточной передаче данных и множественным обращениями за связанными сущностями. GraphQL даёт один входной эндпоинт и позволяет клиенту описать, какие поля ему нужны. Это сокращает объём передаваемых данных и упрощает эволюцию API.
Короткое определение: GraphQL — язык запросов и спецификация для реализации API, где клиент запрашивает конкретную форму данных, а сервер отвечает строго в этом же формате.
Компоненты архитектуры GraphQL (в двух строках)
- Схема (Schema): описывает типы данных и операции.
- Запросы (Query): чтение данных.
- Мутации (Mutation): изменение данных.

Подготовка MongoDB
- Установите MongoDB локально или создайте бесплатный кластер в облаке (например, MongoDB Atlas).
- Скопируйте строку подключения (connection URI). Пример локально: mongodb://localhost:27017
Важно: не храните секреты URI в исходниках — используйте переменные окружения.
Источник кода этого проекта доступен в репозитории GitHub (указан в исходном материале).
Создание проекта и установка зависимостей
Откройте терминал и выполните:
mkdir graphql-API-mongoDB
cd graphql-API-mongoDBИнициализируйте npm-проект:
npm init --yesУстановите пакеты:
npm install apollo-server graphql mongooseСоздайте файл index.js в корне проекта.
Настройка Apollo Server
Вставьте в index.js следующий код (файл из исходного примера):
const { ApolloServer } = require('apollo-server');
const mongoose = require('mongoose');
const typeDefs = require("./graphql/typeDefs");
const resolvers = require("./graphql/resolvers");
const server = new ApolloServer({
typeDefs,
resolvers
});
const MONGO_URI = 'mongodb://localhost:27017';
mongoose
.connect(MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
console.log(`Db Connected`);
return server.listen({ port: 5000 });
})
.then((res) => {
console.log(`Server running at ${res.url}`);
})
.catch(err => {
console.log(err.message);
}); Этот код поднимает локальный GraphQL-сервер и подключается к MongoDB. Сервер будет слушать порт 5000 после успешного подключения.
Важно: для боевого окружения настраивайте переменные окружения и систему логирования.
Определение модели данных в Mongoose
Создайте папку models и файл models/dataModel.js с содержимым:
const {model, Schema} = require('mongoose');
const employeeSchema = new Schema({
name: String,
department: String,
salary: String,
});
module.exports = model('Employee', employeeSchema);Замечание: типы полей можно расширить (Number, Date, вложенные схемы). Для зарплаты удобно использовать Number и хранить валюту отдельно.
Определение схемы GraphQL
Создайте папку graphql и два файла: graphql/typeDefs.js и graphql/resolvers.js
Содержимое typeDefs.js из примера:
const {gql} = require("apollo-server");
const typeDefs = gql`
type Employee {
id: ID!
name: String
department: String
salary: String
}
input EmployeeInput {
name: String
department: String
salary: String
}
type Query {
getEmployee(id: ID): Employee #return Employee by id
employees: [Employee] #return array of Employees
}
type Mutation {
createEmployee(employeeInput: EmployeeInput): Employee
updateEmployee(id: ID, employeeInput: EmployeeInput): Boolean
deleteEmployee(id: ID): Boolean
}
`;
module.exports = typeDefs;Пояснение: типы и input-типы отделяют схему чтения от схемы записи; для сложных операций добавляйте валидацию и отдельные payload-типы.
Реализация резолверов
Добавьте код из примера в graphql/resolvers.js. Резолверы читают и изменяют данные через модель Mongoose.
Часть файла (Query):
const Employee= require("../models/employeesModel");
// GraphQL Resolvers
const resolvers = {
Query: {
employees: async () => {
try {
const employees = await Employee.find({});
return employees;
} catch (error) {
console.error(error);
throw new Error('Failed to fetch employees');
}
},
getEmployee: async (parent, args) => {
try {
const employee = await Employee.findById(args.id);
return employee;
} catch (error) {
console.error(error);
throw new Error('Failed to fetch employee by ID');
}
},
},
Часть файла (Mutation):
Mutation: {
async createEmployee (_, { employeeInput: { name, department, salary } }) {
const newEmployee = new Employee({
name: name,
department: department,
salary: salary
});
const response = await newEmployee.save();
console.log(newEmployee);
return {
id: response._id,
...response._doc
}
},
async updateEmployee (_, {id, employeeInput: {name, department, salary}}) {
const updatedEmployee = await Employee.updateOne(
{ _id: id },
{ name, department, salary }
);
if (!updatedEmployee) {
throw new Error(`Employee with ID: ${id} not found`);
}
return true; // Return a boolean value indicating update success
},
async deleteEmployee (_, {id}) {
const deletedEmployee = await Employee.deleteOne({ _id: id });
if (!deletedEmployee || deletedEmployee.deletedCount === 0) {
throw new Error(`Employee with ID ${id} not found`);
}
return true; // Return a boolean value indicating deletion success
},
},
};
module.exports = resolvers;После этого можно запустить сервер командой:
node index.jsКогда соединение с БД установлено, сервер будет доступен на http://localhost:5000 и вы сможете открыть GraphQL Playground для тестирования.

Типовые примеры запросов и мутаций
Пример запроса всех сотрудников:
query {
employees {
id
name
department
salary
}
}Создание сотрудника:
mutation {
createEmployee(employeeInput: {name: "Ivan", department: "Dev", salary: "5000"}) {
id
name
}
}Эти примеры можно выполнять в встроенном GraphQL Playground.
Когда GraphQL не подходит (контрпример)
- Простые CRUD-сервисы с предсказуемыми моделями и малой нагрузкой — REST может быть проще.
- API с жёсткими требованиями к кешированию HTTP-уровня (CDN, прокси) иногда проще реализовать через REST/HTTP-кэш.
- Сервисы с бинарными потоками (высокопроизводительная потоковая передача) — лучше gRPC или специализированные решения.
Альтернативные подходы и когда их выбрать
- REST: простота, широкая совместимость, хорош для публичных API с чёткими ресурсами.
- gRPC: низкая задержка, строгая контрактность, идеально для микросервисов внутри дата-центра.
- Falcor/JSON: редкие альтернативы для хранения данных на клиенте.
Модель принятия решения (Flowchart)
flowchart TD
A[Нужно ли клиентам выбирать поля?] -->|Да| B[GraphQL]
A -->|Нет| C[REST]
B --> D{Есть ли heavy-streaming?}
D -->|Да| E[gRPC/Streaming]
D -->|Нет| F[GraphQL подходит]Безопасность и харднинг
- Валидация входных данных на уровне резолверов и модели.
- Ограничение глубины запросов и комплексности (query depth, query complexity) чтобы защититься от DoS.
- Авторизация на уровне полей и резолверов (field-level authorization).
- Лимитирование частоты запросов (rate limiting) и защита по IP/токенам.
- Храните секреты (URI, ключи) в менеджере секретов или переменных окружения.
Конфиденциальность и GDPR
- Если вы храните персональные данные сотрудников, определите правовую основу обработки.
- Обеспечьте удаление и экспорт данных по запросам пользователя (право на удаление/экспорт).
- Логирование: избегайте дампов персональных данных в логах.
Чек-листы по ролям
Разработчик:
- Покрыть резолверы юнит-тестами.
- Добавить валидацию входных данных.
- Обработать ошибки и вернуть человекочитаемые сообщения.
DevOps/Инженер инфраструктуры:
- Настроить резервное копирование MongoDB.
- Обеспечить мониторинг и alerting (CPU, память, количество подключений).
- Конфигурировать секреты и доступы.
Продукт/PO:
- Согласовать схему данных и обязательные поля.
- Определить SLA для API.
Playbook: развертывание и выпуск
- Подготовка: собрать Docker-образ с node и зависимостями.
- Тестирование: прогнать unit и интеграционные тесты, покрыть сценарии создания/обновления/удаления.
- Предрелиз: выкатить на staging, прогнать нагрузочные тесты, проверить политики rate limiting.
- Релиз: перевести трафик на новую версию, наблюдать метрики, откат при критических ошибках.
Критерии приёмки
- Все запросы из спецификации отдают ожидаемые поля.
- Мутации корректно меняют состояние БД и возвращают валидный payload.
- Для несуществующего ID возвращается корректная ошибка.
- Тесты проходят в CI без предупреждений безопасности.
Тестовые сценарии
- Позитив: создать сотрудника, получить его по id, обновить данные, удалить и убедиться, что запрос по id возвращает null/ошибку.
- Негатив: попытаться удалить несуществующий id и получить понятную ошибку.
- Нагрузочный: 1000 параллельных запросов на чтение — следить за временем ответа и ошибками.
Отладка и откат (runbook)
Проблемы при старте сервера:
- Проверьте доступность MongoDB и корректность MONGO_URI.
- Проверяйте логи на ошибки Mongoose.
Если в проде появляются ошибки чтения/записи:
- Переключите трафик на предыдущую стабильную версию.
- Проверьте схемы и миграции модели.
Миграция с REST на GraphQL — советы
- Начните с малого: вынесите наиболее «болезненный» REST-эндпоинт в GraphQL.
- Используйте слой BFF (Backend for Frontend), чтобы постепенно переводить клиентов.
- Поддерживайте версионирование на уровне полей и deprecated-полей в схеме.
Локальные альтернативы и подводные камни
- Если в вашей локальной среде Windows, убедитесь, что MongoDB корректно настроен как служба.
- В облачных окружениях провайдеров IPv6 может поменять поведение сетевых правил.
Социальный превью
OG title: GraphQL с MongoDB и Apollo Server OG description: Быстрый старт: GraphQL API на Node.js, Apollo Server и MongoDB — настройка, резолверы, безопасность и тесты.
Короткое объявление (100–200 слов):
В этом руководстве показано, как за несколько шагов развернуть GraphQL API на Node.js с использованием Apollo Server и MongoDB. Вы создадите модель сотрудника в Mongoose, определите схему GraphQL, реализуете резолверы для запросов и мутаций, а также получите чек-листы по безопасности, тестированию и CI/CD. Подойдёт как для прототипа, так и для постепенного внедрения GraphQL в существующие проекты.
Краткое резюме
- GraphQL даёт гибкость и экономию трафика благодаря выбору полей клиентом.
- Apollo Server упрощает запуск на Node.js, а Mongoose — работу с MongoDB.
- Не забывайте об ограничениях: глубина запросов, авторизация, логирование и GDPR.
Ключевые действия, чтобы начать: 1) поднять MongoDB, 2) установить зависимости, 3) создать схему и резолверы, 4) написать тесты и настроить мониторинг.
Ключевые выводы
- GraphQL подходит, если клиентам нужно выбирать поля и агрегировать данные из разных источников.
- Для простых CRUD-сервисов иногда REST остаётся более явным и простым вариантом.
- Безопасность и контроль сложности запросов — обязательны при публичном доступе.