Apollo Graphql Resolver for a Nested Object - graphql

So I have a type like this:
type CardStatus {
status: String
lastUpdated: String
}
type CardCompany {
cardStatus: CardStatus
}
type ExternalAccounting {
cardCompany: CardCompany
}
type User {
balance: String
externalAccounting: ExternalAccounting
}
And my resolver looks something like this
const User = {
balance: (root, args, context) => getBalance().then((res)=>res)
cardStatus: (??)
}
I want to use a resolver to set the nested cardStatus field in the user object.
Balance is the direct field of an object, it's easy- I just run a resolver and return the result to get balance. I want to run a cardStatus api call for the deeply nested cardStatus field, but I have no idea how to do this. I've tried something in my resolver like this:
const User = {
balance: {...}
externalAccounting: {
cardCompany: {
cardStatus: (root) => { (...) },
},
},
}
But it doesn't work in that it does not set the cardStatus nested field of the user object. Seems like it should be relatively easy but I can't find an example online..

You should define the cardStatus resolver under the type CardCompany.
When defining types, they should all be on the same level in the resolver map. Apollo will take care of resolving the query's nested fields according to the type of each nested field.
So your code should look something like this:
const User = {
balance: (root, args, context) => getBalance().then((res)=>res)
}
const CardCompany = {
cardStatus: (root) => { (...) },
}
And I'm not sure how it is connected to your executable schema but it should probably be something similar to:
const schema = makeExecutableSchema({
typeDefs,
resolvers: merge(User, CardCompany, /* ... more resolvers */ )
})

Related

Access context from Apollo GraphQL mutation field directive

I have an input type like this:
input PetGiraffe {
name: String #addUserLastName
}
Inside the directive, I need access to the request's context, so that I can add the user's last name to the giraffe's name. Here's the relevant part of what I've got so far:
const addUserLastNameDirective = {
typeDefs: gql`directive #addUserLastName on INPUT_FIELD_DEFINITION`,
transformer: (schema: GraphQLSchema, directiveName = 'addUserLastName') => {
return mapSchema(schema, {
[MapperKind.INPUT_OBJECT_FIELD]: (fieldConfig, fieldName, typeName, schema) => {
const directive = getDirective(schema, fieldConfig, directiveName)?.[0];
if (directive) {
// Need context in here because the user is in the context.
}
},
});
},
};
For queries, I understand you can override the fieldConfig.resolve method and get access to the context that way. But if I try that with this mutation, it throws: field has a resolve property, but Input Types cannot define resolvers.
The closest I could find was this from the graphql-tools docs, but that doesn't solve my problem of accessing the context.

Change backend service based on GraphQL Param

I have a GraphQL Schema as such:
BigParent (parentParam: Boolean) {
id
name
Parent {
id
name
Child (childParam: Boolean) {
id
name
}
}
}
How can I write resolvers such that I call different backend APIs based on whether the parentParam is true or the childParam is true? The first option is straight-forward. The second one needs to kind of reconstruct the Graph based on the values returned by the service data returned at the level of Child.
I'm not considering both the options as true, as I'll assign some priority so that the param at child level is not considered while the param at the parent level is passed.
You can get the arguments for any field selection in the query by traversing the GraphQL resolver info parameter.
Assuming your query looks like this:
query {
BigParent(parentParam: Boolean) {
id
name
Parent {
id
name
Child(childParam: Boolean) {
id
name
}
}
}
}
You should be able to do something like this:
function getChildParam(info) {
// TODO: Return 'childParam'
}
const resolvers = {
Query: {
async BigParent(parent, args, context, info) {
// 'args' contains the 'parentParam' argument
const parentParam = args.parentParam;
// Extract the 'childParam' argument from the info object
const childParam = getChildParam(info);
if (parentParam || childParam) {
return context.datasource1.getData();
}
return context.datasource2.getData();
},
},
};

GraphQL resolvers only resolving the first type

so im working on a project of mine and i seem to hit a dead end with my abilities.
I am working on a GraphQL backend that is supposed to fetch some data from a MySQL database. I already got the resolvers working so i can fetch all users etc. but i am not able to fetch nested types. For example:
query {
ways {
id
distance
duration
purpose
User {
id
dob
}
}
}
This only returns all the ways from my database but the User returns null
schema.ts
export const typeDefs= gql`
schema {
query: Query
}
type Query {
ways: [Way]
users: [User]
getUser(id: String!): User
getWay(id: String!):Way
}
type Way{
id:String
distance:Float
duration:Float
stages:[Stage]
purpose:String
User:User
}
type User{
id:String
firstname:String
lastname:String
sex:String
dob:String
annualTicket:Boolean
Ways:[Way]
}
resolver.ts
export const resolvers = {
Query: {
ways: async(parent, args, context, info) => {
console.log("ways")
const answer=await getAllWays();
return answer;
},
users: async(parent, args, context, info) => {
console.log("users")
const answer=await getAllUser();
return answer;
},
getUser: async(parent, args, context, info) =>{
console.log("getUser")
const answer=await getUserPerID(args.id);
return answer;
},
getWay:async(parent, args, context, info) =>{
console.log("getWay")
const answer=await getWayPerID(args.id);
return answer;
}
},
User:async(parent, args, context, info) =>{
console.log("User: id="+args.id)
ways: User => getWaysPerUserID(args.id)
},
Way:async(parent, args, context, info) => {
console.log("Way: id="+parent.userID)
user: Ways => getUserPerID(parent.userID)
}
I would expect the outcome to include the user and its data too, using the above mentioned query.
Any advice is much appreciated.
Only fields, not types, can have resolvers. Each key in your resolvers object should map to another object, not a function. So instead of
Way:async(parent, args, context, info) => {
console.log("Way: id="+parent.userID)
user: Ways => getUserPerID(parent.userID)
}
You need to write:
Way: {
// We capitalize the field name User because that's what it is in your schema
User: (parent) => getUserPerID(parent.userID)
}

Grouping graphql mutations

I'm trying to group my mutations into second level types. The schema is parsed correctly, but resolvers aren't firing in Apollo. Is this even possible? Here's the query I want:
mutation {
pets: {
echo (txt:"test")
}
}
Here's how I'm trying to do it
Schema:
type PetsMutations {
echo(txt: String): String
}
type Mutation {
"Mutations related to pets"
pets: PetsMutations
}
schema {
mutation: Mutation
}
Resolvers:
...
return {
Mutation: {
pets : {
echo(root, args, context) {
return args.txt;
}
},
}
Assuming you're using apollo-server or graphql-tools, you cannot nest resolvers in your resolver map like that. Each property in the resolver map should correspond to a type in your schema, and itself be a map of field names to resolver functions. Try something like this:
{
Mutation: {
// must return an object, if you return null the other resolvers won't fire
pets: () => ({}),
},
PetsMutations: {
echo: (obj, args, ctx) => args.txt,
},
}
Side note, your query isn't valid. Since the echo field is a scalar, you can't have a subselection of fields for it. You need to remove the empty brackets.

How to create generics with the schema language?

Using facebook's reference library, I found a way to hack generic types like this:
type PagedResource<Query, Item> = (pagedQuery: PagedQuery<Query>) => PagedResponse<Item>
​
interface PagedQuery<Query> {
query: Query;
take: number;
skip: number;
}
​
interface PagedResponse<Item> {
items: Array<Item>;
total: number;
}
function pagedResource({type, resolve, args}) {
return {
type: pagedType(type),
args: Object.assign(args, {
page: { type: new GraphQLNonNull(pageQueryType()) }
}),
resolve
};
function pageQueryType() {
return new GraphQLInputObjectType({
name: 'PageQuery',
fields: {
skip: { type: new GraphQLNonNull(GraphQLInt) },
take: { type: new GraphQLNonNull(GraphQLInt) }
}
});
}
function pagedType(type) {
return new GraphQLObjectType({
name: 'Paged' + type.toString(),
fields: {
items: { type: new GraphQLNonNull(new GraphQLList(type)) },
total: { type: new GraphQLNonNull(GraphQLInt) }
}
});
}
}
But I like how with Apollo Server I can declaratively create the schema. So question is, how do you guys go about creating generic-like types with the schema language?
You can create an interface or union to achieve a similar result. I think this article does a good job explaining how to implement interfaces and unions correctly. Your schema would look something like this:
type Query {
pagedQuery(page: PageInput!): PagedResult
}
input PageInput {
skip: Int!
take: Int!
}
type PagedResult {
items: [Pageable!]!
total: Int
}
# Regular type definitions for Bar, Foo, Baz types...
union Pageable = Bar | Foo | Baz
You also need to define a resolveType method for the union. With graphql-tools, this is done through the resolvers:
const resolvers = {
Query: { ... },
Pageable {
__resolveType: (obj) => {
// resolve logic here, needs to return a string specifying type
// i.e. if (obj.__typename == 'Foo') return 'Foo'
}
}
}
__resolveType takes the business object being resolved as its first argument (typically your raw DB result that you give GraphQL to resolve). You need to apply some logic here to figure out of all the different Pageable types, which one we're handling. With most ORMs, you can just add some kind of typename field to the model instance you're working with and just have resolveType return that.
Edit: As you pointed out, the downside to this approach is that the returned type in items is no longer transparent to the client -- the client would have to know what type is being returned and specify the fields for items within an inline fragment like ... on Foo. Of course, your clients will still have to have some idea about what type is being returned, otherwise they won't know what fields to request.
I imagine creating generics the way you want is impossible when generating a schema declaratively. To get your schema to work the same way it currently does, you would have to bite the bullet and define PagedFoo when you define Foo, define PagedBar when you define Bar and so on.
The only other alternative I can think of is to combine the two approaches. Create your "base" schema programatically. You would only need to define the paginated queries under the Root Query using your pagedResource function. You can then use printSchema from graphql/utilities to convert it to a String that can be concatenated with the rest of your type definitions. Within your type definitions, you can use the extend keyword to build on any of the types already declared in the base schema, like this:
extend Query {
nonPaginatedQuery: Result
}
If you go this route, you can skip passing a resolve function to pagedResource, or defining any resolvers on your programatically-defined types, and just utilize the resolvers object you normally pass to buildExecutableSchema.

Resources