5. Queries

Query — это операция чтения в GraphQL. Аналог GET-запроса в REST.
Анатомия запроса
Заголовок раздела «Анатомия запроса»query GetUser($id: ID!) { # Операция: query, имя, переменные user(id: $id) { # Поле из Query type, аргументы id # Скалярное поле name email posts { # Вложенный объект id title createdAt } }}query— ключевое слово (можно опустить для простых запросов)GetUser— имя операции (рекомендуется всегда называть)($id: ID!)— переменныеuser(id: $id)— поле + аргумент
Простой запрос (без переменных)
Заголовок раздела «Простой запрос (без переменных)»{ books { title author }}Эта краткая форма без query работает, но не рекомендуется в продакшне.
Именованные запросы
Заголовок раздела «Именованные запросы»query GetAllBooks { books { id title author year }}
query GetBookById($id: ID!) { book(id: $id) { id title author description }}Имена нужны для:
- Дебаггинга (видно в логах)
- Apollo Client кэша
- Документации
- Кодогенерации (GraphQL Codegen)
Переменные
Заголовок раздела «Переменные»Никогда не интерполируй данные напрямую в запрос — используй переменные:
# Правильно ✅query GetUser($id: ID!) { user(id: $id) { name }}
# Неправильно ❌ (SQL injection-like риски)query { user(id: "user-123") { name }}Передача переменных:
{ "id": "user-123"}В Apollo Client:
const { data } = useQuery(GET_USER, { variables: { id: 'user-123' },});Вложенные запросы
Заголовок раздела «Вложенные запросы»GraphQL позволяет получать данные сколь угодно глубоко:
query GetUserWithPosts { user(id: "123") { id name posts { id title comments { id body author { name avatar } } } }}Осторожно: Глубокая вложенность может нагружать сервер. Используй depth limit на сервере.
Aliases — переименование полей
Заголовок раздела «Aliases — переименование полей»Можно запросить одно поле несколько раз с разными аргументами:
query GetTwoUsers { adminUser: user(id: "1") { name email } regularUser: user(id: "2") { name email }}Ответ:
{ "data": { }}Несколько запросов в одной операции
Заголовок раздела «Несколько запросов в одной операции»query Dashboard { me { name unreadNotifications } recentPosts(limit: 5) { title createdAt } popularTags { name count }}Все три запроса выполняются параллельно — один HTTP запрос вместо трёх!
Resolvers для Query
Заголовок раздела «Resolvers для Query»На сервере каждое поле имеет resolver:
const resolvers = { Query: { // (parent, args, context, info) user: async (_, { id }, { db }) => { return db.users.findById(id); },
users: async (_, { limit = 20, offset = 0 }, { db }) => { return db.users.findAll({ limit, offset }); },
me: async (_, __, { db, user }) => { if (!user) throw new Error('Не авторизован'); return db.users.findById(user.id); },
searchPosts: async (_, { query }, { db }) => { return db.posts.search(query); }, },
// Resolver для вложенных полей: User: { posts: async (parent, { limit = 10 }, { db }) => { // parent — это объект User return db.posts.findByAuthor(parent.id, { limit }); }, },};Аргументы resolver
Заголовок раздела «Аргументы resolver»const resolver = ( parent, // Объект родителя (для вложенных полей) args, // Аргументы из запроса context, // Общий контекст (DB, текущий пользователь, etc.) info // Информация о запросе (поля, схема)) => { ... };Пагинация в запросах
Заголовок раздела «Пагинация в запросах»Offset пагинация
Заголовок раздела «Offset пагинация»type Query { posts(limit: Int = 20, offset: Int = 0): PostsConnection!}
type PostsConnection { items: [Post!]! total: Int! hasMore: Boolean!}query GetPosts($limit: Int, $offset: Int) { posts(limit: $limit, offset: $offset) { items { id title } total hasMore }}Cursor пагинация (Relay-style)
Заголовок раздела «Cursor пагинация (Relay-style)»type Query { posts(first: Int, after: String): PostConnection!}
type PostConnection { edges: [PostEdge!]! pageInfo: PageInfo!}
type PostEdge { node: Post! cursor: String!}
type PageInfo { hasNextPage: Boolean! endCursor: String}Фильтрация и сортировка
Заголовок раздела «Фильтрация и сортировка»query FilteredProducts( $minPrice: Float $maxPrice: Float $inStock: Boolean $sortBy: String $order: SortOrder) { products( filters: { minPrice: $minPrice maxPrice: $maxPrice inStock: $inStock } sortBy: $sortBy order: $order ) { id name price inStock }}Практика с useQuery в React
Заголовок раздела «Практика с useQuery в React»import { useQuery, gql } from '@apollo/client';
const GET_POSTS = gql` query GetPosts($limit: Int, $offset: Int) { posts(limit: $limit, offset: $offset) { items { id title author { name } createdAt } total hasMore } }`;
function PostList() { const [offset, setOffset] = useState(0); const limit = 10;
const { loading, error, data, fetchMore } = useQuery(GET_POSTS, { variables: { limit, offset }, });
const loadMore = () => { fetchMore({ variables: { offset: offset + limit }, updateQuery: (prev, { fetchMoreResult }) => { if (!fetchMoreResult) return prev; return { posts: { ...fetchMoreResult.posts, items: [...prev.posts.items, ...fetchMoreResult.posts.items], }, }; }, }); setOffset(offset + limit); };
if (loading) return <Spinner />; if (error) return <Error message={error.message} />;
return ( <div> {data.posts.items.map(post => ( <PostCard key={post.id} post={post} /> ))} {data.posts.hasMore && ( <button onClick={loadMore}>Загрузить ещё</button> )} </div> );}Практика
Заголовок раздела «Практика»- Напиши запрос для получения пользователя по email
- Создай запрос с несколькими alias (запроси двух пользователей сразу)
- Реализуй resolver для
searchPosts(query: String!) - Добавь пагинацию к запросу
posts - В React создай компонент со списком и кнопкой “Загрузить ещё”
query— операция чтения- Переменные (
$name: Type) — безопасный способ передачи данных - Aliases — запрос одного поля несколько раз
- Resolver — функция
(parent, args, context, info)для каждого поля - Несколько полей в одном query выполняются параллельно
Следующий урок → Mutations →