How to react on GraphQL schema directives using graphql-tools - graphql

I have the following directive in y GraphQL schema to check if arguments are real slugs
directive #slug on ARGUMENT_DEFINITION
Query {
subject(id: ID! #slug): Subject
}
We had a working solution before using SchemaDirectiveVisitor from apollo-server-express, which to my understanding was just an re export from graphql-tools. As this was removed with the latest major release we have to refactor it. As far as I understand this, also the graphql-tools API has changed, so to react on directives we have to follow the examples here.
So this is what I have so far but it doesn't go into the fieldConfig.resolve function at all:
export default function (schema) {
return mapSchema(schema, {
[MapperKind.ARGUMENT]: fieldConfig => {
const slugDirective = getDirective(schema, fieldConfig, 'slug')?.[0]
if (slugDirective) {
const { resolve = defaultFieldResolver } = fieldConfig
fieldConfig.resolve = async function (
source,
args,
context,
info
) {
const result = await resolve(source, args, context, info)
console.log('result', result)
}
return fieldConfig
}
},
})
}

Related

Check queried subfields in runtime graphql Nestjs?

I try to optimize one of my queries which returns #ObjectType() like this:
isFeatureEnabled() {
.
.
.
return {
isDistributedInboundEnabled: await getIsDistributedInboundEnabled(),
isBusinessHoursEnabled: await getIsBusinessHoursEnabled(),
};
}
However, when executing this query with :
query GetCompany {
getCompany{
isFeatureEnabled{
isBusinessHoursEnabled
# isDistributedInboundEnabled <--- Do not query this !
}
}
}
both of the functions are executed (await getIsDistributedInboundEnabled(), await getIsBusinessHoursEnabled()).
Can I check which subfields are queried in runtime? That way only required functions will be executed on BE.
Ok, personally used approach described in this link and found it working with nest 8.x.x:
Fortunately, we can access the details about the GraphQL query with the #Info() decorator. The most straightforward way to use it is with the graphql-parse-resolve-info library.
import { Info, Query, Resolver } from '#nestjs/graphql';
import { Post } from './models/post.model';
import PostsService from './posts.service';
import { parseResolveInfo, ResolveTree, simplifyParsedResolveInfoFragmentWithType } from 'graphql-parse-resolve-info';
import { GraphQLResolveInfo } from 'graphql';
#Resolver(() => Post)
export class PostsResolver {
constructor(
private postsService: PostsService
) {}
#Query(() => [Post])
async posts(
#Info() info: GraphQLResolveInfo
) {
const parsedInfo = parseResolveInfo(info) as ResolveTree;
const simplifiedInfo = simplifyParsedResolveInfoFragmentWithType(
parsedInfo,
info.returnType
);
const posts = 'author' in simplifiedInfo.fields
? await this.postsService.getPostsWithAuthors()
: await this.postsService.getPosts();
return posts.items;
}
}

Apollo Client: How to query rest endpoint with query string?

I'm using Apollo to call a rest endpoint that takes variables from query string:
/api/GetUserContainers?showActive=true&showSold=true
I'm having trouble figuring out how to pass variables to the query, so it can then call the correct url. From looking at apollo-link-rest docs and a few issues I think I'm supposed to use pathBuilder but this is not documented and I haven't been able to get it working.
So far I've defined my query like this:
getUserContainersQuery: gql`
query RESTgetUserContainers($showActive: Boolean, $showSold: Boolean, $pathBuilder: any) {
containerHistory #rest(type: "ContainerHistoryResponse", pathBuilder: $pathBuilder) {
active #type(name: "UserContainer") {
...ContainerFragment
}
sold #type(name: "UserContainer") {
...ContainerFragment
}
}
}
${ContainerFragment}
`
and calling it in my component like this, which does not work:
import queryString from 'query-string'
// ...
const { data } = useQuery(getUserContainersQuery, {
variables: {
showActive: true,
showSold: false,
pathBuilder: () => `/api/GetUserContainers?${queryString.stringify(params)}`,
},
fetchPolicy: 'cache-and-network',
})
The only way I got this to work was by passing the fully constructed path to the query from the component:
// query definition
getUserContainersQuery: gql`
query RESTgetUserContainers($pathString: String) {
containerHistory #rest(type: "ContainerHistoryResponse", path: $pathString) { // <-- pass path here, instead of pathBuilder
// same response as above
}
}
`
// component
const params = {
showActive: true,
showSold: false,
}
const { data } = useQuery(getUserContainersQuery, {
variables: {
pathString: `/api/GetUserContainers?${queryString.stringify(params)}`,
},
fetchPolicy: 'cache-and-network',
})
These seems to me like a really hacky solution which I'd like to avoid.
What is the recommended way to handle this query string problem?
You shouldn't need to use the pathBuilder for simple query string params. You can pass your params directly as variables to useQuery then pass then directly into teh path using the {args.somearg} syntax. The issue I see is you've not defined the variables your using for you query containerHistory bu only in the query alias RESTgetUserQueries. If updated is should look like this:
// query definition
getUserContainersQuery: gql`
query RESTgetUserContainers($showActive: Boolean, $showSold: Boolean) {
// pass the variables to the query
containerHistory(showActive:$showActive, showSold:$showSold) #rest(type: "ContainerHistoryResponse", path:"/api/GetUserContainers?showActive={args.showActive}&showSold={args.showTrue}") {
//.. some expected reponse
}
}
`
// component
const params = {
showActive: true,
showSold: false,
}
const { data } = useQuery(getUserContainersQuery, {
variables: {
showActive,
showSold
},
fetchPolicy: 'cache-and-network',
})

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

Switching from graphql-js to native graphql schemas?

Currently trying to switch from graphql-js to literal GraphQL types/schemas, I'd like to know if anyone has had any experience with this.
Let's take this really simple one :
const Person = new GraphQLObjectType({
name: 'Person',
fields: () => ({
name: {
type: GraphQLString,
description: 'Person name',
},
}),
});
I'd like to switch to the native GraphQL schema syntax i.e
type Person {
# Person name
name: String
}
However this would have to be incremental, and given the use of graphql-js, the best solution for now would be to parse GraphQL template literals to GraphQLObjectType (or any other type for that matter). Does anyone have experience doing this, I cannot seem to find any library for it unfortunately.
import { printType } from 'graphql';
printType(Person)
output:
type Person {
"""Person name"""
name: String
}
Here is the demo:
import { expect } from 'chai';
import { printType, printSchema, buildSchema, GraphQLSchema } from 'graphql';
import { logger } from '../util';
import { Person } from './';
describe('test suites', () => {
it('convert constructor types to string types', () => {
const stringTypeDefs = printType(Person).replace(/\s/g, '');
logger.info(printType(Person));
const expectValue = `
type Person {
"""Person name"""
name: String
}
`.replace(/\s/g, '');
expect(stringTypeDefs).to.be.equal(expectValue);
});
it('buildSchema', () => {
const stringTypeDefs = printType(Person);
const schema = buildSchema(stringTypeDefs);
expect(schema).to.be.an.instanceof(GraphQLSchema);
});
it('printSchema', () => {
const stringTypeDefs = printType(Person);
const schema = printSchema(buildSchema(stringTypeDefs));
logger.info(schema);
const expectValue = `
type Person {
"""Person name"""
name: String
}
`.replace(/\s/g, '');
expect(schema.replace(/\s/g, '')).to.be.eql(expectValue);
});
});
source code:
https://github.com/mrdulin/nodejs-graphql/blob/master/src/convert-constructor-types-to-string-types/index.spec.ts
You can use graphql-cli to extract a native graphql schema from a graphql server. All you need to do is..
Download the tool | npm i -g graphql-cli
Run graphql init in the directory of your project to
create .graphqlconfig file
Start your graphql server
Run graphql get-schema and this will generate a your schema in native graphql
SAMPLE .graphqlconfig
{
"projects": {
"my_sample_project": {
"schemaPath": "schema.graphql",
"extensions": {
"endpoints": {
"local": "http://localhost:8080/graphql"
}
}
}
}
}
We leverage the auto-generation of graphql schema/queries/mutations for our CI workflows.

Emit deprecation warnings with Apollo client

Background
We are working on a fairly large Apollo project. A very simplified version of our api looks like this:
type Operation {
foo: String
activity: Activity
}
type Activity {
bar: String
# Lots of fields here ...
}
We've realised splitting Operation and Activity does no benefit and adds complexity. We'd like to merge them. But there's a lot of queries that assume this structure in the code base. In order to make the transition gradual we add #deprecated directives:
type Operation {
foo: String
bar: String
activity: Activity #deprecated
}
type Activity {
bar: String #deprecated(reason: "Use Operation.bar instead")
# Lots of fields here ...
}
Actual question
Is there some way to highlight those deprecations going forward? Preferably by printing a warning in the browser console when (in the test environment) running a query that uses a deprecated field?
So coming back to GraphQL two years later I just found out that schema directives can be customized (nowadays?). So here's a solution:
import { SchemaDirectiveVisitor } from "graphql-tools"
import { defaultFieldResolver } from "graphql"
import { ApolloServer } from "apollo-server"
class DeprecatedDirective extends SchemaDirectiveVisitor {
public visitFieldDefinition(field ) {
field.isDeprecated = true
field.deprecationReason = this.args.reason
const { resolve = defaultFieldResolver, } = field
field.resolve = async function (...args) {
const [_,__,___,info,] = args
const { operation, } = info
const queryName = operation.name.value
// eslint-disable-next-line no-console
console.warn(
`Deprecation Warning:
Query [${queryName}] used field [${field.name}]
Deprecation reason: [${field.deprecationReason}]`)
return resolve.apply(this, args)
}
}
public visitEnumValue(value) {
value.isDeprecated = true
value.deprecationReason = this.args.reason
}
}
new ApolloServer({
typeDefs,
resolvers,
schemaDirectives: {
deprecated: DeprecatedDirective,
},
}).listen().then(({ url, }) => {
console.log(`🚀 Server ready at ${url}`)
})
This works on the server instead of the client. It should print all the info needed to track down the faulty query on the client though. And having it in the server logs seem preferable from a maintenance perspective.

Resources