nexus
nexus copied to clipboard
How to do schema stitching with Nexus?
Hi there!
This is a question not an issue or a bug 😄
What is it the best way to do schema stitching with nexus? The only thing I can think about is using the converter to convert the schemas to code first, but it doesn’t sound ideal and I am not sure if it is possible to use fragment with it.
Thanks for the amazing work!
I think we can just use graphql-tools for this.
Since the output of nexus makeSchema function is a graphql js schema, one can easily use packages from the existing ecosystem like graphql-shield and many others.
import {
makeExecutableSchema,
addMockFunctionsToSchema,
mergeSchemas,
} from 'graphql-tools';
import { makeSchema } from 'nexus';
const nexusSchema = makeSchema({
types: [Account, Node, Query, StatusEnum],
// or types: { Account, Node, Query }
// or types: [Account, [Node], { Query }]
});
const authorSchema = makeExecutableSchema({
typeDefs: `
type User {
id: ID!
email: String
}
type Query {
userById(id: ID!): User
}
`
});
addMockFunctionsToSchema({ schema: authorSchema });
export const schema = mergeSchemas({
schemas: [
authorSchema,
nexusSchema
],
});
@pantharshit00 oh that is great thank you!
Just to clarify a couple of things:
addMockFunctionsToSchema({ schema: authorSchema });
this is optional and just for mocking/testing right?
export const schema = mergeSchemas({
schemas: [
chirpSchema,
nexusSchema
],
});
Did you mean authorSchema
rather than chirpSchema
?
😄
Also do you know if it would be possible to also extend the merged schema with nexus, rather than with graphql tool mergeSchema? Like this:
return mergeSchemas({
schemas: [
bookingSchema,
customerSchema
],
resolvers: {
Booking: {
customer: {
fragment: `... on Booking { id }`,
resolve(booking, args, context, info) {
return info.mergeInfo.delegateToSchema({
schema: customerSchema,
operation: "query",
fieldName: "customers",
args: {
where: { id: booking.customerId }
},
context,
info
});
}
},
To be fair, probably it is no point to try to do it differently.
@otrebu there are some good solutions for doing schema extension and composition with other GraphQL schemas in Nexus that I need to document / add a few new options for.
Unfortunately don't have time to fully go into them right this minute, but I'll leave this ticket open and report back once I have some time to document/add some examples for how to do this.
Would you be able to share the general structure of how your schema is split up and how you'd expect a tool that allowed schema stitching with nexus to work? Would most types be written in nexus and only some would be stitched in?
Also, have you seen the extendType api for building a schema from multiple places in the codebase? There's also a mutationField and I'll soon add a similar queryField
api for this use case.
doing schema stitching using mergeSchemas
from graphql-tools
, working great so far.
using extendType is problematic if the schema is create from a remote schema, because the typechecking have no idea the type to extends exists
@tgriesser thank you for reply, and thank you @kandros for your comment. It would be great to see more examples, as I didn't try to implement the stitching yet.
I would need to create another example, as what I am working on it is for a client. It might take me a while before I can find the time to do this, but I can try.
I need to look into extendType, as I didn't see it yet.
@otrebu most of the example are using features not yet documented, so it's a great place to look into
https://github.com/prisma/nexus/blob/18401c465a745254fb8dbd3c5244bdc1f02c1d11/examples/kitchen-sink/src/kitchen-sink-definitions.ts
Hi @tgriesser do you have any update about the documentation?
I started to implement stitching alongside nexus like @pantharshit00 kindly showed me. For something simple works as a treat. Happy days.
But then I found myself in the scenario (or similar at least) @kandros mentioned!
I would like to add query and mutations that return types that are defined in remote schemas (in my scenario I have full control on those). Nexus is not aware of those types because are stitched after. I would be cool to stitch "before"/ within nexus, so that it is aware of this other types and they become usable.
Otherwise I should merge these schemas, use the converter. It is not optimal I would say.
It's possible, from an introspected schema we could generate nexus code but pretty hard to accomplish, it needs lot of tooling work but looks doable.
I suggest you take a look at nexus codebase to get a grasp of how typings generation is applied, what happens is crazy but easy to follow along 😁
@kandros I horribly done that before, I was hoping to be a temporary solution though. But in the scenario below I didn't want mutations and queries.
I have done something like this(horrible):
import * as fs from "fs";
import { parse, DocumentNode, buildASTSchema, printSchema } from "graphql";
import { convertSDL } from "nexus";
const sdl = fs.readFileSync(__dirname + "/unified-graphql/schema.graphql", "utf-8");
const parsedSchema = parse(sdl);
const newDefinitions = parsedSchema.definitions
.filter(
(d: any) =>
!d.name.value.includes("Query") &&
!d.name.value.includes("Mutation") &&
!d.name.value.includes("Subscription") &&
!d.name.value.includes("Edge") &&
!d.name.value.includes("Aggregate") &&
!d.name.value.includes("Connection")
);
const filteredSchema: DocumentNode = {
kind: "Document",
definitions: newDefinitions,
loc: parsedSchema.loc
};
const newSchema = buildASTSchema(filteredSchema);
const codeFirstString = convertSDL(printSchema(newSchema));
fs.writeFileSync(__dirname + "/someTypesFromPrisma.ts", codeFirstString, {
encoding: "utf-8"
});
Hi @tgriesser, sorry to tag you again.
It would be good to have your view. I see this topic being related to this one on the nexus-prisma
project plugin.
It would be good to be part of nexus
, or a plugin like nexus-prisma
the ability to stitch. As like in my case I would like to be able to add a query and mutations of types I stitched in.
@tgriesser to answer your question 😄
Would you be able to share the general structure of how your schema is split up and how you'd expect a tool that allowed schema stitching with nexus to work? Would most types be written in nexus and only some would be stitched in?
At the moment I am doing something like this:
const stitchSchemas = async () => {
const organisationSchema = await getRemoteSchema(
prismaEndpoints.organisations
);
const customerSchema = await getRemoteSchema(prismaEndpoints.customers);
return mergeSchemas({
schemas: [
organisationSchema,
customerSchema,
nexusSchema,
linkTypeDefs // where I extend some types from organisationSchema and customerSchema
],
resolvers: {
/// resolvers to delegate with the extended schema
}
})};
nexusSchema
is not aware of the types in organisationSchema and customerSchema.
So if I create a mutation using nexus
:
const Mutation = mutationType({
definition(t) {
t.field("createCustomerFromPlainPassword", {
type: "Customer", // I can't do this 😢
args: {
input: arg({ type: createCustomerFromPlainPasswordInput })
},
resolve: async (
root,
{ input },
{ customerService },
info
) => {
const newCustomer = await customerService.createCustomer(
input
);
return newCustomer;
}
});
}
});
I can't do that, Customer
type is defined in my customerSchema
.
So when you ask me what I would really like to be able to do, it is to stitch from nexus
so that it is aware of my other types.
const nexusSchema = makeSchema({
externalSchemasToStitchIn:{
schemas: [customerSchema, organisationSchema],
excludeTypes: ["Role"]
}
types: [CreateCustomerFromPlainPasswordInput, Query, Mutation],
shouldGenerateArtifacts: true,
outputs: {
schema: path.join(__dirname, "./generated/schema.graphql"),
typegen: path.join(__dirname, "./generated/types.ts")
},
typegenAutoConfig: {
contextType: "ctx.IContext",
sources: [
{
alias: "ctx",
source: path.join(__dirname, "../context.ts")
}
]
}
});
So that when I extend my Mutation type I can return my Customer
type. Of which shape I am sure will respect the schema.
Thanks for the info - I need to think a little more about how this will work and come up with some recommended patterns here. I'm considering incorporating something similar to the tools in Prisma to handle schema delegation - but it require a decent bit of work, so no ETA on it at the moment.
By the way, have you tried doing it the other way around, feeding the types generated from the merged schemas into Nexus? Haven't tried it, but something like:
const mergedSchemas = mergeSchemas({
schemas: [
organisationSchema,
customerSchema,
],
resolvers: {
/// resolvers to delegate with the extended schema
}
})};
const addedMutationField = mutationField("createCustomerFromPlainPassword", {
type: "Customer", // should be able to do this now.
args: {
input: arg({ type: createCustomerFromPlainPasswordInput })
},
resolve: async (
root,
{ input },
{ customerService },
info
) => {
const newCustomer = await customerService.createCustomer(
input
);
return newCustomer;
}
});
const finalSchema = makeSchema({
types: [schema.getTypeMap(), addedMutationField],
shouldGenerateArtifacts: true,
outputs: {
schema: path.join(__dirname, "./generated/schema.graphql"),
typegen: path.join(__dirname, "./generated/types.ts")
},
typegenAutoConfig: {
contextType: "ctx.IContext",
sources: [
{
alias: "ctx",
source: path.join(__dirname, "../context.ts")
}
]
}
});
By the way, have you tried doing it the other way around
Thought about this more after making the previous comment and this approach won't quite work as I mentioned - though it's how it should work eventually once the appropriate changes are made to support this. Will work on some approaches for this and keep you posted once there's something to try out here.
@tgriesser thanks!
I see this topic being related to this from the nexus-prisma
plugin: https://github.com/prisma/nexus-prisma/issues/129
What are your thoughts on that?
Just to give you an idea, this is the infrastructure I am working on: There are 4 different prisma services, which we then stitch together. This stitched graphql server which contains all the CRUD operations from these 4 different prisma servers also does some extensions. Adding some queries, mutations and types. At the moment I achieve this by using the converter. Convert the stitched schema into code first, so that I have the type I need to create my nexus schema. Then stitching again, including the nexus schema. This is not ideal, but it seems working.
Then we have another 3 graphql servers in front of the unified graph server. On these we will do the auth, maybe extending/hiding some queries/mutations/types.
If there were a generalised nexus-prisma
it would be great to use that approach to achieve the above. This last paragraph only partially concerning you, but at least you have a picture of a scenario, that doesn't seem so strange in a world of microservices.
If you could help me to help you and help @Weakky . Cheers!
Any update on this given the recent transition to Nexus Framework?
I would love to be able to combine several nexus microservices using a nexus gateway in some way that allows me to reuse my types from the microservices in the gateway code.
Same here. I got an apollo server with the old nexus schema (and sequelize) and building a new service with nexus framework (and prisma) and want to gradually move over the entire system, but its tightly coupled so as soon as I move one objectType over, it has fields that references other ObjectTypes and I keep moving down a rabbithole, so need schema stitching or federtion or something to allow all fields that aren't yet implemented to default back to the old service
Same here as well. It'd be nice to have a gradual migration path from SDL or graphql-js types. It seems like one option is to generate stub Nexus types for everything and merge it with graphql-tools
, but that's a rather brittle process.
Not sure if will address everything above, but see https://github.com/graphql-nexus/nexus/issues/148#issuecomment-747998067 for plans related to stitching code first schemas
I created a repo with a simple example here, hope it helps someone looking to get a feel of how it is done: https://github.com/nayaabkhan/nexus-stitching-example
Have needed to do this in a real world situation so I'm planning on adding first class support for better support of properly merging in types from an external schema.
See #983 which will offer better support for interop with an existing schema
As follow up, contrast, https://github.com/gmac/schema-stitching-handbook/tree/master/subservice-languages/javascript shows a schema stitching way of doing this where you annotate your nexus schema with directives to teach the gateway how to merge your types
Key difference is that the nexus schema is a “remote” schema in this example, ie set up as a separate micro service. I am not advocating for this; in my opinion, it is always better when possible to manage your schema as a monolith, except when you can’t. graphql-tools schema stitching is there for you when you can’t.
otherwise, you are probably best off with schema merging. graphql tools has utility functions for that, too, but definitely makes sense to take advantage of any local framework capabilities like described above now in nexus
Check out 1.2.0-next.14
/ #983 for better support here, idea being that the local Nexus constructed schema definitions can consume & merge with an external schema, with configuration options for merging specific types, and/or omitting individual types / fields /args.
If have multiple schemas, you'll need to merge into a single schema before consuming using something like graphql-tools
wrap
/ merge
, and even if you don't have multiple schemas you'll probably want to use delegation to issue queries against the remote schema.
Would love to get any feedback on it, I'm planning on using it for a real world use case with Cypress and will continue to refine the API as needed, so consider it potentially experimental/subject to change.
are there any docs ? I would like to test it out!