Switching from graphql-js to native graphql schemas? - graphql

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.

Related

GraphQL Codegen duplicates RegisterDocument with typescript-urql

The codegen.ts config below results in duplicating the RegisterDocument entries.
codegen.ts:
const config: CodegenConfig = {
overwrite: true,
schema: "http://localhost:4000/graphql",
documents: "src/graphql/**/*.graphql",
generates: {
"src/generated/graphql": {
preset: "client",
plugins: [
"typescript-urql"
],
config: {
documentVariableSuffix: 'test2'
}
}
}
};
the output:
export const RegisterDocument = {"kind":"Document", ...}
export const RegisterDocument = gql`
mutation Register($username: String!, $password: String!) {
register(options: {username: $username, password: $password}) {
errors {
field
message
}
user {
id
username
createdAt
}
}
}
`;
export function useRegisterMutation() {
return Urql.useMutation<RegisterMutation, RegisterMutationVariables>(RegisterDocument);
};
Seemingly either the documentVariableSuffix param didn't affect the output const naming or it was a wrong param. The use of the typescript-operations or/and typescript packages only led to more duplicates.
What is the way to have typescript-urql register the mutation differently?
UP. The register mutation I need typings for:
const registerMutationDocument = graphql(`
mutation Register($username: String!, $password: String!) {
register(options: { username: $username, password: $password }) {
errors {
field
message
}
user {
id
username
createdAt
}
}
}
`)
I'm Charly, from The Guild, working on GraphQL Code Generator.
The preset: "client" is not meant to be used in combination with other plugins.
You must use either the client-preset or typescript-urql-plugin which provides 2 different ways to get typed GraphQL Operations.
The typescript-urql-plugin generates React hooks while, the client-preset generated typed GraphQL documents that can be used with URQL's native useQuery() and useMutation().
We now recommend using the client-preset that provides a better developer experience and smaller generated code for the same result.
You will find a guide to setup the client-preset with URQL here: https://the-guild.dev/graphql/codegen/docs/guides/react-vue
After a few attempts with various code pieces it seems I've got it to work. Thank you mr. Poly for the hints.
Here's the take.
First, the present iteration of graphql-codegen watches for .ts/.tsx documents not *.graphql ones. Second the only plugins needed are the ones listed in the docs.
const config: CodegenConfig = {
overwrite: true,
schema: "http://localhost:4000/graphql",
documents: "src/graphql/mutations/*.ts",
generates: {
"src/generated/graphql/": {
preset: "client",
plugins: []
}
}
};
Third the way to put the mutations etc. to a dedicated folder that I used was to create one at src/graphql/mutations. The register.ts holding the mutation had the following code:
import { graphql } from '../../generated/graphql';
export const registerMutationDocument = graphql(`
mutation Register($username: String!, $password: String!) {
register(options: { username: $username, password: $password }) {
errors {
field
message
}
user {
id
username
createdAt
}
}
}
`);
The only problem for me was if I tried to add a field to it that didn't exist on the model the editor froze.
The usage within the component:
import { registerMutationDocument } from '../graphql/mutations/register';
...
const [, register] = useMutation(registerMutationDocument);

Apollo Rover CLI returns non-wrapped schema when using NestJS driver

I have two subgraphs hosting their own respective schemas. Each of these schemas have input types that are named the same. For example, both schemas have an entity called Product and therefore both have inputs relevant to this entity called ProductCreateInput. Due to the way Apollo Federation works, because Input types are merged using the intersection strategy, I have to rename the inputs to different names to avoid composition errors when composing a supergraph.
So I rename the ProductCreateInput to something like Product_ProductCreateInput and Review_ProductCreateInput. I do this for every input type by using a regex and wrapSchema from #graphql-tools/wrap to rename the input types to precede with their subgraph name.
The driver code :
#Injectable()
export class GqlConfigService implements GqlOptionsFactory {
constructor(private readonly config: ConfigService, private readonly prisma: PrismaService) {}
createGqlOptions(): ApolloFederationDriverConfig {
const plugins: PluginDefinition[] = [];
if (this.config.graphql.sandbox) plugins.push(ApolloServerPluginLandingPageLocalDefault);
if (!this.config.graphql.trace) plugins.push(ApolloServerPluginInlineTraceDisabled());
return {
typeDefs: print(ALL_TYPE_DEFS),
resolvers: { Upload: GraphQLUpload },
transformSchema: async (schema: GraphQLSchema) => {
return renamedInputTypesSchema(schema);
},
debug: !this.config.production,
playground: false,
plugins,
introspection: this.config.graphql.introspection,
cors: this.config.cors,
csrfPrevention: this.config.graphql.csrfPrevention,
cache: 'bounded',
installSubscriptionHandlers: this.config.graphql.subscriptions,
subscriptions: this.config.graphql.subscriptions
? {
'graphql-ws': {
onConnect: (context: Context<any>) => {
const { connectionParams, extra } = context;
extra.token = connectionParams.token;
},
},
}
: undefined,
context: async (ctx): Promise<IContext> => {
// Subscriptions pass through JWT token for authentication
if (ctx.extra) return { req: ctx.extra, prisma: this.prisma };
// Queries, Mutations
else return { ...ctx, prisma: this.prisma };
},
};
}
}
The schemaWrapper code :
import { RenameTypes, wrapSchema } from '#graphql-tools/wrap';
import { GraphQLSchema } from 'graphql';
export const modelNames = ['User', 'Product'];
//This subgraph's name is set to "Product".
//Input type args is modified to be preceded by "Product_" because inputs are merged using the intersection strategy in the current version of Apollo Federation and directives are not supported with input types.
export const renamedInputTypesSchema = async (schema: GraphQLSchema) => {
const typeMap = schema.getTypeMap();
const models: string = modelNames.join('|');
const inputTypes = Object.keys(typeMap).filter(type => {
const inputTypesRegex = new RegExp(
`(${models})(WhereInput|OrderByWithRelationInput|WhereUniqueInput|OrderByWithAggregationInput|ScalarWhereWithAggregatesInput|CreateInput|UncheckedCreateInput|UpdateInput|UncheckedUpdateInput|CreateManyInput|UpdateManyMutationInput|UncheckedUpdateManyInput|CountOrderByAggregateInput|AvgOrderByAggregateInput|MaxOrderByAggregateInput|MinOrderByAggregateInput|SumOrderByAggregateInput|Create.*?Input|Update.*?Input)`
);
return type.match(inputTypesRegex)?.input;
});
return wrapSchema({
schema: schema,
transforms: [new RenameTypes(name => (inputTypes.includes(name) ? `Product_${name}` : name))],
});
};
This works. When I go into Apollo Sandbox and look at the schema, all the inputs are successfully preceded with Product_ like I expect :
However, when I use rover subgraph introspect in order to pipe the output to publish to my managed federation, I get the unwrapped schema (the relevant rover subgraph introspect output):
input ProductCountOrderByAggregateInput {
id: SortOrder
sku: SortOrder
description: SortOrder
}
input ProductAvgOrderByAggregateInput {
id: SortOrder
}
input ProductMaxOrderByAggregateInput {
id: SortOrder
sku: SortOrder
description: SortOrder
}
input ProductMinOrderByAggregateInput {
id: SortOrder
sku: SortOrder
description: SortOrder
}
What is going on here? Apollo sandbox shows the correct wrapped schema, yet rover introspect doesn't.

TypeGraphQL createUnionFunction with parameter

I'm trying to implement an extension of typegraphql's createUnionType() function to where I can pass in a class/ObjectType instead of hardcoding, and it will return a union type of both.
What I have so far doesn't work but I feel like it's possible. Could anyone provide any insight? Maybe it's not possible?
typedefs
import { ObjectType, Field, createUnionType, ClassType } from "type-graphql";
#ObjectType()
export default class MutationSuccess {
#Field()
success: boolean = true;
}
// Doesn't work
export const MutationResponse = (objectType: ClassType) => createUnionType({
name: 'MutationResponseType',
types: () => [MutationSuccess, objectType],
})
How I'm trying to use it in my resolver
#Resolver()
export default class RelationResolver {
#Mutation(() => MutationResponse(Relation), { description: 'follow user' })
async follow(
#Arg('relationInput') relationInput: RelationInput
): Promise<Relation | MutationSuccess> {
// do some stuff
}
}
error
UnhandledPromiseRejectionWarning: Error: Cannot determine GraphQL output type for follow
The Relation class need to be decorated with #ObjectType and the union type name has to be unique.

Using GraphQl and MongoDB schema as library in NRWL/NX Enviornment

I am fairly new to the NRWL/NX world.
What I am trying to accomplish here is, to use the GraphQL (with the MongoDB) for the API.
In past, I've created the GraphQL project with MongoDB in a non-NRWL environment.
However since now we have multiple projects, we are moving to NX.
There are couple of MongoDB schema which are used across multiple projects, so I've decided to use them as a library. I generated a library and added following code
import { MongooseModule } from '#nestjs/mongoose';
import { ConfigService, ConfigModule } from '#another-lib/config-helper';
import { Module } from '#nestjs/common';
import { Location } from './model/location'; //This wouldn't be accessible from elsewhere
export const databaseProviders = [
MongooseModule.forRootAsync({
imports: [ ConfigModule ],
inject: [ ConfigService ],
useFactory: async (config: ConfigService) => ({
uri: config.get('MONGODB_URI'),
useNewUrlParser: true,
useFindAndModify: false,
}),
}),
];
#Module({
imports: [ ...databaseProviders, Location ],
exports: [ ...databaseProviders, Location ],
})
export class DatabaseModule {}
The MongoDB model is pretty standard.
import * as mongoose from 'mongoose';
const LocationSchema = new mongoose.Schema(
{
LocationName: {
type: String,
},
LocationCode: {
type: String,
},
isPickable: {
type: Boolean,
},
TemplateID: {
type: String,
},
},
{ collection: 'locations', timestamps: true },
);
export interface ILocation extends mongoose.Document {
_id: string;
LocationName: string;
LocationCode: string;
isPickable: boolean;
TemplateID: string;
}
//used for the server
export interface ILocationModel extends mongoose.Model<ILocation> {}
// export const LocationSchema = mongoose.model('location', _LocationSchema);
export const Location: ILocationModel = <ILocationModel>mongoose.model<ILocation>('Location', LocationSchema);
How can I access the mongodb model via DatabaseModule, Please suggest.
Thanks
-N Baua
Managed to solved the problem by setting up the correct directory structure and importing them in the datahelper.ts file.
We later exported the referenced interfaces from the datahelper.ts into the index.ts and we were done.
Thanks.

ApolloServer using mergeSchemas, Error: Apollo Server requires either an existing schema, modules or typeDefs

I think I am just using this module wrong and that is the reason I am getting an error. According to the documentation I can pass an array of resolvers and schemas to the mergeSchemas function from graphql-tools. But I am getting this error:
Error: Apollo Server requires either an existing schema, modules or typeDefs
Here is the code:
app.js
import { ApolloServer } from 'apollo-server'
import schema from './modules'
const server = new ApolloServer({
schema
})
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
})
Merging Schemas
import { mergeSchemas } from 'graphql-tools'
import bookSchema from './book/schema/book.gql'
import bookResolver from './book/resolvers/book'
export const schema = mergeSchemas({
schemas: [bookSchema],
resolvers: [bookResolver] // ==> Maybe I have to merge these before hand?
})
Schema
type Query {
book(id: String!): Book
bookList: [Book]
}
type Book {
id: String
name: String
genre: String
}
Resolver
export default {
Query: {
book: (parent, args, context, info) => {
console.log(parent, args, context, info)
return {
id: `1`,
name: `name`,
genre: `scary`
}
},
bookList: (parent, args, context, info) => {
console.log(parent, args, context, info)
return [
{ id: `1`, name: `name`, genre: `scary` },
{ id: `2`, name: `name`, genre: `scary` }
]
}
}
}
Silly mistake:
Needed to import schema object from de structuring it:
import { schema } from './modules'
For my scenario, I was getting the above error when I passed the argument like below.
const server = new ApolloServer(typeDefs, resolvers);
then it changed to
const server = new ApolloServer({typeDefs, resolvers});
the below error has gone up.
Error: Apollo Server requires either an existing schema, modules or typeDefs

Resources