Как использовать Mongoose-схему для генерации graphQL-типов

в 11:43, , рубрики: graphql, javascript, mongo, mongodb, mongoose, node.js, nodejs, npm, React, ReactJS

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

Все начал с того что я решил написать изоморфный CMS для одного из моих проектов с использованием следующих технологий:

  • React — для постройки UI
  • Express — в качестве сервера
  • MongoDb + Mongoose — noSQL база данных
  • graphQL — основной API для взаимодействия с базой данных
  • Apollo-Client — коннектор для удобного вызова запросов и мутаций через graphQL
  • webpack — для сборки проекта и разделения клиентского и серверного кода

Упрощенная архитектура выглядит следующим образом:

__root
1 |__client
2 |__public
3 |__middleware
4 |__server

  1. React-компоненты
  2. Бандл клиентского кода и остальные публичные файлы
  3. Mongoose: схемы, дополнительные методы и graphQL: типы, класс с запросами, класс с мутациями, схема
  4. Бандл серверного кода вместе с express сервером


Соответственно, для каждой сущности в базе данных мы должны создать Mongoose-схему, чтобы иметь возможность манипулировать данными. Выглядит это примерно следующим образом:

let couponSchema = mongoose.Schema({
    couponCode: Array,
    description: String,
    discountAmount: String,
    minimumAmount: String,
    singleUseOnly: Boolean,
    createdAt: mongoose.Schema.Types.Date,
    updatedAt: mongoose.Schema.Types.Date,
    expirationDate: mongoose.Schema.Types.Date
});

Теперь если мы используем graphQL мы должны создать тип для каждой сущности в базе данных, причем тип каждого свойства должен быть идентичен тому же, что и в Mongoose схеме. Без graphQL-типов мы просто не сможем сгенерировать graphQL-схему. Тип выглядит следующим образом:


let couponType = new GraphQLObjectType({
    name: 'couponType',
    description: 'single use coupon',
    fields: {
        _id: {type: GraphQLString},
        couponCode: {type: new GraphQLList(GraphQLString)},
        description: {type: GraphQLString},
        discountAmount: {type: GraphQLString},
        minimumAmount: {type: GraphQLString},
        singleUseOnly: {type: GraphQLBoolean},
        createdAt: {type: GraphQLString},
        updatedAt: {type: GraphQLString},
        expirationDate: {type: GraphQLString}
    }
});

Думаю, схожесть очевидна? А теперь представьте, что вы имеете несколько десятков сущностей и для каждой из них вам нужно практически дублировать одну и ту же логику. А ведь типы могут содержать массивы других типов и множество других вложенных элементов.

Собственно, для разрешения этой проблемы я написал небольшой модуль (всего 3kb), который поможет избежать дублирования логически идентичного кода и практически сократит вдвое ваш код, в моем случае я сократил код более чем на 2000 строк.

Для начала установим модуль:

npm i mongoose-schema-to-graphql --save

Модуль содержит одну единственную функцию “MTGQL”, которая принимает один объект с конфигурациями в качестве аргумента. Объект имеет следующую структуру:


let configs = {
              name: 'couponType',                          
// название graphQL типа
              description: 'Coupon base schema',  
// описание graphQL типа
              class: 'GraphQLObjectType',             
// graphQL класс который будет использован для сборки
              schema: couponSchema,                 
// Mongoose схема
              exclude: ['_id'],                                   
// свойства которые нужно исключить
              props: {
                price: {type: GraphQLFloat}
              }                                                        
// дополнительные свойства которые нужно добавить или переписать
          }

Пару примеров использования модуля ниже:

dbSchema.js


import mongoose from 'mongoose';
let selectObj = {
    value: String,
    label: String
};

let answerSchema = mongoose.Schema({
    createdAt: mongoose.Schema.Types.Date,
    updatedAt: mongoose.Schema.Types.Date,
    title: String,
    answersImage: String,
    recommended: [selectObj],
    isPublished: Boolean
});

export let questionSchema = mongoose.Schema({
    question: String,
    defRecommended: [selectObj],
    createdAt: mongoose.Schema.Types.Date,
    updatedAt: mongoose.Schema.Types.Date,
    isPublished: Boolean,
    multipleChoice: Boolean,
    answers: [answerSchema]
});

В файле с вашими graphQL типами:

type.js


import MTGQL from 'mongoose-schema-to-graphql';
import {questionSchema} from './dbSchemas';

let config = {
    name: 'questionType',
    description: 'Question collection's type',
    class: 'GraphQLObjectType',
    schema: questionSchema,
    exclude: ['_id']
};

export let questionType = MTGQL(config);

Собственно всего 10 строк кода, которые равносильны следующему:


import {
    GraphQLObjectType, 
    GraphQLString, 
    GraphQLBoolean, 
    GraphQLList, 
    GraphQLInt
} from 'graphql';

let selectType = new GraphQLObjectType({
    name: 'selectType',
    fields: {
        value: {type: GraphQLString},
        label: {type: GraphQLString}
    }
});

let answerType = new GraphQLObjectType({
    name: 'answerType',
    description: 'answer type for question',
    fields: {
        title: {type: GraphQLString},
        answersImage: {type: GraphQLString},
        recommended: {type: new GraphQLList(selectType)},
        createdAt: {type: GraphQLString},
        updatedAt: {type: GraphQLString},
        isPublished: {type: GraphQLBoolean}
    }
});

export let questionType = new GraphQLObjectType({
    name: 'questionType',
    description: 'Question collection's type',
    fields: {
        _id: {type: GraphQLString},
        question: {type: GraphQLString},
        defRecommended: {type: new GraphQLList(selectType)},
        createdAt: {type: GraphQLString},
        updatedAt: {type: GraphQLString},
        isPublished: {type: GraphQLBoolean},
        multipleChoice: {type: GraphQLBoolean},
        answers: {type: new GraphQLList(answerType)}
    }
});

Думаю, разница очевидна. Модуль не поддерживает напрямую “GraphQLFloat”, потому что в Mongoose схеме вы можете задать только тип «Number», который эквивалентен целому числу. Но эту проблему можно легко решить, передав нужное свойство через конфигурационный объект.

Более подробно о том, как работает модуль, вы можете посмотреть на GitHub.

Надеюсь, статья была вам полезна, если у вас есть какие-то предложения по улучшению, пишите, обязательно учту. Спасибо за внимание.

Оригинал статьи находится на medium.com.

Автор: sarkis-tlt

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js