Apollo Graphql Subscriptions: Different subscription resolvers for create, update & delete or a single resolver? - graphql

I am using Typegrpahql for creating my graphql server with express. I want to clarify whether I should create separate subscription resolvers for create, update and delete operations of an entity? Or should I create one resolver since the return type of the data will always be the same?
Suppose I have an entity like this
type Board {
createdAt: DateTime!
id: ID!
members: [User!]!
name: String!
owner: User!
stacks: [Stack!]!
updatedAt: DateTime
}
And I have a mutation resolver class like this
#Resolver(Board)
export class BoardMuations {
#Authorized()
#Mutation(() => Board)
async createBoard(
#PubSub() pubSub: PubSubEngine,
#Arg("name") name: string,
#Ctx() { uid }: MyContext
): Promise<Board> {
// perform create opration
}
#Authorized()
#Mutation(() => Board)
async updateBoard(
#PubSub() pubSub: PubSubEngine,
#Arg("boardId") boardId: string,
#Arg("name") name: string
): Promise<Board | undefined> {
// perform update operation
}
#Authorized()
#Mutation(() => DeleteResponse)
async deleteBoard(#PubSub() pubSub: PubSubEngine, #Arg("boardId") boardId: string): Promise<DeleteResponse> {
//perform delete operation
}
}
So should I create a subscription resolver like this
#Subscription({ topics: "CUD_BOARD" })
newBoard(#Root() data: Board): Board {
return data;
}
Or should I create different resolvers for every operation? what is this topics array?
I could also provide different topic strings as an array as I understand. What difference would it make to create different topics strings and publish the event using them?
I am currently publishing the event like this await pubSub.publish("CUD_BOARD", board);

Related

NestJS GraphQL Apollo Federation many-to-many ResolveField is not triggered

I have 3 services, which are technically completely separate from each other:
User
Channel
UserChannelMembership
Each service defines its own subgraph. User does not refer Channel, Channel does not refer User. The only service which refers both is UserChannelMembership. User <-> Channel is M:M; UserChannelMembership is an actual service, which stores this relations data.
I use Apollo Federation to generate supergraph. Schema generated is good and I am able to obtain all I need except for the User/Channel fields I extend in the UserChannelMembership service [clarification: schema has the fields, the data is null].
Technically #ResolveField() functions are never called.
I tried to put a test #Query inside the same class, and it works. So the resolver file itself is fine. It's either something I don't understand or I miss or a bug (less likely).
Schema files and the resolver code are below.
The question itself is the following: I need UserChannelMembership service to extend the base User and Channel GraphQL types (defined initially in User and Channel services) and provide usersPerChannel and channelsPerUser fields, which will deliver the data provided by corresponding functions in UserChannelMembership service.
user.types.graphql (Defined in User service)
type User #key (fields: "id"){
id: ID!
username: String!
}
channel.types.graphql (Defined in Channel service)
type Channel #key (fields: "id"){
id: ID!
channelname: String!
}
user-channel-members.graphql (Defined in UserChannelMembership service)
extend type User #key(fields: "id") {
id: ID! #external
channelsPerUser: [Channel]
}
extend type Channel #key(fields: "id") {
id: ID! #external
usersPerChannel: [User]
}
user-channel-members.resolver.ts
#Resolver('User')
// also tried:
// #Resolver('UserChannelMembers')
export class UserChannelMembersResolver {
// this query works if I define type Query
// #Query('userChannelMembers')
// findAll() {...removed...}
//
// this never got called
#ResolveField()
// also tried:
// #ResolveField('channelsPerUser')
findChannelsPerUser(#Parent() user: any) {
...never called...
}
}
Final answer is the following.
Doesn't work:
export class UserChannelMembersResolver {
#Resolver('User')
#ResolveField('channelsPerUser')
async channelsPerUser(#Parent() user: any) {...}
#Resolver('Channel')
#ResolveField('usersPerChannel')
async usersPerChannel(#Parent() channel: any) {...}
}
Works:
(first file)
#Resolver('User')
export class UserResolver {
#ResolveField('channelsPerUser')
async channelsPerUser(#Parent() user: any) {...}
}
(second file)
#Resolver('Channel')
export class ChannelResolver {
#ResolveField('usersPerChannel')
async usersPerChannel(#Parent() channel: any) {...}
}
Then import both as a provider in a module and that's it.

What is a correct return type of a GraphQL resolve function?

I faced with an issue that can't resolve on my own. Let's go through it step by step to point out the problem.
I have a mutation bookAppointment which returns an Appointment object
GraphQL schema says that this object should return 4 properties: id, date, specialist, client.
To follow the GraphQL-style the specialist and client properties should be a field level resolvers
To fetch this objects I need pass specialistId to the specialist field level resolver, as well as clientId to the client field level resolver.
At this point a problem arises.
The field level resolvers of client, specialist expects that root mutation returns fields like clientId and specialistId. But GraphQL syntax and types that were generated by that syntax doesn't include this props (make sense).
How to "extend" the return type of the resolver and its interface BookAppointmentPayload to make me and TypeScript happy?
This is my GraphQL schema
type Client {
id: ID!
name: String!
}
type Specialist {
id: ID!
name: String!
}
type Appointment {
id: ID!
date: Date!
client: Client!
specialist: Specialist!
}
input BookAppointmentInput {
date: Date!
userId: ID!
specialistId: ID!
}
type BookAppointmentPayload {
appointment: Appointment!
}
type Mutation {
bookAppointment(input: BookAppointmentInput!): BookAppointmentPayload!
}
This is TypeScript representation of GraphQL schema
interface Client {
id: string
name: string
}
interface Specialist {
id: string
name: string
}
interface Appointment {
id: string
date: Date
client: Client
specialist: Specialist
}
interface BookAppointmentPayload {
appointment: Appointment
}
Here I define my resolvers objects
const resolvers = {
...
Mutation: {
bookAppointment: (parent, args, context, info): BookAppointmentPayload => {
return {
appointment: {
id: '1',
date: new Date(),
clientId: '1', // This prop doesn't exist in the TypeScript interface of Appointment, but is required for the field-level resolver of a `client` prop
specialistId: '1' // This prop doesn't exist int he TypeScript interface of Appointment, but is required for the field-level resolver of a `specialist` prop
}
}
}
},
Appointment: {
client: (parent, args, context, info) => {
// I need a clientId (e.g. args.clientId) to fetch the client object from the database
return {
id: '1',
name: 'Jhon'
}
},
specialist: (parent, args, context, info) => {
// I need a specialistId (e.g. args.specialistId) to fetch the specialist object from the database
return {
id: '1',
name: 'Jane'
}
}
}
}
Solution that come to my mind:
Create an interface which represent "actual" return type of the resolver
...
interface Apppointment {
id: string
date: Date
clientId: string // instead of `client: Client`
specialistId: string // instead of `specialist: Specialist`
}
interface BookAppointmentPayload {
appointment: Appointment
}
...
But this doesn't reflect the GraphQL type. Also tools like graphql-generator generates the type with actual objects that should be included in the response, not the fields that are going to be used by field-level resolvers. (Am I wrong?)
I would like to know how you're solving such issue?
I've been investigating this problem quite a lot and have come to the following conclusion.
Create an interface which represent "actual" return type of the resolver
Most of the time the return type of the resolver function (in JavaScript) doesn't match the type that was declared in the GraphQL SDL
For instance,
# GraphQL SDL
type Appointment {
id: String!
client: User!
specialist: Specialist!
}
type BookAppointmentInput { ... }
type BookAppointmentPayload {
appointment: Appointment!
}
type Mutation {
bookAppointment: (input: BookAppointmentInput!): BookAppointmentPayload!
}
interface AppointmentDatabaseEntity {
id: string
clientId: string // In GraphQL-world this prop is an object, but not in JS. Use this prop in field-level resolver to fetch entire object
specialistId: string // In GraphQL-world this prop is an object, but not in JS. Use this prop in field-level resolver to fetch entire object
}
interface BookAppointmentPayload {
appointment: AppointmentDatabaseEntity // The return type SHOULDN'T be equal to the GraphQL type (Appointment)
}
const resolvers = {
Mutatiuon: {
bookAppointment: (parent, args, context, info) => {
const appointment = { id: '1', specialistId: '1', clientId: '1' }
return {
id: appointment.id,
specialistId: appointment.specialistId, // Pass this prop to the child resolvers to fetch entire object
clientId: appointment.clientId // Pass this prop to the child resolvers to fetch entire object
}
}
},
Appointment: {
client: (parent: AppointmentDatabaseEntity, args, context, info) => {
const client = database.getClient(parent.clientId) // Fetching entire object by the property from the parent object
return {
id: client.id,
name: client.name,
email: client.email
}
},
specialist: (parent: AppointmentDatabaseEntity, args, context, info) => {
const specialist = database.getSpecialist(parent.specialistId) // Fetching entire object by the property from the parent object
return {
id: specialist.id,
name: specialist.name,
email: specialist.email
}
}
}
}
But this doesn't reflect the GraphQL type
As far as I understand it is okay
Also tools like graphql-generator generates the type with actual objects that should be included in the response, not the fields that are going to be used by field-level resolvers. (Am I wrong?)
Yes. I was wrong. The graphql-generator has a configuration file that can be used to replace default generated types with the types that you expect your resolvers to return. This option is called mappers.
plugins
config:
mappers:
User: ./my-models#UserDbObject # User is GraphQL object, which will be replaced with UserDbObject
Book: ./my-modelsBook # Same rule goes here
I don't want to go into details of how to configure it and use, but you can check the links that helped me to understand this
Documentation (check the mappers chapter)
Great explanation by
Jamie Barton (YouTube)
If you disagree with my conclusions or you have a better understanding of how to handle it feel free to leave a comment

how can I fetch data from graphql in my resolver

Within my resolver I seem to be unable to fetch connected data
this works in the graphql playground (prisma) but I am unsure of the syntax about how to form a resolver in apollo server
// my typedef for activity is
type Activity {
id: ID! #id
ActivityType: ActivityType!
title: String!
date: DateTime
user: User!
distance: Float!
distance_unit: Unit!
duration: Float!
elevation: Float
elevation_unit: Unit
createdAt: DateTime! #createdAt
updatedAt: DateTime! #updatedAt
// and my resolver currently looks like this
async activity(parent, args, ctx, info) {
const foundActivity = await ctx.db.query.activity({
where: {
id: args.id
}
});
// todo fetch user data from query
console.log(foundActivity);
}
// where db has been placed onto the ctx (context)
// the CL gives all of the data for Activity apart from the user
// in the playground I would do something like this
query activity {
activity(where: {
id: "cjxxow7c8si4o0b5314f9ibek"
}){
title
user {
id
name
}
}
}
// but I do not know how to specify what is returned in my resolver.
console.log(foundActivity) gives:
{ id: 'cjxxpuh1bsq750b53psd2c77d',
ActivityType: 'CYCLING',
title: 'Test Activity',
date: '2019-07-10T20:21:27.681Z',
distance: 13.4,
distance_unit: 'KM',
duration: 90030,
elevation: 930,
elevation_unit: 'METERS',
createdAt: '2019-07-10T20:48:50.879Z',
updatedAt: '2019-07-10T20:48:50.879Z' }
Prisma is the DB ORM and then I have an Apollo-Server 2 server running on top of that. Unfortunately, stack overflow also thinks that there is too much code on this post so I will have to waffle on about inconsequential gibberish due to the fact that their system can't handle it.
You will have to implement a resolver for Activity.user. Unfortunately your entity does not seem to contain a reference to the user. First, add the user connection to your Prisma data model. Then implement a resolver for Activity.user. I am not very familiar with Prisma 1 but this naive implementation should already do what you want:
let resolvers = {
Query: {
// ...
},
Activity: {
user(parent, args, ctx) {
return ctx.db.query.activity({ id: parent.id }).user();
}
}
}
Find out more about resolving relations in Prisma here
So the answer was incredibly simple:
I just add a second argument to the query (after the "where" with a gql tag of the data shape to be returned so my code now looks like:
const foundActivity = await ctx.db.query.activity(
{
where: {
id: args.id
}
},
`{id title user { id name }}`
);

Create mutation between related types in GraphQL

I'm using GraphQL to try to create a record that has a relation to another type. The types are Task and Day
datamodel.graphql:
type Task {
id: ID! #unique
content: String!
completed: Boolean!
dateToDo: Day!
}
type Day {
id: ID! #unique
content: String!
tasks: [Task]
}
I want to create a task so that it has a reference of the date it should be completed (from the Day type)
schema.graphql
type Mutation {
createTask(content: String!, completed: Boolean!, dateToDo: ID! ): Task!
}
my mutation resolver looks like this:
const Mutations = {
async createTask(parent, args, ctx, info) {
const task = await ctx.db.mutation.createTask(
{
data: {
dateToDo: {
connect: {
id: args.dateToDo
}
},
...args
}
},
info
);
return task;
},
when I run this mutation to create the task:
mutation CREATE_ONE_TASK {
createTask(
content: "a new task",
completed: false,
dateToDo: "cjqycv9dtjklr09179y9zfntq")
{
id
}
}
I get this error:
"message": "Variable \"$_v0_data\" got invalid value
{\"dateToDo\":\"cjqycv9dtjklr09179y9zfntq\",\"content\":\"a new
task\",\"completed\":false}; Expected type
DayCreateOneWithoutTasksInput to be an object at value.dateToDo.",
My questions are: Am I using connect correctly in the mutation resolver? And what the heck is DayCreateOneWithoutTasksInput (I see its been automagically added in prisma.graphql) and how do I use it to create a Task that has a relation to a Day's ID?
The mutation to create the task has the following shape:
mutation b {
createTask(
data: {
content: "Task1"
completed: false
dateToDo: { connect: { id: "cjqzjvk6w000e0999a75mzwpx" } }
}
) {
id
}
}
The type DayCreateOneWithoutTasksInput Prisma is asking for is autogenerated and is the one expected for the field dataToDo. The name means that Prisma would accept a type that creates one Day node but does not have the field tasks or a type that specifies a connection. The WithoutTasksInput part states is there because the type can only be used nested in a mutation where you start from a task, Prisma therefore already has the value to fill in for the tasks field on the nested Day node and you do not need to specify it if you create the day instead of connecting an existing one.
If you use the Playground you can explore the schema that contains all the types on the right side.
schema explorer in the playground
Hope that helps!

Include relationship when querying node using Prisma generated wrapper

I am following the GraphQL Prisma Typescript example provided by Prisma and created a simple data model, generated the code for the Prisma client and resolvers, etc.
My data model includes the following nodes:
type User {
id: ID! #unique
displayName: String!
}
type SystemUserLogin {
id: ID! #unique
username: String! #unique
passwordEnvironmentVariable: String!
user: User!
}
I've seeded with a system user and user.
mutation {
systemUserLogin: createSystemUserLogin({
data: {
username: "SYSTEM",
passwordEnvironmentVariable: "SYSTEM_PASSWORD",
user: {
create: {
displayName: "System User"
}
}
}
})
}
I've created a sample mutation login:
login: async (_parent, { username, password }, ctx) => {
let user
const systemUser = await ctx.db.systemUserLogin({ username })
const valid = systemUser && systemUser.passwordEnvironmentVariable && process.env[systemUser.passwordEnvironmentVariable] &&(process.env[systemUser.passwordEnvironmentVariable] === password)
if (valid) {
user = systemUser.user // this is always undefined!
}
if (!valid || !user) {
throw new Error('Invalid Credentials')
}
const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET)
return {
token,
user: ctx.db.user({ id: user.id }),
}
},
But no matter what I do, systemUser.user is ALWAYS undefined!
This makes sense - how would the client wrapper know how "deep" to recurse into the graph without me telling it?
But how can I tell it that I want to include the User relationship?
Edit: I tried the suggestion below to use prisma-client.
But none of my resolvers ever seem to get called...
export const SystemUserLogin: SystemUserLoginResolvers.Type<TypeMap> = {
id: parent => parent.id,
user: (parent, args, ctx: any) => {
console.log('resolving')
return ctx.db.systemUserLogin({id: parent.id}).user()
},
environmentVariable: parent => parent.environmentVariable,
systemUsername: parent => parent.systemUsername,
createdAt: parent => parent.createdAt,
updatedAt: parent => parent.updatedAt
};
And...
let identity: UserParent;
const systemUserLogins = await context.db.systemUserLogins({
where: {
systemUsername: user,
}
});
const systemUserLogin = (systemUserLogins) ? systemUserLogins[0] : null ;
if (systemUserLogin && systemUserLogin.environmentVariable && process.env[systemUserLogin.environmentVariable] && process.env[systemUserLogin.environmentVariable] === password) {
console.log('should login!')
identity = systemUserLogin.user; // still null
}
Edit 2: Here is the repository
https://github.com/jshin47/annotorious/tree/master/server
There are currently two ways to solve this problem:
Using the Prisma client as OP does at the moment
Using Prisma bindings as was suggested by #User97 in the accepted answer
You can learn more about the difference between Prisma client and Prisma bindings in this forum post.
As OP is currently using Prisma client, I'll use it for this answer as well!
Let's take a look at a statement OP made in the question:
This makes sense - how would the client wrapper know how "deep" to recurse into the graph without me telling it?
OP stated correctly that the Prisma client can't know how to deep to go into the graph and what relationships to fetch. In fact, unless explicitly told otherwise (e.g. using the $fragment API), the client will never fetch any relationships and will always only fetch the scalar values. From the Prisma docs:
Whenever a model is queried using the Prisma client, all scalar fields of that model are fetched. This is true no matter if a single object or a list of objects is queried.
So, how to properly resolve this situation? In fact, the solution is not to make changes to the way how the Prisma client is used, but to implement an additional GraphQL resolver function!
The point about resolvers is that they're fetching the data for specific fields in your schema. In OP's case, there currently is no resolver that would "resolve" the user relation that's defined on the SystemUserLogin type:
type SystemUserLogin {
id: ID! #unique
username: String! #unique
passwordEnvironmentVariable: String!
user: User! # GraphQL doesn't know how to resolve this
}
To resolve this situation, you need to implement a dedicated "type resolver" for it like so:
const resolvers = {
SystemUserLogin: {
user(parent, args, ctx) {
return ctx.db.systemUserLogin({id: parent.id}).user()
}
}
}
Full disclosure: I work at Prisma and we're working on adding better documentation and resources for that use case. Also check out this example where explicit resolvers for the author and posts relation fields are required for the same reason.
Hope that helps!
EDIT: We have also added a slightly more thorough explanation in the Prisma tutorial about Common resolver patterns.
Second parameter of prisma binding functions accept GraphQL query string. Changing following line from
const systemUser = await ctx.db.query.systemUserLogin({ username })
to
const systemUser = await ctx.db.query.systemUserLogin({ username }, `{id username user {id displayName}}`)
will give you the data of user.
Prisma binding will return only direct properties of model in case second parameter is not passed to it.

Resources