graphql icon indicating copy to clipboard operation
graphql copied to clipboard

Generate graphql operations (code first)

Open radarsu opened this issue 4 years ago • 6 comments

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`,
    ...
}),

radarsu avatar Mar 18 '20 22:03 radarsu

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?

Nightbr avatar Dec 01 '20 09:12 Nightbr

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 avatar Dec 03 '20 19:12 Zippersk

@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

radarsu avatar Dec 04 '20 09:12 radarsu

@rradarsu The script above works great, however it doesn't generated fragments from what I can tell.

deviant32 avatar Jan 24 '22 19:01 deviant32

Without this code-first seems incomplete.

I think if there are no plans to add this feature any time soon, this issue should be reopened https://github.com/nestjs/graphql/issues/937

Unless there is some other non-hacky workaround for this.

mchaliadzinau avatar Nov 10 '23 16:11 mchaliadzinau

@mchaliadzinau this feature doesn't seem to be required for the code-first approach

kamilmysliwiec avatar Nov 13 '23 08:11 kamilmysliwiec