Apollo: passing root to resolver with info.mergeInfo.delegateToSchema - graphql

I have a stitched graphql schema. Some type fields are resolved with info.mergeInfo.delegateToSchema
Here's an example (which is from the apollo docs):
const mergedSchema = mergeSchemas({
schemas: [
transformedChirpSchema,
authorSchema,
linkTypeDefs,
],
resolvers: {
User: {
chirps: {
fragment: `... on User { id }`,
resolve(user, args, context, info) {
return info.mergeInfo.delegateToSchema({
schema: chirpSchema,
operation: 'query',
fieldName: 'chirpsByAuthorId',
args: {
authorId: user.id,
},
context,
info,
});
},
},
},
});
Is it possible to access root in chirps resolver? So that in the root there were all the parent fields? Another way is, of course, to use context for this purpose, but using root, I guess, would be better from a code perspective as I'm already using root value in some cases.

Under the hood info.mergeInfo.delegateToSchema can call remote GraphQL application (more details).
So by design remote resolver don't have access to local root/context/info/arg, you need send all required data in arguments for remote field. For example:
const mergedSchema = mergeSchemas({
schemas: [
transformedChirpSchema,
authorSchema,
linkTypeDefs,
],
resolvers: {
User: {
chirps: {
fragment: `... on User { id }`,
resolve(user, args, context, info) {
return info.mergeInfo.delegateToSchema({
schema: chirpSchema,
operation: 'query',
fieldName: 'chirpsByAuthorId',
args: {
// author is InputType at remove schema with similar user structure
author: user,
},
context,
info,
});
},
},
},
});
I don't know your case, but don't forgot about schema-transforms during working with remove schemas.

Related

joining schemas from remote server by batching same types

I am new to apollo server and I am trying really hard to understand how to make remote schemas one huge scheme, I was able to join the schemas and can now query the data, however, I cannot seem to be able to link/resolve the type, my two micro services uses the same type name for those type that are same everywhere with the pk being common in all of them only that one has only the pk and another one has some extra fields,
my shop schema looks like this
type UserType implements Node {
id: ID!
shops: [ShopType]
pk: Int
}
and what really matters is the pk in this case because it is suppose to join with my auth schema which looks like this
type UserType implements Node {
id: ID!
username: String
email: String
pk: Int
}
with so many other fields, I would like to be able to join the data fields of the two in appolo server since that is where I am merging my two schemas so that when ever I query
{
shops {
shopOwner {
username
email
}
}
}
then even though the username and email are not in the first schema, then it can resolve those fields by pk from the auth schema
I have used something like this
const createNewSchema = async () => {
const schemas = await createRemoteExecutableSchemas();
return mergeSchemas({
schemas,
});
};
to join my schemas so how do I even make the two work together as I desire? thanks so much in advance
I was able to make it work, I used stitchschemas and it looked like this
const createNewSchema = async () => {
const schemas = await createRemoteExecutableSchemas();
return stitchSchemas({
subschemas: [
{
schema: schemas['shop'],
merge: {
UserType: {
entryPoints: [
{
fieldName: 'shopOwnerById',
selectionSet: '{ pk }',
args: originalObject => ({ pk: originalObject.pk }),
},
{
fieldName: 'shopById',
selectionSet: '{ pk }',
args: originalObject => ({ pk: originalObject.pk }),
}
]
},
},
}, {
schema: schemas['auth'],
merge: {
UserType: {
entryPoints: [
{
fieldName: 'userById',
selectionSet: '{ pk }',
args: originalObject => ({ pk: originalObject.pk }),
},
]
}
}
}
],
mergeTypes: true,
});
};
in my case just not to confuse anyone I made the schemas to be dictionary where each was names by the API it was linking to. also hade to make sure that I made queries for all the fieldName's in my respective enpoints. thanks for taking a looks the guid here https://www.graphql-tools.com/docs/stitch-type-merging helped alot

How to define graphql query in schema with exactly one of two parameters required [duplicate]

I'm just getting to grips with GraphQL,
I have set up the following query:
​
type: UserType,
args: {
id: { name: 'id', type: new GraphQLNonNull(GraphQLID) },
email: { name: 'email', type: new GraphQLNonNull(GraphQLString) }
},
resolve: (root, { id, email }, { db: { User } }, fieldASTs) => {
...
}
I would like to be able to pass either an 'id' or 'email' to the query, however, with this setup it requires both an id and email to be passed.
Is there a way to set up the query so only one argument is required, either id or email, but not both?
There's no built-in way to do that in GraphQL. You need to make your arguments nullable (by removing the GraphQLNonNull wrapper type from both of them) and then, inside your resolver, you can just do a check like:
resolve: (root, { id, email }, { db: { User } }, fieldASTs) => {
if (!id && !email) return Promise.reject(new Error('Must pass in either an id or email'))
if (id && email) return Promise.reject(new Error('Must pass in either an id or email, but not both.'))
// the rest of your resolver
}
Define an interface credentials and have that implemented as id or email.

GraphQL nested query returns null

I am trying to use a GraphQL nested query (I am 80% sure this is a nested query?) to get information on the listing and the chef (author) of the listing. I can get the listing info just fine, but I am unable to get the chef info.
I was under the impression that the default resolver (user) would fire when getListing(args) returned without a valid User object for the chef. But the default resolver does not appear to be firing.
How do I properly get the nested information?
For example, my query is:
query getListing($listingID: String!) {
getListing(listingID: $listingID) {
name
chef {
firstName
}
}
}
The query returns:
{
"data": {
"getListing": {
"name": "Test",
"chef": {
"firstName": null
}
}
}
}
The function getListing(args) queries the DB and returns:
{
name: 'Test',
chef: 'testUsername',
listingID: 'testListingID'
}
My Schema is:
type Listing {
uuid: String!
name: String!
chef: User!
}
type User {
username: String
firstName: String
}
type Query {
getUser(jwt: String!): User
getListing(listingID: String): Listing
}
And my resolvers are:
const resolvers = {
Query: {
getListing: async (parent, args, context, info) => {
console.log('GET_LISTING');
return getListing(args);
},
getUser: async (parent, args, context, info) => {
console.log('GET_USER');
return getUser(args);
},
},
User: async (parent, args) => {
console.log('USER RESOLVER');
return getUser(args);
},
};
Other Info:
I am using Apollo Server running on AWS Lambda integrating with DynamoDB on the backend.
Resolvers exist only at the field level. You can't resolve a type (i.e. User). You can only resolve a field that has that type (i.e. chef).
const resolvers = {
// ...
Listing: {
chef: (parent, args) => {
return getUser()
},
},
}
It's unclear what sort of parameters getUser accepts, so you'll need to modify the above example accordingly. You won't use args unless you actually specify arguments for the field being resolved in your schema. It looks like the returning listing has a chef property that's the name of the user, so you can access that value with parent.chef.

How get rid of redundant wrapper object of a mutation result?

When I'm making a request to my backend through a mutation like that:
mutation{
resetPasswordByToken(token:"my-token"){
id
}
}
I'm getting a response in such format:
{
"data": {
"resetPasswordByToken": {
"id": 3
}
}
}
And that wrapper object named the same as the mutation seems somewhat awkward (and at least redundant) to me. Is there a way to get rid of that wrapper to make the returning result a bit cleaner?
This is how I define the mutation now:
export const ResetPasswordByTokenMutation = {
type: UserType,
description: 'Sets a new password and sends an informing email with the password generated',
args: {
token: { type: new GraphQLNonNull(GraphQLString) },
captcha: { type: GraphQLString },
},
resolve: async (root, args, request) => {
const ip = getRequestIp(request);
const user = await Auth.resetPasswordByToken(ip, args);
return user.toJSON();
}
};
In a word: No.
resetPasswordByToken is not a "wrapper object", but simply a field you've defined in your schema that resolves to an object (in this case, a UserType). While it's common to request just one field on your mutation type at a time, it's possible to request any number of fields:
mutation {
resetPasswordByToken(token:"my-token"){
id
}
someOtherMutation {
# some fields here
}
andYetAnotherMutation {
# some other fields here
}
}
If we were to flatten the structure of the response like you suggest, we would not be able to distinguish between the data returned by one mutation from another. We likewise need to nest all of this inside data to keep our actual data separate from any returned errors (which appear in a separate errors entry).

How do I set up GraphQL query so one or another argument is required, but not both

I'm just getting to grips with GraphQL,
I have set up the following query:
​
type: UserType,
args: {
id: { name: 'id', type: new GraphQLNonNull(GraphQLID) },
email: { name: 'email', type: new GraphQLNonNull(GraphQLString) }
},
resolve: (root, { id, email }, { db: { User } }, fieldASTs) => {
...
}
I would like to be able to pass either an 'id' or 'email' to the query, however, with this setup it requires both an id and email to be passed.
Is there a way to set up the query so only one argument is required, either id or email, but not both?
There's no built-in way to do that in GraphQL. You need to make your arguments nullable (by removing the GraphQLNonNull wrapper type from both of them) and then, inside your resolver, you can just do a check like:
resolve: (root, { id, email }, { db: { User } }, fieldASTs) => {
if (!id && !email) return Promise.reject(new Error('Must pass in either an id or email'))
if (id && email) return Promise.reject(new Error('Must pass in either an id or email, but not both.'))
// the rest of your resolver
}
Define an interface credentials and have that implemented as id or email.

Resources