Как разделить длинную схему GraphQL

Я пытаюсь создать схему, однако это будет слишком долго и запутанно, каковы наилучшие методы разделения различных запросов, мутаций и входных данных, чтобы я мог просто потребовать их и организовать их, чтобы их было легко читать.

Я пытался найти информацию в Интернете, но там вообще ничего не понятно, и я стараюсь не использовать Аполлон.

const { buildSchema } = require('graphql');

module.exports = buildSchema(`
type Region {
  _id: ID!
  name: String!
  countries: [Country!]
}

type Country {
  _id: ID!
  name: String!
  region: [Region!]!
}

type City {
  _id: ID!
  name: String!
  country: [Country!]!
}

type Attraction {
  _id: ID!
  name: String!
  price: Float!
  description: String!
  city: [City!]!
}

type Eatery {
  _id: ID!
  name: String!
  cuisine: String!
  priceRange: String!
  location: [Location!]!
  typeOfEatery: String!
  city: [City!]!
}

type Location {
  _id: ID!
  latitude: String!
  longitude: String!
  address: Float
}

type User {
  _id: ID!
  email: String!
  password: String!
}

type AuthData {
  userId: ID!
  token: String!
  tokenExpiration: String!
}

type RegionInput {
  name: String!
}

type CountryInput {
  name: String!
}

type CityInput {
  name: String!
}

type RootQuery {
  regions: [Region!]!
  countries: [Country!]!
  login(email: String!, password: String!): AuthData!
}

type RootMutation {
  createRegion(regionInput: RegionInput): Region
  createCountry(countryInput: CountryInput): Country
  createCity(cityInput: CityInput): City
}

schema {
  query: RootQuery
  mutation: RootMutation
}
`);

Мне нужно что-то очень организованное и позволяющее привести все в порядок и четко, объединить все файлы в один индекс - лучшее решение.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
17
0
11 005
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Ответ принят как подходящий

Вариантов несколько, вот три из них:

  1. Вы можете взглянуть на блог Аполлона — они показывают способ модульности схемы: Модульность кода схемы GraphQL Если вам нужен пример, вы можете взглянуть на этот репозиторий github

  2. Конечно, вы всегда можете просто использовать литералы шаблона для встраивания частей схемы в виде строк:

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. Другой способ — использовать подход «сначала код» и писать схемы на javascript вместо языка схем graphql.

Итак, я могу просто создать несколько файлов и правильно вставить их в индекс? Я рассмотрю Apollo в будущем, потому что сначала я научусь делать это без использования какой-либо другой библиотеки. Сначала мне нужно понять весь процесс, спасибо за вашу помощь!

Scarecrowsama 15.07.2019 15:36

Да, но мне нравится подход Аполлона

Liron Navon 15.07.2019 15:42

Убедитесь, что у вас есть хотя бы один тест, в котором вы вызываете «buildSchema», так как могут быть дубликаты, а «buildSchema» должна выдать вам ошибку, если какие-либо дубликаты существуют.

Liron Navon 15.07.2019 15:43

Сделайте его отдельной папкой и структурой, чтобы сделать коды удобными. Я делаю следующее:

Репозиторий примеров GraphQL

Скриншот файловой структуры

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 types
    resolvers: mergeResolvers(registerResolvers,)//merge resolver type
})
// mongodb connection if you prefer mongodb
require('./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.info('Running a GraphQL API server at http://localhost:4000/graphql');

Вы также можете использовать Слияние определений типов (SDL) из graphql-tools.

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

В документе представлены несколько способов объединения SDL.

Я сделал пример модульная схема, используя технический стек apollo-server.

В typescript вы можете поместить свои схемы graphQL в файл, из которого вы читаете:

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

function getTypeDefs(filename: string) {
    return gql(readFileSync(path.join(process.cwd(), 'resources', filename), 'utf8'));
}

Имея файловую структуру, похожую на:

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

Вы можете передать несколько gql в массиве, например:

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

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

лучший способ следовать моим кодам

я использую экспресс-стартер npm typescript

export interface IGraphqlSchema {
  type?: string,
  query?: string,
  input?: string,
  mutation?: string,


}


export const 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";


export function combineSchemaGraphql(schemas: IGraphqlSchema[]): string {

  let combine: string = "";
  combine = combine.concat(sharedGraphqlSchema.scalar);
  let temp: string = "";


  for (let index = 0; index < 4; index++) {
    for (let j = 0; j < schemas.length; j++) {
      const item = schemas[j];
      switch (index) {
        case 0:
          combine = combine.concat(item.type);
          break;
        case 1:
          combine = combine.concat(item.input);
          break;
        case 2:
          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:
    `
  type Profile {
      _id: ID!
     firstName: String
     lastName: String
     userName: String
     password: String
     email: String
     phone: String
     image: String
     address: String
     gender: Boolean
     country: String
     city: String
     active: Boolean
     status: Boolean
     statusMessage: String
     createdAt: String
     updatedAt: String
     deletedAt: String
    }
   `,
  input:
    `
     input ProfileInputData {
         image: String
        firstName: String
        lastName: String
        country: String
        city: String
        password: String
        passConfirm: String
   }`,
  query:
    `
     profile: Profile!
`,
  mutation: `
 profileUpdate(inputs: ProfileInputData): Void
 `
};


import { NextFunction, Request, Response } from "express";
import { StatusCodes } from "http-status-codes";
import { default as i18n } from "i18next";
import { RequestWithUser } from "../../auth/interfaces/reqeust.with.user.interface";
import ProfileService from "../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";

export const ProfileResolver = {

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


    const user: IUserLogIn = req.user;

    const profileService = new ProfileService();
    const findOneData: IUser = await profileService.show(user._id);

    return {
      ...findOneData._doc
    };

  },


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

    const profileValidation = new ProfileValidation(inputs);

    try {
      await validateOrReject(profileValidation);

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

    const user: IUserLogIn = req.user;
    const userEntity = new UserEntity(inputs);
    await userEntity.updateNow().generatePasswordHash();
    const profileService = new ProfileService();
    if (!isEmpty(req.file)) {

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

    const updateData: 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";
import App from "./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 = new App(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";

class App implements AppInterface {
  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  ===== `);
      logger.info(`https://github.com/yasinpalizban`);
      logger.info(`======= ENV: ${this.env} =======`);
      logger.info(`? App listening on the port ${this.port}`);

    });
  }

  public getServer(): express.Application {
    return this.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;


пожалуйста

Другие вопросы по теме