Я пытаюсь создать схему, однако это будет слишком долго и запутанно, каковы наилучшие методы разделения различных запросов, мутаций и входных данных, чтобы я мог просто потребовать их и организовать их, чтобы их было легко читать.
Я пытался найти информацию в Интернете, но там вообще ничего не понятно, и я стараюсь не использовать Аполлон.
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
}
`);
Мне нужно что-то очень организованное и позволяющее привести все в порядок и четко, объединить все файлы в один индекс - лучшее решение.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вариантов несколько, вот три из них:
Вы можете взглянуть на блог Аполлона — они показывают способ модульности схемы: Модульность кода схемы GraphQL Если вам нужен пример, вы можете взглянуть на этот репозиторий github
Конечно, вы всегда можете просто использовать литералы шаблона для встраивания частей схемы в виде строк:
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);
Да, но мне нравится подход Аполлона
Убедитесь, что у вас есть хотя бы один тест, в котором вы вызываете «buildSchema», так как могут быть дубликаты, а «buildSchema» должна выдать вам ошибку, если какие-либо дубликаты существуют.
Сделайте его отдельной папкой и структурой, чтобы сделать коды удобными. Я делаю следующее:
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;
пожалуйста
Итак, я могу просто создать несколько файлов и правильно вставить их в индекс? Я рассмотрю Apollo в будущем, потому что сначала я научусь делать это без использования какой-либо другой библиотеки. Сначала мне нужно понять весь процесс, спасибо за вашу помощь!