Why does GraphQL require me to have a resolver that returns an empty object? - graphql

So, when I have a schema:
type Query { me: Me }
type Me { hello: String }
I expect a satisfying resolver to be:
const resolvers = {
Me: {
hello() { return "Hi, me!"; }
}
}
Alas, this isn't the case, I have to add a dummy me-resolver (see below).
Why is this the case?
I'd say it should traverse the query, and if it can't find a satisfying field-resolver, it should look for a corresponding type-resolver.
const { graphql } = require("graphql");
const { makeExecutableSchema } = require("graphql-tools");
const compose = require("compose-function");
const typeDefs = `
type Query {
me: Me
}
type Me {
hello: String!
}
`;
const resolvers = {
Query: {
// ===========================>
me() {
return {};
}
// <===========================
},
Me: {
hello() {
return "Hi, me!";
}
}
};
const schema = makeExecutableSchema({
typeDefs,
resolvers
});
graphql(schema, "{ me { hello } }").then(
compose(
console.log,
JSON.stringify
)
); // {"data":{"me":{"hello":"Hi, me!"}}}

Alright, I've figured it out. It actually makes a lot of sense.
null is a valid response. Only if there is an object with properties that the query wants, it should look for the next resolver to satisfy the query.
It's also written in the spec under Errors and Non‐Null Fields https://graphql.github.io/graphql-spec/June2018/#sec-Executing-Selection-Sets

Related

Graphql directives doesn't work for mutation input arguments when pass the arguments as external variables

I am implementing custom Graphql directives to validate client input. A sample code as below, I referred to the official examples here: https://www.apollographql.com/docs/apollo-server/schema/creating-directives/#enforcing-value-restrictions
const { ApolloServer, gql, SchemaDirectiveVisitor } = require('apollo-server');
const { GraphQLScalarType, GraphQLNonNull } = require('graphql');
const typeDefs = gql`
directive #validateInput on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION
type Mutation {
sampleMutation(
test1: String #validateInput
nestedInput: SampleMutationInput
): String
}
input SampleMutationInput {
test2: String #validateInput
}
`;
The implementation of the directive logic:
class ValidateInputDirective extends SchemaDirectiveVisitor {
visitInputFieldDefinition(field) {
this.wrapType(field);
}
visitFieldDefinition(field) {
this.wrapType(field);
}
visitArgumentDefinition(argument) {
console.log('visitArgumentDefinition', argument);
this.wrapType(argument);
}
wrapType(field) {
console.log('wrapType', field);
if (
field.type instanceof GraphQLNonNull &&
field.type.ofType instanceof GraphQLScalarType
) {
field.type = new GraphQLNonNull(
new ValidateInputType(field.type.ofType)
);
} else if (field.type instanceof GraphQLScalarType) {
field.type = new ValidateInputType(field.type);
} else {
throw new Error(`Not a scalar type: ${field.type}`);
}
}
}
class ValidateInputType extends GraphQLScalarType {
constructor(type) {
super({
name: 'ValidatedInput',
serialize(value) {
return value;
},
parseValue(value) {
const result = type.parseValue(value);
if (/[?!]/.test(result)) {
throw new Error('Invalid characters');
}
return result;
},
parseLiteral(ast) {
const result = type.parseLiteral(ast);
if (/[?!]/.test(result)) {
throw new Error('Invalid characters');
}
return result;
},
});
}
}
export default { validateInput: ValidateInputDirective };
It works as expected for the input field 'test2', but for the argument 'test1', it works when the String value is directly passed to the mutation, then the method "parseLiteral" is called and the validation logic applied to the input value. However, when I pass the 'test1' value as external variables (via JSON format), the directive doesn't work and the method "parserValue" never be called.
What I found so far:
"parserValue" is used when the input comes from variable JSON. "parseLiteral" is used when the input comes directly from the query/mutation.
It seems a bug in Graphql tools according to https://github.com/ardatan/graphql-tools/issues/789
I want to understand:
what's the real difference between an argument passed by variable and directly pass to mutation?
is there an alternate way to apply the directives to an argument to avoid this issue?
If this is really a bug with Graphql, does it fixed now? Which version should I use to resolve the issue?

Apollo nodejs server; How to get mutation/query schema path in the request context when writing a plugin?

I'm writing an Apollo server plugin for node.js, and my goal is to improve my teams debugging experience. My plugin currently looks something like this:
export function eddyApolloPlugin(): ApolloServerPlugin {
return {
requestDidStart(requestContext) {
// Set requestId on the header
const requestId = (requestContext?.context as EddyContext)?.requestId;
if (requestId) {
requestContext.response?.http?.headers.set('requestId', requestId);
}
return {
willSendResponse(context) { // <== Where do I find the "path" in the schema here?
// Inspired by this: https://blog.sentry.io/2020/07/22/handling-graphql-errors-using-sentry
// and the official documentation here: https://docs.sentry.io/platforms/node/
// handle all errors
for (const error of requestContext?.errors || []) {
handleError(error, context);
}
},
};
},
};
}
I would like to know if I can access the path in the schema here? It's pretty easy to find the name of mutaiton/query with operation.operationName, but where can I get the name of the query/mutation as defined in the schema?
Solution
export function eddyApolloPlugin(): ApolloServerPlugin {
return {
requestDidStart(requestContext) {
// Set requestId on the header
const requestId = (requestContext?.context as EddyContext)?.requestId;
if (requestId) {
requestContext.response?.http?.headers.set('requestId', requestId);
}
return {
didResolveOperation(context) {
const operationDefinition = context.document
.definitions[0] as OperationDefinitionNode;
const fieldNode = operationDefinition?.selectionSet
.selections[0] as FieldNode;
const queryName = fieldNode?.name?.value;
// queryName is what I was looking for!
},
};
},
};
}
Your requirement is not very clear. If you want to get the name of the query/mutation to distinguish which query or mutation the client sends.
You could get the name from context.response.data in willSendResponse event handler.
E.g.
server.ts:
import { ApolloServer, gql } from 'apollo-server';
import { ApolloServerPlugin } from 'apollo-server-plugin-base';
import { parse, OperationDefinitionNode, FieldNode } from 'graphql';
function eddyApolloPlugin(): ApolloServerPlugin {
return {
requestDidStart(requestContext) {
return {
didResolveOperation(context) {
console.log('didResolveOperation');
const obj = parse(context.request.query!);
const operationDefinition = obj.definitions[0] as OperationDefinitionNode;
const selection = operationDefinition.selectionSet.selections[0] as FieldNode;
console.log('operationName: ', context.request.operationName);
console.log(`${context.operation!.operation} name:`, selection.name.value);
},
willSendResponse(context) {
console.log('willSendResponse');
console.log('operationName: ', context.request.operationName);
console.log(`${context.operation!.operation} name:`, Object.keys(context.response.data!)[0]);
},
};
},
};
}
const typeDefs = gql`
type Query {
hello: String
}
type Mutation {
update: String
}
`;
const resolvers = {
Query: {
hello() {
return 'Hello, World!';
},
},
Mutation: {
update() {
return 'success';
},
},
};
const server = new ApolloServer({ typeDefs, resolvers, plugins: [eddyApolloPlugin()] });
const port = 3000;
server.listen(port).then(({ url }) => console.log(`Server is ready at ${url}`));
GraphQL Query:
query test {
hello
}
the logs of the server:
didResolveOperation
operationName: test
query name: hello
willSendResponse
operationName: test
query name: hello
GraphQL Mutation:
mutation test {
update
}
the logs of the server:
didResolveOperation
operationName: test
mutation name: update
willSendResponse
operationName: test
mutation name: update

Why apollo server custom directive not working?

I am trying to implement a custom directive with apollo server. I took the example from the official site.
My query is like below:
directive #upper on FIELD_DEFINITION
type Query {
hello: String #upper
}
My resolver is like below:
Query:{
async hello(){
return "hello world";
}
}
Here is my apollo server config for custom directive:
const { ApolloServer, SchemaDirectiveVisitor } = require('apollo-server-express');
const { defaultFieldResolver } = require("graphql");
class UpperCaseDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field;
field.resolve = async function (...args) {
const result = await resolve.apply(this, args);
if (typeof result === "string") {
return result.toUpperCase();
}
return result;
};
}
}
const server = new ApolloServer({
schema,
schemaDirectives: {
upper: UpperCaseDirective
},
introspection: true,
playground: true,
cors: cors()
});
The output I always get :
{
"data": {
"hello": "hello world"
}
}
Why the custom directive is not activated? Why the output is not in uppercase?
You would pass schemaDirectives to ApolloServer's constructor if ApolloServer was building your schema for you -- that is, if you were also passing in resolvers and typeDefs. If you're passing in an existing schema, it's already built and ApolloServer won't apply the directives. If you're using makeExecutableSchema, you can pass your schemaDirectives to that. It's also possible to manually visit all directives like this:
SchemaDirectiveVisitor.visitSchemaDirectives(schema, schemaDirectives)
This is the only way to get directives to work with certain libraries, like graphql-modules.

How to get field value having space, hyphen in REST API in GraphQL

REST API Endpoint - https://api.jikan.moe/v3/manga/13
"Alterantive version", "Side story" and "Spin-off" fields are having space and hyphen.
common_schema.js
const { gql } = require('apollo-server');
const typeDefs = gql`
type RelatedType {
Adaptation: [RelatedSubType]
SideStory: [RelatedSubType]
Character: [RelatedSubType]
Summary: [RelatedSubType]
Other: [RelatedSubType]
AlternativeVersion: [RelatedSubType]
SpinOff: [RelatedSubType]
}
type RelatedSubType {
mal_id: ID
type: String
name: String
url: String
}
`;
module.exports = typeDefs;
If I write field value as Spin-off or Alternative version then it gives an error in terminal. "Spin-off" also doesn't work. I know these aren't valid but then also tried.
manga_resolver.js
module.exports = {
Query: {
manga: (_, { id }, { dataSources }) =>
dataSources.mangaAPI.getMangaDetail(id)
}
};
manga.js
const { RESTDataSource } = require('apollo-datasource-rest');
class MangaAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = 'https://api.jikan.moe/v3/manga/';
}
async getMangaDetail(mal_id) {
const response = await this.get(`/${mal_id}`);
return response;
}
}
module.exports = MangaAPI;
Query -
query getMangaDetail{
manga(id: 13){
related{
Adaptation{
name
}
AlternativeVersion{
name
}
SpinOff{
name
}
}
}
}
Getting null in those fields which are having space and hyphen.
Query result -
{
"data": {
"manga": {
"related": {
"Adaptation": [
{
"name": "One Piece"
}
],
"AlternativeVersion": null,
"SpinOff": null
}
}
}
}
Repository - jikan-graphql
According to the spec, names in GraphQL must follow this format:
/[_A-Za-z][_0-9A-Za-z]*/
In other words, neither spaces nor dashes are permitted. If your data source is returning property names that are formatted incorrectly, you can just provide resolvers for the fields in question:
const resolvers = {
RelatedType: {
sideStory: (parent) => {
return parent['Side story']
},
...
}
}

Nested query and mutation in type-Graphql

I found a feature in graphql to write nested query and mutation, I tried it but got null. I found the best practices of building graphqL schema on Meetup HolyJs and the speaker told that one of the best ways is building "Namespaced" mutations/queries nested, in this way you can write some middlewares inside the "Namespaced" mutations/queries and for get the Child mutation you should return an empty array because if you return an empty array, Graphql understand it and go one level deep.
Please check the example code.
Example in graphql-tools
const typeDefs = gql`
type Query { ...}
type Post { ... }
type Mutation {
likePost(id: Int!): LikePostPayload
}
type LikePostPayload {
recordId: Int
record: Post
# ✨✨✨ magic – add 'query' field with 'Query' root-type
query: Query!
}
`;
const resolvers = {
Mutation: {
likePost: async (_, { id }, context) => {
const post = await context.DB.Post.find(id);
post.like();
return {
record: post,
recordId: post.id,
query: {}, // ✨✨✨ magic - just return empty Object
};
},
}
};
This is my Code
types
import { ObjectType, Field } from "type-graphql";
import { MeTypes } from "../User/Me/Me.types";
#ObjectType()
export class MeNameSpaceTypes {
#Field()
hello: string;
#Field({ nullable: true })
meCheck: MeTypes;
}
import { Resolver, Query } from "type-graphql";
import { MeNameSpaceTypes } from "./MeNamespace.types";
#Resolver()
export class MeResolver {
#Query(() => MeNameSpaceTypes)
async Me() {
const response = {
hello: "world",
meCheck:{}
};
return response;
}
}
Result of code
query {
Me{
hello
meCheck{
meHello
}
}
}
--RESULT--
{
"data": {
"Me": {
"hello": "world",
"meCheck": {
"meHello": null
}
}
}
}
I got a null instead a meHello resolver. Where am I wrong?
Namespaced mutations are against GraphQL spec as they are not guarranted to run sequentially - more info in this discussion in GitHub issue related to your problem:
https://github.com/MichalLytek/type-graphql/issues/64

Resources