HandbookArchitecturePublic and private APIs

Public & private APIs

This example demonstrates filtering unwanted elements from the public schema, and serving public and private versions of the stitched schema for internal and external use.

This example demonstrates:

  • Filtering unwanted fields from the final stitched schema.
  • Serving public (filtered) and private (unfiltered) API versions.

Sandbox

⬇️ Click ☰ to see the files

You can also see the project on GitHub here.

The following services are available for interactive queries:

  • Public (filtered) gateway: listening on 4000/public/graphql
  • Private (unfiltered) gateway: listening on 4000/private/graphql

For simplicity, all subservices in this example are run locally by the gateway server. You could easily break out any subservice into a standalone remote server following the combining local and remote schemas example.

Summary

The last step in composing a clean and elegant gateway schema is to remove internal implementation details that should not be made available to the general public. You’ll notice that the private gateway includes all artifacts of our original stitched schema, including underscored service names such as _sdl, _users and _products, and the underscored argument _scope. By constrast, the public gateway has removed these. This is quite simple using filterSchema and pruneSchema helpers from GraphQL Tools utils:

import { filterSchema, pruneSchema } from '@graphql-tools/utils'
 
const privateSchema = makeGatewaySchema()
const publicSchema = pruneSchema(
  filterSchema({
    schema: privateSchema,
    rootFieldFilter: (type, fieldName) => !fieldName.startsWith('_'),
    fieldFilter: (type, fieldName) => !fieldName.startsWith('_'),
    argumentFilter: (typeName, fieldName, argName) => !argName.startsWith('_')
  })
)

Filtering a schema will remove unwanted elements, and pruning it will cleanup orphans. This results in two versions of our schema: the complete original schema, and the groomed public schema. Each of these schemas may be served at their own endpoint—this gives public consumers one API with access limitations, while the complete set of fields remain available for internal purposes at another location:

export const gatewayApp = createRouter()
 
gatewayApp.all(
  '/private/graphql',
  createYoga({
    schema: privateSchema,
    maskedErrors: false,
    graphqlEndpoint: '/private/graphql',
    graphiql: {
      title: 'Private API'
    }
  })
)
 
gatewayApp.all(
  '/public/graphql',
  createYoga({
    schema: publicSchema,
    maskedErrors: false,
    graphqlEndpoint: '/public/graphql',
    graphiql: {
      title: 'Public API'
    }
  })
)