Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to do schema stitching with Nexus? #70

Open
otrebu opened this issue Mar 5, 2019 · 24 comments
Open

How to do schema stitching with Nexus? #70

otrebu opened this issue Mar 5, 2019 · 24 comments
Labels
type/docs Relates to knowledge transfer matter (refs, guides, tuts, examples, ...) type/feat Add a new capability or enhance an existing one

Comments

@otrebu
Copy link

otrebu commented Mar 5, 2019

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!

@pantharshit00
Copy link

pantharshit00 commented Mar 6, 2019

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
  ],
});

@otrebu
Copy link
Author

otrebu commented Mar 6, 2019

@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.

@tgriesser tgriesser added the type/docs Relates to knowledge transfer matter (refs, guides, tuts, examples, ...) label Mar 6, 2019
@tgriesser
Copy link
Member

@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.

@kandros
Copy link
Contributor

kandros commented Mar 8, 2019

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

@otrebu
Copy link
Author

otrebu commented Mar 8, 2019

@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.

@kandros
Copy link
Contributor

kandros commented Mar 8, 2019

@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

@otrebu
Copy link
Author

otrebu commented Mar 15, 2019

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.

@kandros
Copy link
Contributor

kandros commented Mar 15, 2019

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 😁

@otrebu
Copy link
Author

otrebu commented Mar 15, 2019

@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"
});

@otrebu
Copy link
Author

otrebu commented Mar 18, 2019

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.

@otrebu
Copy link
Author

otrebu commented Mar 19, 2019

@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.

@tgriesser
Copy link
Member

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")
            }
        ]
    }
});

@tgriesser
Copy link
Member

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.

@otrebu
Copy link
Author

otrebu commented Mar 20, 2019

@tgriesser thanks!

I see this topic being related to this from the nexus-prisma plugin: graphql-nexus/nexus-prisma#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!

@OliverEvans96
Copy link

OliverEvans96 commented Apr 29, 2020

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.

@cyrus-za
Copy link

cyrus-za commented Sep 8, 2020

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

@swac
Copy link

swac commented Dec 9, 2020

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.

@yaacovCR
Copy link

Not sure if will address everything above, but see #148 (comment) for plans related to stitching code first schemas

@nayaabkhan
Copy link

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

@tgriesser tgriesser added the type/feat Add a new capability or enhance an existing one label Sep 3, 2021
@tgriesser
Copy link
Member

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.

@tgriesser
Copy link
Member

See #983 which will offer better support for interop with an existing schema

@yaacovCR
Copy link

yaacovCR commented Sep 5, 2021

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

@tgriesser
Copy link
Member

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.

@m1cl
Copy link

m1cl commented Sep 27, 2021

are there any docs ? I would like to test it out!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type/docs Relates to knowledge transfer matter (refs, guides, tuts, examples, ...) type/feat Add a new capability or enhance an existing one
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants