npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@n1ru4l/graphql-public-schema-filter

v2.0.0

Published

Filter your GraphQL schema into a public schema.

Downloads

2,124

Readme

@n1ru4l/graphql-public-schema-filter

This library allows filtering an existing GraphQL schema down into a subset of the original schema. It supports both the code first development flow of building GraphQL schemas (via GraphQL extension fields) and the SDL first development flow (via schema directives).

The implementation is smart and warns the user if the processed annotations would result in an invalid schema such as:

  • Object Type without fields
  • Field whose type is not part of the schema

If such a scenario is encountered the implementation will propagate and hide all fields/types that use types that are not marked as public or that would be invalid.

Why would you need this?

As I have been building GraphQL APIs I often had the need to have both a private and public API.

The private API is used for in-house products. Breaking changes can and will occur. It also includes types and fields specific to in-house application built around the API. The public API, however, is used by individuals not part of our organization. We cannot simply roll out breaking changes on the GraphQL API for those. Furthermore, they should only have access to a subset of the whole GraphQL graph. By generating a subgraph out of the internal graph we can hide stuff, without having to maintain and build two GraphQL schema.

Install instructions

This library requires graphql as a peer dependency and has a runtime dependency on @graphql-tools/utils.

yarn add -E @n1ru4l/graphql-public-schema-filter

Usage Instructions

This library is designed to be inclusive for anyone within the GraphQL.js ecosystem. It supports both the SDL makeExecutableSchema and code-first via extension fields flow.

There is no delegation or validation overhead when executing against the newly generated schema. It is highly recommended to built the public schema during server-startup and not on the fly during incoming requests.

Code-First

Annotate types and fields that should be public with the isPublic extension.

import { GraphQLObjectType, GraphQLString } from "graphql";
import { buildPublicSchema } from "@n1ru4l/graphql-public-schema-filter";

const GraphQLQueryType = new GraphQLObjectType({
  name: "Query",
  fields: {
    hello: {
      type: GraphQLString,
      resolve: () => "hi",
      extensions: {
        isPublic: true,
      },
    },
    secret: {
      type: GraphQLString,
      resolve: () => "sup",
    },
  },
});

const privateSchema = new GraphQLSchema({
  query: GraphQLQueryType,
});
const publicSchema = buildPublicSchema({ schema: privateSchema });
// serve privateSchema or publicSchema based on the request :)

You can also find this example within examples/src/schema.ts.

SDL-First

Instead of using the extension fields, we use the @public directive.

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 :)

FAQ

Why isPublic and @public over isPrivate and @private?

Deny-listing is more prone to errors than allow-listing. By adding a directive/extension field we explicitly set something to public. The other way around it is easier to forgot to add a @private/ isPrivate annotation, which would automatically result in the new fields being public. Being verbose about what fields are public is the safest way.

Why is the no more granular control that allows building multiple unique public schemas?

I considered this at the beginning, but in practice we never had a use for this. Having multiple public schemas requires maintaining a lot of documentation. In our use-case we only have a public and a private schema. There is still role based access for the public schema. certain users are not allowed to select specific fields. Instead of hiding those fields for those users we instead deny operations that select fields the users are not allowed to select before even executing it with the envelop useOperationFieldPermissions plugin.

You can overwrite the isPublic function which is used to determine whether a field or type is public based on directives and extensions. This allows to fully customize the behavior based on your needs.

type SharedExtensionAndDirectiveInformation = {
  extensions?: Maybe<{
    [attributeName: string]: any;
  }>;
  astNode?: Maybe<
    Readonly<{
      directives?: ReadonlyArray<DirectiveNode>;
    }>
  >;
};

export const defaultIsPublic = (
  input: SharedExtensionAndDirectiveInformation
): boolean =>
  input.extensions?.["isPublic"] === true ||
  !!input.astNode?.directives?.find(
    (directive) => directive.name.value === "public"
  );

License

MIT