GraphQL Subscriptions (Подписки)
Что такое Subscription?
Subscription — это операция для получения обновлений в реальном времени. Использует WebSocket вместо HTTP.
HTTP: Клиент → (запрос) → Сервер
WebSocket: Клиент ↔ (bidirectional) ↔ СерверСинтаксис базовой подписки
graphql
subscription {
messageAdded {
id
text
user {
name
}
}
}Именованная подписка
graphql
subscription OnMessageAdded {
messageAdded {
id
text
user {
name
}
}
}Подписка с аргументами
graphql
subscription OnPostCommented($postId: ID!) {
postCommented(postId: $postId) {
id
text
author {
name
}
createdAt
}
}Определение Subscription типа
typescript
const typeDefs = gql`
type Subscription {
messageAdded: Message!
userOnline(userId: ID!): User!
postUpdated(postId: ID!): Post!
}
type Message {
id: ID!
text: String!
user: User!
createdAt: String!
}
`;Реализация Subscription на сервере
Apollo Server с PubSub
typescript
import { ApolloServer, gql, PubSub } from 'apollo-server';
const pubSub = new PubSub();
const resolvers = {
Query: {
messages: () => messages
},
Mutation: {
addMessage: (_, { text, userId }) => {
const message = {
id: String(messages.length + 1),
text,
userId,
createdAt: new Date().toISOString()
};
messages.push(message);
// Отправляем подписчикам
pubSub.publish('MESSAGE_ADDED', { messageAdded: message });
return message;
}
},
Subscription: {
messageAdded: {
subscribe: () => pubSub.asyncIterator(['MESSAGE_ADDED'])
}
}
};
const server = new ApolloServer({
typeDefs,
resolvers
});Использование в Apollo Client
typescript
import { gql, useSubscription } from '@apollo/client';
const SUBSCRIPTION = gql`
subscription OnMessageAdded {
messageAdded {
id
text
user {
name
}
}
}
`;
function ChatComponent() {
const { data, loading, error } = useSubscription(SUBSCRIPTION);
if (loading) return <p>Ожидание сообщений...</p>;
if (error) return <p>Ошибка: {error.message}</p>;
return (
<div>
<p>Новое сообщение: {data?.messageAdded.text}</p>
</div>
);
}Практические примеры
Уведомления в реальном времени
graphql
subscription OnNotification($userId: ID!) {
notificationReceived(userId: $userId) {
id
title
message
type
createdAt
}
}Обновления статуса
graphql
subscription OnUserStatusChanged($userId: ID!) {
userStatusChanged(userId: $userId) {
userId
status
lastSeen
}
}Обновления данных в реальном времени
graphql
subscription OnPostUpdated($postId: ID!) {
postUpdated(postId: $postId) {
id
title
likes
comments {
count
}
}
}Multi-user подписки (Chat)
typescript
const resolvers = {
Mutation: {
sendMessage: (_, { channelId, text, userId }) => {
const message = {
id: `msg-${Date.now()}`,
channelId,
text,
userId,
createdAt: new Date()
};
pubSub.publish(`CHANNEL_${channelId}_MESSAGE`, {
messageSent: message
});
return message;
}
},
Subscription: {
messageSent: {
subscribe: (_, { channelId }) => {
return pubSub.asyncIterator([`CHANNEL_${channelId}_MESSAGE`]);
}
}
}
};Фильтрация подписок
typescript
const resolvers = {
Subscription: {
userCreated: {
subscribe: (_, { roleFilter }) => {
return pubSub.asyncIterator(['USER_CREATED']);
},
resolve: (payload, _, __, info) => {
// Фильтруем результаты
if (info.variableValues.roleFilter) {
if (payload.userCreated.role !== info.variableValues.roleFilter) {
return null;
}
}
return payload.userCreated;
}
}
}
};Жизненный цикл Subscription
1. Клиент устанавливает WebSocket соединение
2. Клиент отправляет GQL_CONNECTION_INIT
3. Сервер отвечает GQL_CONNECTION_ACK
4. Клиент отправляет subscription
5. Сервер отправляет обновления
6. Клиент может отписаться (unsubscribe)
7. WebSocket соединение закрываетсяОбработка ошибок в Subscription
typescript
const resolvers = {
Subscription: {
messageAdded: {
subscribe: async (_, __, context) => {
if (!context.userId) {
throw new Error('Not authenticated');
}
return pubSub.asyncIterator(['MESSAGE_ADDED']);
}
}
}
};Отмена подписки (Unsubscribe)
typescript
// Apollo Client
const subscription = useSubscription(SUBSCRIPTION);
// Отписаться
subscription.unsubscribe?.();Лучшие практики
✅ Делайте:
- Проверяйте аутентификацию для чувствительных подписок
- Используйте фильтрацию для ограничения данных
- Закрывайте соединения после отписки
- Мониторьте количество активных соединений
❌ Не делайте:
- Не отправляйте большие объемы данных часто
- Не используйте для polling (используйте Query с интервалами)
- Не забывайте закрывать соединения
- Не доверяйте клиентским параметрам без валидации
Дальше
Изучите Schemas для определения типов данных.