redwoodjs / redwood

The App Framework for Startups
https://redwoodjs.com
MIT License
17.15k stars 980 forks source link

RFC: Support Public vs App GraphQL Schema #3151

Open dthyresson opened 3 years ago

dthyresson commented 3 years ago

The https://github.com/n1ru4l/graphql-public-schema-filter library can filter schemas to expose just a portion of the schema to be public.

This would be useful have a public API that was limited to just a few types/models and a few queries.

But, you would not have to declare and define a separate SDL -- but annotate the SDL with what is @public.

However, there is no easy way to add other types which this library needs to do into the process of making the executing and merged schema.

When creating the GraphQL handler in the app as:

export const handler = createGraphQLHandler({
  loggerConfig: { logger, options: {} },
  schema: makeMergedSchema({
    schemas,
    services: makeServices({ services }),
  }),
  onException: () => {
    // Disconnect from your database with an unhandled exception.
    db.$disconnect()
  },
})

the makeMergedSchema will merge in the types: the root schema (with redwood, etc) and the app's defined schema types:

https://github.com/redwoodjs/redwood/blob/d3b05e31efa5ab369f2b2f49db5d51af8a93286a/packages/graphql-server/src/makeMergedSchema/makeMergedSchema.ts#L205

  const typeDefs = mergeTypes(
    [rootSchema.schema, ...Object.values(schemas).map(({ schema }) => schema)],
    { all: true }
  )

  const schema = makeExecutableSchema({
    typeDefs,
    schemaDirectives,
    ...schemaOptions,
  })

and that is the executable schema.

But, there is a case where we might want to in other to filer the schema using graphql-public-schema-filter" see: https://github.com/n1ru4l/graphql-public-schema-filter

import { makeExecutableSchema } from "@graphql-tools/schema";
import {
  publicDirectiveSDL,
  buildPublicSchema,
} from "@n1ru4l/graphql-public-schema-filter";

const source = /* GraphQL */ `
  type Query {
    hello: String @public
    secret: String
  }
`;

const privateSchema = makeExecutableSchema({
  typeDefs: [publicDirectiveSDL, source],
});
const publicSchema = buildPublicSchema({ schema: privateSchema });
// serve privateSchema or publicSchema based on the request :)

So, we might want to all passing in types.

Or ...

RedwoodJS incorporates this library and has a makeMergedPublicSchema and that could be used by a different Serverless function like api instead of graphql.

Also, I am not sure if the output of makeExecutableSchema can be used as the schema here

const privateSchema = makeExecutableSchema({
  typeDefs: [publicDirectiveSDL, schema],
});
const publicSchema = buildPublicSchema({ schema: privateSchema });

If so, then perhaps this is not needed as part of the framework.

tubbo commented 2 years ago

Having multiple schemas would violate the one graph principle, and thus make your API a bit more difficult to handle if it ever needs to scale out. But I really like the idea of using @public to mark parts of your graph as public-facing, because it allows introspection of the graph in production.

Instead of having multiple schemas and splitting them by usage of this directive, could graphql-public-schema-filter be used to pick out the public API for use in graphiql and introspection queries? This way, one could define a single graph for their organization, but mark parts of it as public-facing. Users with graphql clients could connect and introspect the API for documentation and such. While the entire schema would technically be accessible to anyone, you at least wouldn't be allowing introspection of your private APIs, so this additional security risk is mitigated.

All of this, of course, would be opt-in, since public introspection without limits can cause performance issues, and it adds more security risk. But it would be really neat to be able to point more technical users to a GUI at my.app/graphql where they can mess with the public graph and view documentation.