Skip to content Skip to sidebar Skip to footer

How To Split A Long Graphql Schema

I am trying to create a Schema, however is going to get too long and confusing, what are the best practices to split the different queries, mutations and inputs so I can just requ

Solution 1:

There are multiple options, here are three of them:

  1. You can take a look at Apollo's blog - they show a way to modularize the schema: Modularizing your GraphQL schema code If you want an example, you can take a look at this github repository

  2. Of course you can always just use the template literals to embed parts of the schema as strings:

const countryType = `
type Country {
  _id: ID!
  name: String!
  region: [Region!]!
}
`const regionType = `
type Region {
  _id: ID!
  name: String!
  countries: [Country!]
}
`const schema = `
${countryType}${regionType}

# ... more stuff ...
`module.exports = buildSchema(schema);
  1. Another way is to use the code first approach and write the schemas in javascript instead of graphql schema language.

Solution 2:

Make it separate folder and structure as well to make codes maintainable I do the following:

GraphQL Example Repository

File Structure Screenshot

const express = require('express');
const glob = require("glob");
const {graphqlHTTP} = require('express-graphql');
const {makeExecutableSchema, mergeResolvers, mergeTypeDefs} = require('graphql-tools');
const app = express();
//iterate through resolvers file in the folder "graphql/folder/folder/whatever*-resolver.js"let resolvers = glob.sync('graphql/*/*/*-resolver.js')
let registerResolvers = [];
for (const resolver of resolvers){
// add resolvers to array
    registerResolvers = [...registerResolvers, require('./'+resolver),]
}
//iterate through resolvers file in the folder "graphql/folder/folder/whatever*-type.js"let types = glob.sync('graphql/*/*/*-type.js')
let registerTypes = [];
for (const type of types){
// add types to array
    registerTypes = [...registerTypes, require('./'+type),]
}
//make schema from typeDefs and Resolvers with "graphql-tool package (makeExecutableSchema)"const schema = makeExecutableSchema({
    typeDefs: mergeTypeDefs(registerTypes),//merge array typesresolvers: mergeResolvers(registerResolvers,)//merge resolver type
})
// mongodb connection if you prefer mongodbrequire('./helpers/connection');
// end mongodb connection//Make it work with express "express and express-graphql packages"
app.use('/graphql', graphqlHTTP({
    schema: schema,
    graphiql: true,//test your query or mutation on browser (Development Only)
}));
app.listen(4000);
console.log('Running a GraphQL API server at http://localhost:4000/graphql');

Solution 3:

You could use Type definitions (SDL) merging of graphql-tools as well.

This tools merged GraphQL type definitions and schema. It aims to merge all possible types, interfaces, enums and unions, without conflicts.

The doc introduces multiple ways to merge SDL.

I made an example of modularizing-the-schema using apollo-server tech stack.

Solution 4:

In typescript, you can put your graphQL schemas in file that you read from:

import { readFileSync } from"fs";
import path from"path";

functiongetTypeDefs(filename: string) {
    returngql(readFileSync(path.join(process.cwd(), 'resources', filename), 'utf8'));
}

Having a file structure looking like:

.
├── src
│   └── server.ts
└── resources
    ├── other.graphql
    └── schema.graphql

You can pass multiple gql in an array like:

import { ApolloServer, gql } from"apollo-server-express";
import { makeExecutableSchema } from"graphql-tools";

const server = newApolloServer({
    schema: makeExecutableSchema({
        typeDefs: [getTypeDefs('schema.graphql'), getTypeDefs('other.graphql')],
        resolvers: YourResolvers
    })
});

Solution 5:

the best way follow my code s

i m use npm typescripter express starter

exportinterface IGraphqlSchema {
  type?: string,
  query?: string,
  input?: string,
  mutation?: string,


}


exportconst sharedGraphqlSchema = {
  scalar: `
      scalar Void
      scalar Any
 `,

  rootSchema:
    `
schema {
query: RootQuery
mutation: RootMutation
}`
  ,
  rootQuery: "\ntype RootQuery {",

  rootMutation: "\ntype RootMutation {",


};

import { IGraphqlSchema } from"core/interfaces/graphql.schema";
import { sharedGraphqlSchema } from"./shared.graphql.schema";


exportfunctioncombineSchemaGraphql(schemas: IGraphqlSchema[]): string {

  letcombine: string = "";
  combine = combine.concat(sharedGraphqlSchema.scalar);
  lettemp: string = "";


  for (let index = 0; index < 4; index++) {
    for (let j = 0; j < schemas.length; j++) {
      const item = schemas[j];
      switch (index) {
        case0:
          combine = combine.concat(item.type);
          break;
        case1:
          combine = combine.concat(item.input);
          break;
        case2:
          temp = temp.concat(item.query);

          break;

        default:
          temp = temp.concat(item.mutation);
          break;
      }

    }

    if (index == 2) {
      combine = combine.concat(sharedGraphqlSchema.rootQuery);
      combine = combine.concat(temp);
      combine = combine.concat("\n}");
      temp = "";
    }
    if (index == 3) {
      combine = combine.concat(sharedGraphqlSchema.rootMutation);
      combine = combine.concat(temp);
      combine = combine.concat("\n}");
    }

  }

  combine = combine.concat(sharedGraphqlSchema.rootSchema);

  return combine;
}


import { IGraphqlSchema } from"../../../core/interfaces/graphql.schema";export const profileSchema:IGraphqlSchema= {

  type:`typeProfile {
      _id:ID!firstName:StringlastName:StringuserName:Stringpassword:Stringemail:Stringphone:Stringimage:Stringaddress:Stringgender:Booleancountry:Stringcity:Stringactive:Booleanstatus:BooleanstatusMessage:StringcreatedAt:StringupdatedAt:StringdeletedAt:String
    }
   `,
  input:`inputProfileInputData {
         image:StringfirstName:StringlastName:Stringcountry:Stringcity:Stringpassword:StringpassConfirm:String
   }`,
  query:`profile:Profile!`,
  mutation:`profileUpdate(inputs:ProfileInputData):Void`
};
import { NextFunction, Request, Response } from"express";
import { StatusCodes } from"http-status-codes";
import { defaultas i18n } from"i18next";
import { RequestWithUser } from"../../auth/interfaces/reqeust.with.user.interface";
importProfileServicefrom"../services/profile.service";
import { UserEntity } from"../entities/user.entity";
import { IUser } from"../../auth/interfaces/user.interface";
import { isEmpty } from"./../../shared/utils/is.empty";
import { IMulterFile } from"./../../shared/interfaces/multer.file.interface";
import { optimizeImage } from"./../../shared/utils/optimize.image";
import { commonConfig } from"./../../common/configs/common.config";
import { IUserLogIn } from"@/modules/auth/interfaces/Log.in.interface";
import { ProfileValidation } from"@/modules/common/validations/profile.validation";
import { HttpException } from"@/core/exceptions/HttpException";
import { validateOrReject, Validator } from"class-validator";
import { warpValidationError } from"@/core/utils/validator.checker";
import { sharedConfig } from"@/modules/shared/configs/shared.config";

exportconstProfileResolver = {

  profile: asyncfunction({ inputs }, req: RequestWithUser): Promise<void | any> {


    constuser: IUserLogIn = req.user;

    const profileService = newProfileService();
    constfindOneData: IUser = await profileService.show(user._id);

    return {
      ...findOneData._doc
    };

  },


  profileUpdate: asyncfunction({ inputs }, req: RequestWithUser): Promise<void | Object> {

    const profileValidation = newProfileValidation(inputs);

    try {
      awaitvalidateOrReject(profileValidation);

    } catch (e) {
      warpValidationError(e);
    }

    constuser: IUserLogIn = req.user;
    const userEntity = newUserEntity(inputs);
    await userEntity.updateNow().generatePasswordHash();
    const profileService = newProfileService();
    if (!isEmpty(req.file)) {

      constfile: IMulterFile = req.file;
      // userEntity.image = commonConfig.profileDirectory + file.filename;
      userEntity.image = sharedConfig.publicRoot + file.filename;
      awaitoptimizeImage(file.destination + file.filename, 200, 200, 60);
    }

    constupdateData: IUser = await profileService.update(user._id, userEntity);



  }


};



import { settingSchema } from"@/modules/common/schemas/setting.schema";

process.env["NODE_CONFIG_DIR"] = __dirname + "/core/configs";
import"dotenv/config";
importAppfrom"./app";
import { merge } from"lodash";
import { combineSchemaGraphql } from"./core/utils/merge.graphql.type";
import { authSchema } from"@/modules/auth/schemas/auth.schema";
import { profileSchema } from"@/modules/common/schemas/profile.schema";
import { AuthResolver } from"@/modules/auth/resolvers/auth.resolver";
import { ProfileResolver } from"@/modules/common/resolvers/profile.resolver";
import { SettingResolver } from"@/modules/common/resolvers/setting.resolver";
import { sharedSchema } from"@/modules/shared/schemas/shared.schema";

const rootQuery = combineSchemaGraphql(
  [
    sharedSchema,
    authSchema,
    profileSchema,
    settingSchema]);

const mutation = merge(
  AuthResolver,
  ProfileResolver,
  SettingResolver);

const app = newApp(rootQuery, mutation);
app.listen();



import { AppInterface } from "./core/interfaces/app.interface";

process.env["NODE_CONFIG_DIR"] = __dirname + "/core/configs";

import compression from "compression";
import cookieParser from "cookie-parser";
import config from "config";
import express from "express";
import helmet from "helmet";
import hpp from "hpp";
import morgan from "morgan";
import { connect, set } from "mongoose";
import swaggerJSDoc from "swagger-jsdoc";
import swaggerUi from "swagger-ui-express";
import * as path from "path";
import * as fs from "fs";
import i18nMiddleware from "i18next-express-middleware";
import { default as i18n } from "i18next";
import Backend from "i18next-node-fs-backend";
import { LanguageDetector } from "i18next-express-middleware";
import { dbConnection } from "./core/databases/database.config";
import errorMiddleware from "./core/middlewares/error.middleware";
import { logger, stream } from "./core/utils/logger";
import contentNegotiationMiddleware from "./modules/common/middlewares/content.negotiation.middleware";
import corsMiddleware from "./modules/common/middlewares/cors.middleware";
import userAgent from "express-useragent";
import { graphqlHTTP } from "express-graphql";
import { buildSchema } from "graphql";
import authMiddleware from "@/modules/auth/middlewares/auth.middleware";
import multer from "multer";
import { multerFunctions, multerFileFilter } from "@/modules/shared/utils/multer.functions";
import { sharedConfig } from "@/modules/shared/configs/shared.config";

classAppimplementsAppInterface{
  public app: express.Application;
  public port: string | number;
  public env: string;

  constructor(schema: string, resolver: object) {
    this.app = express();
    this.port = process.env.PORT || 4000;
    this.env = process.env.NODE_ENV || "development";
    this.initializeI18n();
    this.connectToDatabase();
    this.initializeMiddlewares();
    this.initializeSwagger();
    this.initializeErrorHandling();
    this.initGraphql(schema, resolver);

  }

  public listen(): void {
    this.app.listen(this.port, () => {
      logger.info(`====  typescript express.js modular graphql  kick starter ====`);
      logger.info(`===== by yasin palizban ===== `);
      logger.info(`https://github.com/yasinpalizban`);
      logger.info(`======= ENV: ${this.env} =======`);
      logger.info(`🚀 App listening on the port ${this.port}`);

    });
  }

  public getServer(): express.Application {
    returnthis.app;
  }

  private connectToDatabase(): void {
    if (this.env !== "production") {
      set("debug", true);
    }

    connect(dbConnection.url, dbConnection.options);

  }

  private initializeMiddlewares(): void {
    this.app.use(morgan(config.get("log.format"), { stream }));
    this.app.use(corsMiddleware);
    this.app.use(hpp());
    this.app.use(helmet());
    this.app.use(compression());
    this.app.use(express.json());
    this.app.use(express.urlencoded({ extended: true }));
    this.app.use(cookieParser());
    this.app.use(userAgent.express());

    this.app.use("/public", express.static(path.join(__dirname, "public")));
    this.app.use(authMiddleware);

    this.app.use(contentNegotiationMiddleware);


    const storage = multer.diskStorage({
      destination: sharedConfig.publicRoot,
      filename: multerFunctions
    });
    const maxSize = 4 * 1000 * 1000;
    const upload = multer({ storage: storage, fileFilter: multerFileFilter, limits: { fileSize: maxSize } });
    this.app.use(upload.array("image"));

  }


  private initializeSwagger(): void {
    const options = {
      swaggerDefinition: {
        info: {
          title: "REST API",
          version: "1.0.0",
          description: "Example docs"
        }
      },
      apis: ["swagger.yaml"]
    };

    const specs = swaggerJSDoc(options);
    this.app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(specs));
  }


  private initializeI18n(): void {

    i18n
      .use(Backend)
      .use(LanguageDetector)
      .init({
        lng: "en",
        whitelist: ["en", "fa"],
        fallbackLng: "en",
        // have a common namespace used around the full app
        ns: ["translation"],
        debug: false,
        backend: {
          loadPath: path.join(__dirname + "/locales/{{lng}}/{{ns}}.json")
          // jsonIndent: 2
        },
        preload: ["en", "fa"]
      });
    this.app.use(i18nMiddleware.handle(i18n));

  }

  private initializeErrorHandling(): void {
    this.app.use(errorMiddleware);
  }

  private initGraphql(schema: string, resolver: object): void {

    this.app.use(
      "/graphql",
      graphqlHTTP({
        schema: buildSchema(schema),
        rootValue: resolver,
        graphiql: true,
        customFormatErrorFn: (error) => ({
          message: error.message,
          locations: error.locations,
          stack: error.stack ? error.stack.split('\n') : [],
          path: error.path,
        })
      //  customFormatErrorFn(err) {// if (!err.originalError) {//   return err;// }//// // @ts-ignore// const data = err.originalError.data;// const message = err.message || "An error occurred.";// // @ts-ignore// const code = err.originalError.status || 500;//// return { message: message, status: code, data: data };//  }
      })
    );
  }

}

export default App;


your well come

Post a Comment for "How To Split A Long Graphql Schema"