Date and Json in type definition for graphql - graphql

Is it possible to have a define a field as Date or JSON in my graphql schema ?
type Individual {
id: Int
name: String
birthDate: Date
token: JSON
}
actually the server is returning me an error saying :
Type "Date" not found in document.
at ASTDefinitionBuilder._resolveType (****node_modules\graphql\utilities\buildASTSchema.js:134:11)
And same error for JSON...
Any idea ?

Have a look at custom scalars: https://www.apollographql.com/docs/graphql-tools/scalars.html
create a new scalar in your schema:
scalar Date
type MyType {
created: Date
}
and create a new resolver:
import { GraphQLScalarType } from 'graphql';
import { Kind } from 'graphql/language';
const resolverMap = {
Date: new GraphQLScalarType({
name: 'Date',
description: 'Date custom scalar type',
parseValue(value) {
return new Date(value); // value from the client
},
serialize(value) {
return value.getTime(); // value sent to the client
},
parseLiteral(ast) {
if (ast.kind === Kind.INT) {
return parseInt(ast.value, 10); // ast value is always in string format
}
return null;
},
})
};

Primitive scalar types in GraphQL are Int, Float, String, Boolean and ID. For JSON and Date you need to define your own custom scalar types, the documentation is pretty clear on how to do this.
In your schema you have to add:
scalar Date
type MyType {
created: Date
}
Then, in your code you have to add the type implementation:
import { GraphQLScalarType } from 'graphql';
const dateScalar = new GraphQLScalarType({
name: 'Date',
parseValue(value) {
return new Date(value);
},
serialize(value) {
return value.toISOString();
},
})
Finally, you have to include this custom scalar type in your resolvers:
const server = new ApolloServer({
typeDefs,
resolvers: {
Date: dateScalar,
// Remaining resolvers..
},
});
This Date implementation will parse any string accepted by the Date constructor, and will return the date as a string in ISO format.
For JSON you might use graphql-type-json and import it as shown here.

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?

GraphQL: A schema must have a query operation defined

My IDE (Phpstorm with JS GraphQL) is giving me the title error for my schema.
I'm new to GraphQL, what should the query be set to if the actual query operation only has a mutation at the root level?
Below is an actual query taken out of a (Shopify) tutorial for their GraphQL API. I'm copying my local schema definition below which attempted to accommodate its shape.
As you can see, The query is entirely nested in a mutation so I don't know what a query definition at the root level should even have.
// graphql.ts
import "isomorphic-fetch";
const buildPricingPlanQuery = (redirectUrl: string) => `mutation {
appSubscribeCreate(
name : "Plan 1"
returnUrl : "${redirectUrl}"
test : true
lineItems : [
{
plan : {
appUsagePricingDetails : {
cappedAmount : {
amount : 10
, currencyCode : USD
}
terms : "Up to 50 products"
}
}
}
{
plan : {
appRecurringPricingDetails : {
price : {
amount : 10
, currencyCode : USD
}
terms : "some recurring terms"
}
}
}
]
)
{
userErrors {
field
message
}
confirmationUrl
appSubscription {
id
}
}
}`;
export const requestSubscriptionUrl = async (ctx: any, accessToken: string, shopDomain: string) => {
const requestUrl = `https://${shopDomain}/admin/api/2019-10/graphql.json`;
const response = await fetch(requestUrl, {
method : 'post'
, headers : {
'content-type' : "application/json"
, 'x-shopify-access-token' : accessToken
},
body : JSON.stringify({query: buildPricingPlanQuery(`https://${shopDomain}`)})
});
const responseBody = await response.json();
const confirmationUrl = responseBody
.data
.appSubscriptionCreate
.confirmationUrl;
return confirmationUrl;
};
// pricingSchema.graphql
# ------------ Minor Types
enum CurrencyCode {
USD
EUR
JPY
}
type cappedAmount {
amount: Int
currencyCode : CurrencyCode
}
type appUsagePricingDetails {
cappedAmount: cappedAmount
}
input PlanInput {
appUsagePricingDetails: cappedAmount
terms: String
}
type userErrors {
field: String
message: String
}
type appSubscription {
id: Int
}
# ------------ Major Type and Schema definition
type PricingPlan {
appSubscribeCreate(
name: String!
returnUrl: String!
test: Boolean
lineItems: [PlanInput!]!
): String
userErrors: userErrors
confirmationUrl: String
appSubscription: appSubscription
}
schema {
mutation: PricingPlan
}
The error you see is referring to this stipulation of the GraphQL specification:
The query root operation type must be provided and must be an Object type.
There have been a couple proposals to remove this restriction, but as of the latest (June 2018) spec, a schema is considered invalid if there is no Query type. The spec also states that Object types (including Query) cannot be empty.
My advice: Just add a simple query type, such as
type Query {
ping: String #deprecated(reason: "https://stackoverflow.com/questions/59868942/graphql-a-schema-must-have-a-query-operation-defined")
}
If the spec gets updated, you can remove it later :)

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

Schema Stitching in Apollo GraphQL doesn't resolve types from other parts

I'm trying to make my GraphQL schema composable through schema stitching, but I'm struggling with how to resolve properties of types from a different part.
Here's the schema before decomposing:
type Referee {
id: ID!
stringProp: String!
}
type Referer {
id: ID!
pointer: Referee!
}
type Query {
referers: [Referer]
}
The types both have resolvers, in their respective schemas, that expand object { id } into { id, stringProp } or { id, pointer: { id } }, so that a query
query FromSingleSchema {
referers: {
id
pointer {
id
stringProp
}
}
}
resolves as expected; Query.referers resolves to a list of [{id}] objects, and each of those in turn resolve first into a Referer and then fetches the pointed-to Referee through type resolvers.
Now, I try to decompose the schema:
// schema A
type Referee {
id: ID!
stringProp: String!
}
// schema B
type Referer {
id: ID!
}
type Query {
referers: [Referer]
}
// schema Extensions
extend type Referer {
pointer: Referee!
}
and compose it again:
// both schemaA and schemaB have been created with makeExecutableSchema
import schemaA from './A'
import schemaB from './B'
// schemaExtensions is just a raw GraphQL string
// resolverExtensions is shown below
import { schemaExtensions, resolverExtensions } from './B'
const schema = mergeSchemas({
schemas: [schemaA, schemaB, schemaExtensions],
resolvers: Object.assign({}, resolverExtensions)
})
// resolverExtensions defined as follows:
{
Referer: {
pointer: {
fragment: 'fragment IdFragment on Referee { id }',
resolve: o => ({ id: o.pointerId })
}
}
}
With this, I can run this query without problems:
query OnlyIdFromDecomposedSchemas {
referers: {
id
pointer {
id
}
}
}
but this fails
query FullRefereeFromDecomposedSchemas {
referers: {
id
pointer {
id
stringProp
}
}
}
with the error message
Cannot return null for non-nullable field Referee.stringProp.
What do I need to do for the type resolver for Referee to be able to fill in the rest of the properties once { id } is available, like it does in a single, non-decomposed, schema?
I think you are looking for schema delegation. Schema Delegation is a way to automatically forward a query (or a part of a query) from a parent schema to another schema (called a subschema) that is able to execute the query.
You can use delegateToSchema method like this in your resolver:
{
Referer: {
pointer : {
resolve(parent, args, context, info) {
return info.mergeInfo.delegateToSchema({
schema: schemaA,
operation: 'query',
fieldName: 'referee', // modify according to your query for referee
context,
info,
});
}
}
}
}

Graphql Typecheck on redux like events

I'm implementing a graphql server over some existing REST api using apollo-server.
There is and endpoint returning a list of redux-like events, where Hi have a type and a payload.
type is a string and payload is an object. e.g.
[
{
type:"joined"
from:"member1"
payload:{
received_events:"1518377870416"
invited_by:"member2"
}
},
{
type:"text"
from:"member1"
payload:{
test_string:"hello"
}
}
]
What I need to check is the following:
1) type is an enum joined|text
2) from is a String
3) if type == joined then the payload should contain received_events and invited_by, if type == text then payload should contain test_string
What is the best way to do it? I'm looking at the scalar , and Union, but I'm not sure what to do.
One way to solve this is to inject the type from your event type into the payload object. It can then be used inside the __resolveType resolver for your union. A simple example:
const typeDefs = `
type Query {
events: [Event]
}
type Event {
type: String
payload: Payload
}
union Payload = Text | Joined
type Text {
test_string: String
}
type Joined {
received_events: String
invited_by: String
}
`;
// Since your type names and the specified types are different, we
// need to map them (resolveType needs to return the exact name)
const typesMap = {
text: 'Text',
joined: 'Joined'
}
const resolvers = {
Query: {
events: (root, args, context) => {
return [
{ type: 'text', payload: { test_string: 'Foo' } }
];
},
},
// We can use the resolver for the payload field to inject the type
// from the parent object into the payload object
Event: {
payload: (obj) => Object.assign({ type: obj.type }, obj.payload)
},
// The type can then be referenced inside resolveType
Payload: {
__resolveType: (obj) => typesMap[obj.type]
}
};

Resources