Aller au contenu

Subscriptions

type-graphql-query includes out of the box subscription support. When enabling subscriptions, automatic subscriptions are created for all CRUD operations, allowing clients to receive real-time updates when data changes.

This guide will help you understand:

  • ✅ How to enable subscriptions for DTOs
  • ✅ Available subscription events
  • ✅ How to filter subscriptions
  • ✅ How to use custom PubSub providers
  • ✅ How to enable/disable individual subscriptions
  • ✅ Best practices for subscriptions

When enabling subscriptions, the following subscriptions will be automatically created:

  • created{ObjectName} - published when the createOne or createMany mutation is called.
  • updatedOne{ObjectName} - published when the updateOne mutation is called.
  • updatedMany{ObjectName} - published when updateMany mutation is called.
  • deletedOne{ObjectName} - published when the deleteOne mutation is called.
  • deletedMany{ObjectName} - published when deleteMany mutation is called.
  • upserted{ObjectName} - published when the upsertOne or upsertMany mutation is called.

You can enable subscriptions through the auto-generated resolver using buildSchema or buildTypeDefsAndResolvers or by manually defining your resolver.

In both cases you need to set the enableSubscriptions option to true.

In the below example the following subscriptions will be exposed.

  • createdProduct - published when the createOne or createMany mutation is called.
  • updatedOneProduct - published when the updateOne mutation is called.
  • updatedManyProducts - published when updateMany mutation is called.
  • deletedOneProduct - published when the deleteOne mutation is called.
  • deletedManyProducts - published when deleteMany mutation is called.
  • upsertedProduct - published when the upsertOne or upsertMany mutation is called.
import { buildTypeDefsAndResolvers } from '@lib/type-graphql-query';
import { ProductDTO } from './product/product.type';
import { ProductInputDTO } from './product/product-input.type';
import { ProductUpdateDTO } from './product/product-update.type';
const { resolvers, typeDefs } = await buildTypeDefsAndResolvers({
resolvers: [
{
DTOClass: ProductDTO,
CreateDTOClass: ProductInputDTO,
UpdateDTOClass: ProductUpdateDTO,
enableSubscriptions: true,
},
],
// ... other options
});
import { buildSchema } from '@lib/type-graphql-query';
import { ProductDTO } from './product/product.type';
import { ProductInputDTO } from './product/product-input.type';
import { ProductUpdateDTO } from './product/product-update.type';
const schema = await buildSchema({
resolvers: [
{
DTOClass: ProductDTO,
CreateDTOClass: ProductInputDTO,
UpdateDTOClass: ProductUpdateDTO,
enableSubscriptions: true,
},
],
// ... other options
});

When manually defining your resolver you must also provide a readonly pubSub property. In this case the default PubSub implementation should be injected.

import { CRUDResolver } from '@lib/type-graphql-query';
import { Resolver } from '@lib/type-graphql';
import { PubSub } from 'graphql-subscriptions';
import { ProductDTO } from './product/product.type';
import { ProductInputDTO } from './product/product-input.type';
import { ProductUpdateDTO } from './product/product-update.type';
import { ProductService } from './product/product.service';
@Resolver()
export class ProductResolver extends CRUDResolver(ProductDTO, {
CreateDTOClass: ProductInputDTO,
UpdateDTOClass: ProductUpdateDTO,
enableSubscriptions: true,
}) {
constructor(
readonly service: ProductService,
readonly pubSub: PubSub,
) {
super(service);
}
}

You also have the option to selectively enable or disable individual subscriptions.

In this example the updatedMany and deletedMany subscriptions are disabled.

import { buildTypeDefsAndResolvers } from '@lib/type-graphql-query';
import { ProductDTO } from './product/product.type';
import { ProductInputDTO } from './product/product-input.type';
import { ProductUpdateDTO } from './product/product-update.type';
const { resolvers, typeDefs } = await buildTypeDefsAndResolvers({
resolvers: [
{
DTOClass: ProductDTO,
CreateDTOClass: ProductInputDTO,
UpdateDTOClass: ProductUpdateDTO,
enableSubscriptions: true,
update: { many: { enableSubscriptions: false } },
delete: { many: { enableSubscriptions: false } },
},
],
// ... other options
});
import { CRUDResolver } from '@lib/type-graphql-query';
import { Resolver } from '@lib/type-graphql';
import { PubSub } from 'graphql-subscriptions';
import { ProductDTO } from './product/product.type';
import { ProductInputDTO } from './product/product-input.type';
import { ProductUpdateDTO } from './product/product-update.type';
import { ProductService } from './product/product.service';
@Resolver()
export class ProductResolver extends CRUDResolver(ProductDTO, {
CreateDTOClass: ProductInputDTO,
UpdateDTOClass: ProductUpdateDTO,
enableSubscriptions: true,
update: { many: { enableSubscriptions: false } },
delete: { many: { enableSubscriptions: false } },
}) {
constructor(
readonly service: ProductService,
readonly pubSub: PubSub,
) {
super(service);
}
}

The created, updatedOne, deletedOne, and upserted subscriptions all allow a Filter to be passed in to filter for events that match the criteria.

The filter you provide is the same type that is used when querying for records.

For example to filter for all created Products where the name starts with Chicken and can be sold, you could do the following:

subscription {
createdProduct(input: { filter: { name: { like: "Chicken%" }, canBeSold: { is: true } } }) {
id
name
canBeSold
createdAt
updatedAt
}
}

The subscription filter input includes:

  • filter - The filter to apply (same as query filters)
  • search - Search term (if searchable fields are defined)
  • locale - Locale for localized fields
  • withDeleted - Include soft-deleted records
subscription {
updatedOneProduct(
input: { filter: { name: { like: "Chicken%" } }, locale: "fr", withDeleted: false }
) {
id
name
canBeSold
}
}

You can override the default PubSub implementation by creating your own provider and providing it to type-graphql-query.

Below is an example provider using Redis.

import { pubSubToken } from '@lib/type-graphql-query';
import { RedisPubSub } from 'graphql-redis-subscriptions';
import Redis from 'ioredis';
import { Provider } from '@nestjs/common';
export class RedisPubSubProvider {
static provider(): Provider {
return {
provide: pubSubToken(),
useValue: new RedisPubSub({
publisher: new Redis(),
subscriber: new Redis(),
}),
};
}
}

In order to let type-graphql-query know about the provider you can set the pubSub option in your resolver configuration.

import { buildTypeDefsAndResolvers } from '@lib/type-graphql-query';
import { ProductDTO } from './product/product.type';
import { ProductInputDTO } from './product/product-input.type';
import { ProductUpdateDTO } from './product/product-update.type';
import { RedisPubSubProvider } from './redis-pub-sub.provider';
const { resolvers, typeDefs } = await buildTypeDefsAndResolvers({
resolvers: [
{
DTOClass: ProductDTO,
CreateDTOClass: ProductInputDTO,
UpdateDTOClass: ProductUpdateDTO,
enableSubscriptions: true,
pubSub: RedisPubSubProvider.provider(),
},
],
// ... other options
});

The following events are published for each operation:

  • Event Name: created{ObjectName}
  • Published When: createOne or createMany mutations are called
  • Payload: The created DTO(s)
subscription {
createdProduct {
id
name
canBeSold
}
}
  • Event Name: updatedOne{ObjectName}
  • Published When: updateOne mutation is called
  • Payload: The updated DTO
subscription {
updatedOneProduct(input: { filter: { canBeSold: { is: true } } }) {
id
name
canBeSold
}
}
  • Event Name: updatedMany{ObjectName}
  • Published When: updateMany mutation is called
  • Payload: The updated DTOs
subscription {
updatedManyProducts {
id
name
canBeSold
}
}
  • Event Name: deletedOne{ObjectName}
  • Published When: deleteOne mutation is called
  • Payload: The deleted DTO (partial)
subscription {
deletedOneProduct(input: { filter: { canBeSold: { is: true } } }) {
id
name
}
}
  • Event Name: deletedMany{ObjectName}
  • Published When: deleteMany mutation is called
  • Payload: The deleted DTOs (partial)
subscription {
deletedManyProducts {
id
name
}
}
  • Event Name: upserted{ObjectName}
  • Published When: upsertOne or upsertMany mutations are called
  • Payload: The upserted DTO(s)
subscription {
upsertedProduct(input: { filter: { name: { like: "Chicken%" } } }) {
id
name
canBeSold
}
}

Here’s a complete example showing how to set up subscriptions:

import { buildTypeDefsAndResolvers } from '@lib/type-graphql-query';
import { ProductDTO } from './product/product.type';
import { ProductInputDTO } from './product/product-input.type';
import { ProductUpdateDTO } from './product/product-update.type';
const { resolvers, typeDefs } = await buildTypeDefsAndResolvers({
resolvers: [
{
DTOClass: ProductDTO,
CreateDTOClass: ProductInputDTO,
UpdateDTOClass: ProductUpdateDTO,
enableSubscriptions: true,
// Optionally disable specific subscriptions
update: { many: { enableSubscriptions: false } },
delete: { many: { enableSubscriptions: false } },
},
],
// ... other options
});
  1. Use Filtering Wisely

    • Always use filters to reduce the number of events your clients receive
    • Filters help reduce network traffic and improve performance
  2. Choose the Right PubSub Implementation

    • Use in-memory PubSub for development and single-server deployments
    • Use Redis PubSub for production multi-server deployments
    • Consider using other implementations (e.g., RabbitMQ, Kafka) for high-scale scenarios
  3. Disable Unnecessary Subscriptions

    • Only enable subscriptions you actually need
    • Disable many subscriptions if you don’t need bulk operation notifications
  4. Handle Errors Gracefully

    • Implement error handling in your subscription resolvers
    • Use try-catch blocks when processing subscription events
  5. Monitor Subscription Usage

    • Monitor the number of active subscriptions
    • Set limits to prevent resource exhaustion
Subscription Event Published When
created{ObjectName} Creation createOne or createMany
updatedOne{ObjectName} Single update updateOne
updatedMany{ObjectName} Bulk update updateMany
deletedOne{ObjectName} Single deletion deleteOne
deletedMany{ObjectName} Bulk deletion deleteMany
upserted{ObjectName} Upsert upsertOne or upsertMany

Continue learning:

  1. 🔍 Queries - Learn about query endpoints
  2. ✏️ Mutations - Learn about mutation endpoints
  3. 🔎 Filtering - Understand filtering options
  4. 🔧 Resolvers - Learn about resolver implementation