apollographql / apollographql/graphql-subscriptions
Subscription: make `Subscription` resolver independent of resolvers for `Create` and `Update` records
- Dominant language
- TypeScript
- Stars
- 1.6k
- Forks
- 129
- PR merge metrics
- No merged PRs in 30d
Description
# How do you make a `Subscription` resolver independent of resolvers for `Creating` and `Updating` records
### ❌ Current behaviour: `subscribeTasks` resolver doesn't have proper filters, nor does it return me the latest `Task` items
### ✔️ Expected behaviour: `subscribeTasks` resolver returns me all `Task` items given a filter parameter (based on `TaskWhereInput`) in the query
I'm building a GraphQL API using NestJS.
The API is simple, it has to do CRUD operations on `Task` entity - I'm using PostgreSQL
### Data model:
```typescript
import { Field } from '@nestjs/graphql';
import { ObjectType } from '@nestjs/graphql';
import { ID } from '@nestjs/graphql';
@ObjectType()
export class Task {
@Field(() => ID, { nullable: false })
id!: string;
@Field(() => String, { nullable: true })
title!: string | null;
@Field(() => Date, { nullable: true })
createdAt!: Date | null;
@Field(() => Boolean, { nullable: true, defaultValue: false })
completed!: boolean | null;
@Field(() => Date, { nullable: true })
completedAt!: Date | null;
}
```
The API needs to have the following 3 core resolvers:
- `createTask` => creates a task in db
- `updateTask` => update a task in db
- `subscribeTasks` => subscribe to tasks given a filter parameter, and the tasks subscribed need to update whenever a `Task` is created or updated
## ⚠️My biggest question:
### How do I make sure `subscribeTasks` resolver only updates the tasks subscribed BASED ON CHANGES IN THE DB? (rather than triggering updates all the tasks subscribed using `PubSub` by publishing `pubSubEvents` in `createTask` and `updateTask` resolvers?)
### I also need a proper filter to be applied on `subscribeTasks` resolver. So that I can write subscription queries like how I'd do with a normal gql query like this:
```graphql
subscription {
subscribeTasks (
where: {
completed: {
equals: false
},
createdAt: {
equals: "2022-06-25"
}
}
) {
id
title
completed
completedAt
createdAt
}
}
```
TaskWhereInput:
```typescript
@InputType()
export class TaskWhereInput {
@Field(() => [TaskWhereInput], { nullable: true })
AND?: Array;
@Field(() => [TaskWhereInput], { nullable: true })
OR?: Array;
@Field(() => [TaskWhereInput], { nullable: true })
NOT?: Array;
@Field(() => StringFilter, { nullable: true })
id?: StringFilter;
@Field(() => StringNullableFilter, { nullable: true })
title?: StringNullableFilter;
@Field(() => DateTimeNullableFilter, { nullable: true })
createdAt?: DateTimeNullableFilter;
@Field(() => BoolNullableFilter, { nullable: true })
completed?: BoolNullableFilter;
@Field(() => DateTimeNullableFilter, { nullable: true })
completedAt?: DateTimeNullableFilter;
}
```
## This is how my resolvers look like:
```typescript
import { Resolver, Mutation, Subscription, Args, Query } from '@nestjs/graphql';
import { PubSub } from 'graphql-subscriptions';
import { TaskCreateInput } from 'src/generated/task/task-create.input';
import { TaskUpdateInput } from 'src/generated/task/task-update.input';
import { TaskWhereUniqueInput } from 'src/generated/task/task-where-unique.input';
import { TaskWhereInput } from 'src/generated/task/task-where.input';
import { Task } from 'src/generated/task/task.model';
import { TaskService } from './Task.service';
@Resolver(() => Task)
export class TaskResolver {
constructor(
private readonly taskService: TaskService,
private pubsub: PubSub,
) {}
@Mutation(() => [Task])
public async createTask(@Args('input') input: TaskCreateInput) {
try {
const res = await this.taskService.createTask(input);
this.pubsub.publish('taskUpdatedEvent', { taskUpdatedEvent: res });
return res;
} catch (error) {
throw error;
}
}
@Mutation(() => [Task])
public async updateTask(
@Args('where') where: TaskWhereUniqueInput,
@Args('data') data: TaskUpdateInput,
) {
try {
const res = await this.taskService.updateTask(where, data);
this.pubsub.publish('taskAddedEvent', { taskAddedEvent: res });
return res;
} catch (error) {
throw error;
}
}
@Subscription(() => [Task])
public async subscribeTasks(
@Args('where', { nullable: true }) where?: TaskWhereInput,
) {
this.pubsub.asyncIterator('taskAddedEvent');
this.pubsub.asyncIterator('taskUpdatedEvent');
return await this.taskService.tasks(where);
}
@Query(() => [Task])
async tasks(@Args('where', { nullable: true }) where: TaskWhereInput) {
return await this.taskService.tasks(where);
}
}
```
## Speaking of subscription filters, I also have no idea how to apply what's on NestJS doc in my use case (MANY filters, not just one)
NestJS doc filter demo:
```typescript
@Subscription(returns => Comment, {
filter: (payload, variables) =>
payload.commentAdded.title === variables.title,
})
commentAdded(@Args('title') title: string) {
return pubSub.asyncIterator('commentAdded');
}
```
- [ ] has-reproduction
- [ ] feature
- [x] blocking
- [ ] good first issue
Contributor guide
Research direction
Start by reading the TaskResolver subscription, createTask and updateTask methods, then trace TaskService.tasks and the graphql-subscriptions PubSub usage. Compare the requested filtering and database-change behavior with the NestJS subscription filter example; done means the subscription applies the requested TaskWhereInput filters and updates appropriately for created or updated tasks.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- graphql, postgresql, typescript
- Domain
- api, backend-api-design
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 15/100