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

graphql-compose-modules

v3.0.1

Published

A toolkit for construction GraphQL Schema via file structure

Downloads

6

Readme

graphql-compose-modules

npm Codecov coverage Github Actions Trends Commitizen friendly TypeScript compatible Backers on Open Collective Sponsors on Open Collective

This is a toolkit for creating big GraphQL schemas with code-first approach in JavaScript.

Quick demo

You may find a simple GraphQL server example in the following folder: examples/simple.

GraphQL schema entrypoints from a file structure

When you are using code-first approach in GraphQL Schema construction you may face problem when you cannot understand which entrypoints your schema has. And where exactly the code is placed which serve this or that entrypoint.

overview

graphql-compose-modules uses a file-system based schema entrypoint definition (something like NextJS does with its pages concept for routing). You just create folder schema/ and put inside it the following sub-folders (root directories): query, mutation and subscription. Inside these folders you may put .js or .ts files with FieldConfigs which describes entrypoints. Assume you create the following directory structure:

schema/
  query/
    articleById.ts
    articlesList.ts
  mutation/
    createArticle.ts
    updateArticle.ts
    removeArticle.ts
  subscription/
    onArticleChange.ts

With this directory structure graphql-compose-modules will use file names as field names for your root types and you get the following GraphQL schema:

type Query {
  articleById: ...
  articlesList: ...
}

type Mutation {
  createArticle: ...
  updateArticle: ...
  removeArticle: ...
}

type Subscription {
  onArticleChange: ...
}

If you want rename field articlesList to articles in your schema just rename articlesList.ts file. If you want to add a new field to Schema – just add a new file to Query, Mutation, Subscription folders. This simple approach helps you understand entrypoints of your schema without launching the GraphQL server – what you see in folders that you get in GraphQL Schema.

Describing Entrypoints in files

Every Entrypoint (FieldConfig definition) is described in a separate file. This file contains information about input args, output type, resolve function and additional fields like description, deprecationReason, extensions. As an example let's create schema/Query/sum.ts and put inside the following content:

export default {
  type: 'Int!',
  args: {
    a: 'Int!',
    b: 'Int!',
  },
  resolve: (source, args, context, info) => {
    return args.a + args.b;
  },
  description: 'This method sums two numbers',
  deprecationReason: 'This method is deprecated and will be removed soon.',
  extensions: {
    someExtraParam: 'Can be used for AST transformers',
  },
};

If you are familiar with graphql-js FieldConfig definition then you may notice that type & args properties are defined in SDL format. This syntax sugar is provided by graphql-compose package.

Entrypoints with namespaces for big schemas

If your GraphQL Schema has a lot of entrypoints you may create sub-folders for grouping them under Namespaces:

schema/
  query/
    articles/
      byId.ts
      list.ts
    ...
  mutation/
    articles/
      create.ts
      update.ts
      remove.ts
    ...

With such structure you will get the following schema – namespace types QueryArticles & MutationArticles are created automatically:

type Query {
  articles: QueryArticles
}

type Mutation {
  articles: MutationArticles
}

type QueryArticles {
  byId: ...
  list: ...
}

type MutationArticles {
  create: ...
  update: ...
  remove: ...
}

You may use namespaces (sub-folders) for Query & Mutation and all servers supports this feature. But for Subscription most current server implementations (eg. apollo-server) does not support this yet.

GraphQLSchema construction

In schema folder create a file index.ts with the following content which traverses query, mutation, subscription folders and creates a GraphQLSchema instance for you:

import { buildSchema } from 'graphql-compose-modules';

export const schema = buildSchema(module);

After that you may create a GraphQL server:

import { ApolloServer } from 'apollo-server';
import { schema } from './schema';

const server = new ApolloServer({ schema });

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

Advanced GraphQLSchema construction

If you want transform AST of entrypoints (e.g. for adding authorization, logging, tracing) and for merging with another schemas distributed via npm packages – you may use the following advanced way for schema construction:

import { directoryToAst, astToSchema, astMerge } from 'graphql-compose-modules';
import { addQueryToMutations } from './transformers/addQueryToMutations';
import { remoteServiceAST } from '@internal/some-service';

// traverse `query`, `mutation`, `subscription` folders placed near this module
let ast = directoryToAst(module);

// apply transformer which uses astVisitor() method under the hood
addQueryToMutations(ast);

// merge with other ASTs distributed via npm packages
ast = astMerge(ast, remoteServiceAST);

// construct SchemaComposer
const sc = astToSchema(ast);

// construct GraphQLSchema instance and export it
export const schema = sc.buildSchema();

Writing own transformer for entrypoints

For writing your own transformers you need to use astVisitor() method. For instance let's implement addQueryToMutations transformer which adds query: Query field to all your mutations:

import { astVisitor, VISITOR_SKIP_CHILDREN, AstRootNode } from 'graphql-compose-modules';
import { ObjectTypeComposer, SchemaComposer } from 'graphql-compose';

export function addQueryToMutations(
  ast: AstRootNode,
  schemaComposer: SchemaComposer<any>
): void {
  astVisitor(ast, schemaComposer, {
    // skip `query` & `subscriptions` root types
    ROOT_TYPE: (info) => {
      if (!info.isMutation) {
        return VISITOR_SKIP_CHILDREN;
      }
      return;
    },
    // for every file in `mutation` folder try to add `query` field if it does not exists
    FILE: (info) => {
      // get FieldConfig from loaded file in `schema` folder
      const fieldConfig = info.fieldConfig || {};

      // if `resolve` method does not exist then skip this transformation
      const next = fieldConfig.resolve;
      if (!next) return;

      // if output type isn't an object then skip this transformation
      if (!info.isOutputTypeIsObject()) return;

      const outputTC = info.getOutputUnwrappedOTC();
      if (outputTC.hasField('query')) return;
      outputTC.setField('query', {
        description: 'Sub-query which have to be executed after mutation.',
        type: schemaComposer.Query,
      });

      fieldConfig.resolve = async (s: any, args: any, context: any, i: any) => {
        const result = await next(s, args, context, i);
        return {
          query: {},
          ...result,
        };
      };
    },
  });
}

API

Main API method:

  • buildSchema(module: NodeModule, opts: BuildOptions): GraphQLSchema – use this method for creating graphql schema from directory

Advanced API methods:

The following methods help to use schema composition, applying middlewares and schema transformation via visitor pattern:

overview

  • directoryToAst(module: NodeModule, options: DirectoryToAstOptions): AstRootNode – traverses directories and construct AST for your graphql entrypoints
  • astToSchema(ast: AstRootNode, opts: AstToSchemaOptions): SchemaComposer – converts AST to GraphQL Schema
  • astMerge(...asts: Array<AstRootNode>): AstRootNode – combines several ASTs to one AST (helps compose several graphql schemas which may be distributed via npm packages)
  • astVisitor(ast: AstRootNode, schemaComposer: SchemaComposer, visitor: AstVisitor): void – modify AST via visitor pattern. This method is used for construction of your AST transformers.