Введение: Почему GraphQL и C++ — это мощная комбинация
GraphQL стал стандартом для современных API, предлагая гибкость и эффективность по сравнению с REST. Но как реализовать его на C++ — языке, известном своей производительностью и строгостью? В этом туториале мы разберем создание полноценного GraphQL API на C++ с использованием библиотеки cppgraphqlgen. Вы узнаете, как определить схему, написать резолверы, обработать запросы и мутации. Примеры кода будут практическими, чтобы вы могли сразу применить их в своих проектах.
К концу статьи вы сможете создать простой, но рабочий GraphQL сервер на C++, который принимает запросы и возвращает данные. Поехали!
Установка и настройка окружения
Для работы нам понадобятся:
- Компилятор с поддержкой C++17 (например, GCC 8+ или Clang 7+)
- CMake (версия 3.14+)
- Библиотека
cppgraphqlgen(генерация кода из схемы GraphQL) - HTTP-сервер (например,
cpp-httplibдля простоты)
Установим зависимости через vcpkg или соберем вручную. Для этого туториала мы используем vcpkg:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg install cppgraphqlgen cpp-httplib
Создадим структуру проекта:
graphql-cpp-tutorial/
├── schema.graphql
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ ├── resolver.cpp
│ └── resolver.h
Шаг 1: Определяем GraphQL-схему
Создадим файл schema.graphql с простой схемой для управления задачами (Todo):
type Query {
todos: [Todo!]!
todo(id: ID!): Todo
}
type Mutation {
addTodo(title: String!): Todo!
completeTodo(id: ID!): Todo
}
type Todo {
id: ID!
title: String!
completed: Boolean!
}
Эта схема описывает:
- Query — получение списка задач или одной задачи по ID
- Mutation — добавление новой задачи и отметка выполненной
- Type Todo — объект с полями id, title, completed
Схема — это контракт нашего API. Далее мы сгенерируем из нее C++ код.
Шаг 2: Генерация C++ кода из схемы
Используем утилиту graphqlgenerategen из пакета cppgraphqlgen:
graphqlgenerategen schema.graphql --output-dir ./generated
После выполнения в папке generated появятся файлы:
TodoObject.h— класс для типа TodoQueryObject.h— базовый класс для резолверов QueryMutationObject.h— базовый класс для резолверов Mutationschema.h— интеграция схемы
Эти файлы содержат сгенерированные интерфейсы, которые мы должны реализовать.
Шаг 3: Реализация резолверов
Создадим файл resolver.h с объявлениями:
#include "generated/QueryObject.h"
#include "generated/MutationObject.h"
#include <vector>
#include <memory>
struct Todo {
std::string id;
std::string title;
bool completed;
};
class QueryResolver : public graphql::todo::QueryObject {
public:
std::vector<std::shared_ptr<graphql::todo::TodoObject>> getTodos() const override;
std::shared_ptr<graphql::todo::TodoObject> getTodo(const std::string& id) const override;
private:
static std::vector<Todo> todos_;
};
class MutationResolver : public graphql::todo::MutationObject {
public:
std::shared_ptr<graphql::todo::TodoObject> applyAddTodo(const std::string& title) override;
std::shared_ptr<graphql::todo::TodoObject> applyCompleteTodo(const std::string& id) override;
};
Теперь реализуем логику в resolver.cpp:
#include "resolver.h"
#include "generated/TodoObject.h"
#include <algorithm>
std::vector<Todo> QueryResolver::todos_ = {
{"1", "Изучить GraphQL", false},
{"2", "Написать туториал", true}
};
// Резолвер для списка задач
std::vector<std::shared_ptr<graphql::todo::TodoObject>>
QueryResolver::getTodos() const {
std::vector<std::shared_ptr<graphql::todo::TodoObject>> result;
for (const auto& todo : todos_) {
auto obj = std::make_shared<graphql::todo::TodoObject>();
obj->setId(todo.id);
obj->setTitle(todo.title);
obj->setCompleted(todo.completed);
result.push_back(obj);
}
return result;
}
// Резолвер для одной задачи
std::shared_ptr<graphql::todo::TodoObject>
QueryResolver::getTodo(const std::string& id) const {
auto it = std::find_if(todos_.begin(), todos_.end(),
[&id](const Todo& t) { return t.id == id; });
if (it == todos_.end()) return nullptr;
auto obj = std::make_shared<graphql::todo::TodoObject>();
obj->setId(it->id);
obj->setTitle(it->title);
obj->setCompleted(it->completed);
return obj;
}
// Мутация: добавление задачи
std::shared_ptr<graphql::todo::TodoObject>
MutationResolver::applyAddTodo(const std::string& title) {
static int nextId = 3;
Todo newTodo{std::to_string(nextId++), title, false};
todos_.push_back(newTodo);
auto obj = std::make_shared<graphql::todo::TodoObject>();
obj->setId(newTodo.id);
obj->setTitle(newTodo.title);
obj->setCompleted(newTodo.completed);
return obj;
}
// Мутация: завершение задачи
std::shared_ptr<graphql::todo::TodoObject>
MutationResolver::applyCompleteTodo(const std::string& id) {
auto it = std::find_if(todos_.begin(), todos_.end(),
[&id](Todo& t) { return t.id == id; });
if (it == todos_.end()) return nullptr;
it->completed = true