Перейти к содержимому

5. Queries

GraphQL 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 на сервере.

Можно запросить одно поле несколько раз с разными аргументами:

query GetTwoUsers {
adminUser: user(id: "1") {
name
email
}
regularUser: user(id: "2") {
name
email
}
}

Ответ:

{
"data": {
"adminUser": { "name": "Алексей", "email": "[email protected]" },
"regularUser": { "name": "Иван", "email": "[email protected]" }
}
}
query Dashboard {
me {
name
unreadNotifications
}
recentPosts(limit: 5) {
title
createdAt
}
popularTags {
name
count
}
}

Все три запроса выполняются параллельно — один HTTP запрос вместо трёх!

На сервере каждое поле имеет 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 });
},
},
};
const resolver = (
parent, // Объект родителя (для вложенных полей)
args, // Аргументы из запроса
context, // Общий контекст (DB, текущий пользователь, etc.)
info // Информация о запросе (поля, схема)
) => { ... };
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
}
}
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
}
}
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>
);
}
  1. Напиши запрос для получения пользователя по email
  2. Создай запрос с несколькими alias (запроси двух пользователей сразу)
  3. Реализуй resolver для searchPosts(query: String!)
  4. Добавь пагинацию к запросу posts
  5. В React создай компонент со списком и кнопкой “Загрузить ещё”
  • query — операция чтения
  • Переменные ($name: Type) — безопасный способ передачи данных
  • Aliases — запрос одного поля несколько раз
  • Resolver — функция (parent, args, context, info) для каждого поля
  • Несколько полей в одном query выполняются параллельно

Следующий урокMutations →