Graphql: Generate graphql operations (code first)

Created on 18 Mar 2020  路  3Comments  路  Source: nestjs/graphql

Current behavior and motivation

We can generate schema.graphql which is great.
Although in order to generate Angular sdk library to better communicate with NestJS all of the code-generator tools I've found are much like https://github.com/dotansimha/graphql-code-generator - they require operations graphql schema files which unfortunatelly have to be created manually, i.e.:

query someQuery {
    someQuery {
        result
    }
}

That leads to redundancy, errors and slower development process.

Expected behavior

Add functionality to generate optional operations.graphql file with graphql queries and mutations instead of inputs and types (which are in schema.graphql).

GraphQLModule.forRoot({
    ...
    autoSchemaFile: `schema.graphql`,
    // That would solve all the world's problems.
    autoOperationsSchemaFile: `operations.graphql`,
    ...
}),
enhancement

Most helpful comment

@Zippersk very cool, worked for me for angular with configuration as below:

schema: 'http://localhost:3000/graphql'
overwrite: true
documents:
    - 'http://localhost:3000/graphql':
          loader: ./operations-from-schema.generator.js
generates:
    ./sdk.ts:
        plugins:
            - 'typescript'
            - 'typescript-operations'
            - 'typescript-apollo-angular':
                  sdkClass: true
                  serviceName: Api

And this is my operations-from-schema.graphql.ts file (refactored to TypeScript, for in safety, post -> get etc. and I use got library over axios):

import { buildClientSchema, getIntrospectionQuery, parse, print } from 'graphql';

import { buildOperationNodeForField } from '@graphql-tools/utils';
import got from 'got';

const getSchemaFromUrl = async (url: string) => {
    const searchParams = {
        query: getIntrospectionQuery().toString(),
    };

    const response = await got.get(url, {
        searchParams,
        responseType: `json`,
    });

    const { data } = response.body as any;
    return buildClientSchema(data);
};

const main = async (schemaUrl: string) => {
    const schema = await getSchemaFromUrl(schemaUrl);
    const operationsDictionary = {
        query: { ...(schema.getQueryType()?.getFields() ?? {}) },
        mutation: { ...(schema.getMutationType()?.getFields() ?? {}) },
        subscription: { ...(schema.getSubscriptionType()?.getFields() ?? {}) },
    };

    let documentString = ``;

    for (const [operationKind, operationValue] of Object.entries(operationsDictionary)) {
        for (const operationName of Object.keys(operationValue)) {
            const operationAST = buildOperationNodeForField({
                schema,
                kind: operationKind as any,
                field: operationName,
            });

            documentString += print(operationAST);
        }
    }

    return parse(documentString);
};

export default main;

Usage

npx tsc ./operations-from-schema.generator.ts && npx graphql-codegen

All 3 comments

Hey, same here for React App, we would like to generate GraphQL operation from code first resolver (with annotation) but seems not included to GqlModuleOptions so far. Any idea on this?

I wrote a custom document loader for graphql-code-generator which will automatically generate operations from graphql schema.

This is my codegen.yml

schema: "http://localhost:3000/graphql"
overwrite: true
documents:
  - "http://localhost:3000/graphql":
      loader: ./operationsFromSchemaGenerator.js
generates:
  src/generated/graphql.ts:
    plugins:
      - typescript
      - typescript-operations
      - typescript-vue-apollo
    config:
      withCompositionFunctions: true
      vueCompositionApiImportFrom: vue
      dedupeOperationSuffix: true

and this is operationsFromSchemaGenerator.js:

const {
  getIntrospectionQuery,
  parse,
  buildClientSchema,
  print,
} = require("graphql");
const { buildOperationNodeForField } = require("@graphql-tools/utils");
const axios = require("axios").default;

async function getSchemaFromUrl(url) {
  const response = await axios
    .post(url, { query: getIntrospectionQuery().toString() })
    .catch((e) => console.log(e));

  return buildClientSchema(response.data.data);
}

module.exports = async function(schemaUrl) {
  const schema = await getSchemaFromUrl(schemaUrl);
  const operationsDictionary = {
    query: { ...(schema.getQueryType()?.getFields() || {}) },
    mutation: { ...(schema.getMutationType()?.getFields() || {}) },
    subscription: { ...(schema.getSubscriptionType()?.getFields() || {}) },
  };

  let documentString = "";
  for (const operationKind in operationsDictionary) {
    for (const operationName in operationsDictionary[operationKind]) {
      const operationAST = buildOperationNodeForField({
        schema,
        kind: operationKind,
        field: operationName,
      });

      documentString += print(operationAST);
    }
  }

  return parse(documentString);
};

It is not fancy, but it works for my use-case. Feel free to customize it for your needs.

@Zippersk very cool, worked for me for angular with configuration as below:

schema: 'http://localhost:3000/graphql'
overwrite: true
documents:
    - 'http://localhost:3000/graphql':
          loader: ./operations-from-schema.generator.js
generates:
    ./sdk.ts:
        plugins:
            - 'typescript'
            - 'typescript-operations'
            - 'typescript-apollo-angular':
                  sdkClass: true
                  serviceName: Api

And this is my operations-from-schema.graphql.ts file (refactored to TypeScript, for in safety, post -> get etc. and I use got library over axios):

import { buildClientSchema, getIntrospectionQuery, parse, print } from 'graphql';

import { buildOperationNodeForField } from '@graphql-tools/utils';
import got from 'got';

const getSchemaFromUrl = async (url: string) => {
    const searchParams = {
        query: getIntrospectionQuery().toString(),
    };

    const response = await got.get(url, {
        searchParams,
        responseType: `json`,
    });

    const { data } = response.body as any;
    return buildClientSchema(data);
};

const main = async (schemaUrl: string) => {
    const schema = await getSchemaFromUrl(schemaUrl);
    const operationsDictionary = {
        query: { ...(schema.getQueryType()?.getFields() ?? {}) },
        mutation: { ...(schema.getMutationType()?.getFields() ?? {}) },
        subscription: { ...(schema.getSubscriptionType()?.getFields() ?? {}) },
    };

    let documentString = ``;

    for (const [operationKind, operationValue] of Object.entries(operationsDictionary)) {
        for (const operationName of Object.keys(operationValue)) {
            const operationAST = buildOperationNodeForField({
                schema,
                kind: operationKind as any,
                field: operationName,
            });

            documentString += print(operationAST);
        }
    }

    return parse(documentString);
};

export default main;

Usage

npx tsc ./operations-from-schema.generator.ts && npx graphql-codegen
Was this page helpful?
0 / 5 - 0 ratings

Related issues

willsoto picture willsoto  路  5Comments

danil-z picture danil-z  路  4Comments

JayAhn2 picture JayAhn2  路  3Comments

cschroeter picture cschroeter  路  3Comments

rkutca picture rkutca  路  3Comments