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

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.

Related

Restrict lambda resolver to owner for GraphQL API using Amplify

In my schema.graphql file I have the following:
type Floorplan #model #auth(rules: [
{ allow: private, operations: [read], provider: userPools },
{ allow: owner, provider: userPools }
]) #aws_cognito_user_pools {
id: ID! #primaryKey
name: String!
bedrooms: Int!
}
input FloorplanInput {
id: ID!
name: String!
bedrooms: Int!
}
type Mutation {
floorplanLambda(input: FloorplanInput): Floorplan
#function(name: "floorplanLambda-${env}")
#aws_cognito_user_pools
}
I created the lambda function to perform custom validation before updating.
The problem is that any authenticated user can update other users' floor plans. I thought adding #aws_cognito_user_pools would resolve this, but it doesn't.
Question: What do I need to add to lock down the floorplanLambda function so that it can only be successfully called by the owner of the Floorplan model?
When calling floorplanLambda I receive the error: "Not Authorized to access floorplanLambda on type Floorplan". I'm making the call with authMode AMAZON_COGNITO_USER_POOLS.
For some more context, I followed this tutorial to create the custom mutation lambda function: https://www.theclouddeveloper.io/use-lambda-resolvers-in-your-graph-ql-api-with-aws-amplify
So according to the response to my GitHub issue, this workflow is not currently supported. You can follow it here: https://github.com/aws-amplify/amplify-category-api/issues/528#issuecomment-1157894170
A workaround was provided by:
...setting the auth rule to private and then perform
validation in the lambda function...
I was thinking to create a custom auth function and then chain it in front of my custom mutation. Not sure if that will work but I'll report back once I've made some progress.

Nested field resolvers in GraphQL

The goal is to use NestJS to implement a GraphQL schema using the code-first approach.
Let's say I have a Pet type in my GraphQL schema with two fields, name and age. If those two pieces of information come from different sources of truth (and I don't always want to fetch both), I could implement a PetResolver class with resolvers for each field:
#Resolver(() => Pet)
export class PetResolver {
#Query(() => Pet)
pet(): Pet {
return {};
}
#ResolveField()
name(): Promise<string> {
return Promise.resolve('Spot');
}
#ResolveField(() => Int)
age(): Promise<number> {
return Promise.resolve(2);
}
}
which could be used like this:
query GetPet {
pet {
name
}
}
This works and would ensure that the value of each field is only fetched when requested, but what if I wanted to have a pet field on my User type that I could query like this:
query GetUserWithPet {
currentUser {
email
pet {
name
}
}
}
Applying the same principle, I could create a UserResolver class like this:
#Resolver(() => User)
export class UserResolver {
#Query(() => User)
#UseGuards(AuthCognitoGuard)
currentUser(#CurrentUser() user: IdTokenPayload): User {
return {
id: user.sub,
email: user.email,
};
}
#ResolveField()
pet(#Parent() user: User): Promise<Pet> {
return petService.getPetForUserId(user.id);
}
}
but then the PetService implementation would have to be aware of which fields were requested if it only wanted to fetch relevant data.
A) Is there a way to use PetResolver within UserResolver to make use of the individual field resolution logic?
B) If not, what is the best way to determine which fields were requested in the query using NestJS code-first conventions?
C) Is this the "wrong" way to think about GraphQL queries? Do best practices dictate that I keep the separate resolver and use a query like this:
query GetUserWithPet {
currentUser {
email
}
pet {
name
}
}
User should contain some petIds [array] value (internal, DB stored field/column) ...
... making possible to resolve pets: [Pet] prop/relation - list of Pet ...
... like starshipIDs explained in https://graphql.org/learn/execution/
Notice: pets service is asked about records using pet ids.
... but of course pet can contain some ownerId (only or explicitely visible, DB stored field/column) making possible to resolve owner: User prop [reverse] relation - this way you can:
query PetWithOwner {
pet (id: "someID") {
id
name
owner {
id
email
# more pets?
pets {
id
name
# you can loop it ;)
owner {
id
email
pet.owner field resolver can return only { id: ownerId } object (partial response) ... server will try to resolve 'missing' (required by query) email prop using User (owner is User type) type resolver, passing id as an arg (check/console.log parent and args resolver args). You don't have to do it [the same] 'manually' inside pet.owner field resolver.
Query required fields ...
... [selection set] can be read from info object - 4th resolver arg - read docs/tutorial for details

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

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);

Using nested arguments in GraphQL operations

I have a schema like so:
scalar Date
schema {
query: Query
}
type Query {
user(id: ID!): User
messages(userId: ID!): [ChatMessage!]!
}
type User {
id: ID!
username: String!
email: String!
}
type ChatMessage {
id: ID!
content: String!
time: Date!
user: User!
}
And I want to make an operation where you can get all messages for a user, but since User and ChatMessage are in separate database tables I would need to perform two queries (one to get the ChatMessages and one to get the User), so I thought I should model it like so:
query findMessagesForUser($userId: ID!) {
messages(userId: $userId) {
id
content
user(id: $userId) {
username
email
}
}
}
This returns a parse error on the schema:
GraphQLDocumentError: Unknown argument "id" on field "ChatMessage.user".
So, how do I get the $userId argument passed to the resolver for ChatMessage.user?
In your schema, you’ve defined an id input on your Query.user method. In your query, you are trying to supply an id to the Message.user property, however you haven't defined this input in your schema.
If you wanted to accept an id on ChatMessage.user, you'd need to define it as:
type ChatMessage {
id: ID!
content: String!
time: Date!
user(id: ID!): User
}
However, it wouldn't really make sense (at least to me) to construct a schema this way, I assume there's only one user (author) per message.
As indicated by #xadm, the object you resolved at the ChatMessage level will be passed into the user resolver as the first argument.
Even if you're not exposing ChatMessage.userId in the schema (that's fine), you'd still probably load this up in your back- end (the foreign key value in the ChatMessage table) and set this on the object used to resolve ChatMessage.
This way, you'll (lazy) load user IF that's included in the query, using the userId property of the parent ChatMessage object argument (remember you don't need to expose ChatMessage.userId via the schema, it's just on the object you use to resolve ChatMessage).
I'd consider modelling more like this (filter input used as an additional contrived example):
type Query {
user(id: ID!): User
messages(filter: MessageFilter): [ChatMessage!]!
}
type MessageFilter {
search: String
paging: PagingFilter
}
type PagingFilter {
after: ID!
pageSize: Int!
}
type User {
id: ID!
username: String!
email: String!
messages(filter: MessageFilter): [ChatMessage!]!
}
In your resolver map, you can wire up the same function to resolve messages at the User level and at the Query level. The only difference is you wouldn't have a userId at the Query level.
If consumers want to view/search messages from all users, they use the top level Query messages method.
{
messages({search: 'graphql'}) {
id,
content,
time
}
}
If the consumer wants to view/search one user's messages, go through the top level Query users method into messages.
{
user(id: 3) {
messages({search: 'graphql'}) {
id,
content,
time
}
}
}
The filter example is contrived, but could provide basic paging for loading of messages.
apollographql.com/docs/graphql-tools/resolvers

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