• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

graphql-compose/graphql-compose-mongoose: Mongoose model converter to GraphQL ty ...

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

graphql-compose/graphql-compose-mongoose

开源软件地址(OpenSource Url):

https://github.com/graphql-compose/graphql-compose-mongoose

开源编程语言(OpenSource Language):

TypeScript 99.7%

开源软件介绍(OpenSource Introduction):

graphql-compose-mongoose

travis build codecov coverage npm trends Commitizen friendly Backers on Open Collective Sponsors on Open Collective

This is a plugin for graphql-compose, which derives GraphQLType from your mongoose model. Also derives bunch of internal GraphQL Types. Provide all CRUD resolvers, including graphql connection, also provided basic search via operators ($lt, $gt and so on).

Release Notes for v9.0.0 contains a lot of improvements. It's strongly recommended for reading before upgrading from v8.

Installation

npm install graphql graphql-compose mongoose graphql-compose-mongoose --save

Modules graphql, graphql-compose, mongoose are in peerDependencies, so should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.

Intro video

Viktor Kjartansson created a quite solid intro for graphql-compose-mongoose in comparison with graphql-tools:

#2 Mongoose - add GraphQL with graphql-compose

https://www.youtube.com/watch?v=RXcY-OoGnQ8 (23 mins)

Example

Live demo: https://graphql-compose.herokuapp.com/

Source code: https://github.com/graphql-compose/graphql-compose-examples

Small explanation for variables naming:

  • UserSchema - this is a mongoose schema
  • User - this is a mongoose model
  • UserTC - this is a ObjectTypeComposer instance for User. ObjectTypeComposer has GraphQLObjectType inside, available via method UserTC.getType().
  • Here and in all other places of code variables suffix ...TC means that this is ObjectTypeComposer instance, ...ITC - InputTypeComposer, ...ETC - EnumTypeComposer.
import mongoose from 'mongoose';
import { composeMongoose } from 'graphql-compose-mongoose';
import { schemaComposer } from 'graphql-compose';

// STEP 1: DEFINE MONGOOSE SCHEMA AND MODEL
const LanguagesSchema = new mongoose.Schema({
  language: String,
  skill: {
    type: String,
    enum: ['basic', 'fluent', 'native'],
  },
});

const UserSchema = new mongoose.Schema({
  name: String, // standard types
  age: {
    type: Number,
    index: true,
  },
  ln: {
    type: [LanguagesSchema], // you may include other schemas (here included as array of embedded documents)
    default: [],
    alias: 'languages', // in schema `ln` will be named as `languages`
  },
  contacts: { // another mongoose way for providing embedded documents
    email: String,
    phones: [String], // array of strings
  },
  gender: { // enum field with values
    type: String,
    enum: ['male', 'female'],
  },
  someMixed: {
    type: mongoose.Schema.Types.Mixed,
    description: 'Can be any mixed type, that will be treated as JSON GraphQL Scalar Type',
  },
});
const User = mongoose.model('User', UserSchema);

// STEP 2: CONVERT MONGOOSE MODEL TO GraphQL PIECES
const customizationOptions = {}; // left it empty for simplicity, described below
const UserTC = composeMongoose(User, customizationOptions);

// STEP 3: ADD NEEDED CRUD USER OPERATIONS TO THE GraphQL SCHEMA
// via graphql-compose it will be much much easier, with less typing
schemaComposer.Query.addFields({
  userById: UserTC.mongooseResolvers.findById(),
  userByIds: UserTC.mongooseResolvers.findByIds(),
  userOne: UserTC.mongooseResolvers.findOne(),
  userMany: UserTC.mongooseResolvers.findMany(),
  userDataLoader: UserTC.mongooseResolvers.dataLoader(),
  userDataLoaderMany: UserTC.mongooseResolvers.dataLoaderMany(),
  userByIdLean: UserTC.mongooseResolvers.findById({ lean: true }),
  userByIdsLean: UserTC.mongooseResolvers.findByIds({ lean: true }),
  userOneLean: UserTC.mongooseResolvers.findOne({ lean: true }),
  userManyLean: UserTC.mongooseResolvers.findMany({ lean: true }),
  userDataLoaderLean: UserTC.mongooseResolvers.dataLoader({ lean: true }),
  userDataLoaderManyLean: UserTC.mongooseResolvers.dataLoaderMany({ lean: true }),
  userCount: UserTC.mongooseResolvers.count(),
  userConnection: UserTC.mongooseResolvers.connection(),
  userPagination: UserTC.mongooseResolvers.pagination(),
});

schemaComposer.Mutation.addFields({
  userCreateOne: UserTC.mongooseResolvers.createOne(),
  userCreateMany: UserTC.mongooseResolvers.createMany(),
  userUpdateById: UserTC.mongooseResolvers.updateById(),
  userUpdateOne: UserTC.mongooseResolvers.updateOne(),
  userUpdateMany: UserTC.mongooseResolvers.updateMany(),
  userRemoveById: UserTC.mongooseResolvers.removeById(),
  userRemoveOne: UserTC.mongooseResolvers.removeOne(),
  userRemoveMany: UserTC.mongooseResolvers.removeMany(),
});

// STEP 4: BUILD GraphQL SCHEMA OBJECT
const schema = schemaComposer.buildSchema();
export default schema;

// STEP 5: DEMO USE OF GraphQL SCHEMA OBJECT
// Just a demo, normally you'd pass schema object to server such as Apollo server.
import { graphql } from 'graphql';

(async () => {
  await mongoose.connect('mongodb://localhost:27017/test');
  await mongoose.connection.dropDatabase();

  await User.create({ name: 'alice', age: 29, gender: 'female' });
  await User.create({ name: 'maria', age: 31, gender: 'female' });
  const bob = await User.create({ name: 'bob', age: 30, gender: 'male' });

  const response1 = await graphql({
    schema,
    source: 'query { userMany { _id name } }',
  });
  console.dir(response1, { depth: 5 });

  const response2 = await graphql({
    schema,
    source: 'query($id: MongoID!) { userById(_id: $id) { _id name } }',
    variableValues: { id: bob._id },
  });
  console.dir(response2, { depth: 5 });

  const response3 = await graphql({
    schema,
    source: 'mutation($id: MongoID!, $name: String) { userUpdateOne(filter: {_id: $id}, record: { name: $name }) { record { _id name } } }',
    variableValues: { id: bob._id, name: 'bill' },
  });
  console.dir(response3, { depth: 5 });

  mongoose.disconnect();
})();

That's all! You think that is to much code? I don't think so, because by default internally was created about 55 graphql types (for input, sorting, filtering). So you will need much much more lines of code to implement all these CRUD operations by hands.

Working with Mongoose Collection Level Discriminators

Variable Namings

  • ...DTC - Suffix for a DiscriminatorTypeComposer instance, which is also an instance of ObjectTypeComposer. All fields and Relations manipulations on this instance affects all registered discriminators and the Discriminator Interface.
  import mongoose from 'mongoose';
  import { schemaComposer } from 'graphql-compose';
  import { composeMongooseDiscriminators } from 'graphql-compose-mongoose';

  // pick a discriminatorKey
  const DKey = 'type';

  const enumCharacterType = {
    PERSON: 'Person',
    DROID: 'Droid',
  };

  // DEFINE BASE SCHEMA
  const CharacterSchema = new mongoose.Schema({
    // _id: field...
    type: {
      type: String,
      required: true,
      enum: (Object.keys(enumCharacterType): Array<string>),
      description: 'Character type Droid or Person',
    },

    name: String,
    height: Number,
    mass: Number,
    films: [String],
  });

  // DEFINE DISCRIMINATOR SCHEMAS
  const DroidSchema = new mongoose.Schema({
    makeDate: String,
    primaryFunction: [String],
  });

  const PersonSchema = new mongoose.Schema({
    gender: String,
    hairColor: String,
    starships: [String],
  });

  // set discriminator Key
  CharacterSchema.set('discriminatorKey', DKey);

  // create base Model
  const CharacterModel = mongoose.model('Character', CharacterSchema);

  // create mongoose discriminator models
  const DroidModel = CharacterModel.discriminator(enumCharacterType.DROID, DroidSchema);
  const PersonModel = CharacterModel.discriminator(enumCharacterType.PERSON, PersonSchema);

  // create DiscriminatorTypeComposer
  const baseOptions = { // regular TypeConverterOptions, passed to composeMongoose
    fields: {
      remove: ['friends'],
    }
  }
  const CharacterDTC = composeMongooseDiscriminators(CharacterModel, baseOptions);

  // create Discriminator Types
  const droidTypeConverterOptions = {  // this options will be merged with baseOptions -> customizationsOptions
    fields: {
      remove: ['makeDate'],
    }
  };
  const DroidTC = CharacterDTC.discriminator(DroidModel, droidTypeConverterOptions);
  const PersonTC = CharacterDTC.discriminator(PersonModel);  // baseOptions -> customizationsOptions applied

  // You may now use CharacterDTC to add fields to all Discriminators
  // Use DroidTC, `PersonTC as any other ObjectTypeComposer.
  schemaComposer.Mutation.addFields({
    droidCreate: DroidTC.getResolver('createOne'),
    personCreate: PersonTC.getResolver('createOne'),
  });

  const schema = schemaComposer.buildSchema();

  describe('createOne', () => {
    it('should create child document without specifying DKey', async () => {
      const res = await graphql.graphql({
        schema,
        source: `mutation CreateCharacters {
          droidCreate(record: {name: "Queue XL", modelNumber: 360 }) {
            record {
              __typename
              type
              name
              modelNumber
            }
          }

          personCreate(record: {name: "mernxl", dob: 57275272}) {
            record {
              __typename
              type
              name
              dob
            }
          }
        }`
      );

      expect(res).toEqual({
        data: {
          droidCreate: {
            record: { __typename: 'Droid', type: 'Droid', name: 'Queue XL', modelNumber: 360 },
          },
          personCreate: {
            record: { __typename: 'Person', type: 'Person', name: 'mernxl', dob: 57275272 },
          },
        },
      });
    });
  });

Customization options

composeMongoose customization options

When you converting mongoose model const UserTC = composeMongoose(User, opts: ComposeMongooseOpts); you may tune every piece of future derived types – setup name and description for the main type, remove fields or leave only desired fields.

type ComposeMongooseOpts = {
  /**
   * Which type registry use for generated types.
   * By default is used global default registry.
   */
  schemaComposer?: SchemaComposer<TContext>;
  /**
   * What should be base type name for generated type from mongoose model.
   */
  name?: string;
  /**
   * Provide arbitrary description for generated type.
   */
  description?: string;
  /**
   * You can leave only whitelisted fields in type via this option.
   * Any other fields will be removed.
   */
  onlyFields?: string[];
  /**
   * You an remove some fields from type via this option.
   */
  removeFields?: string[];
  /**
   * You may configure generated InputType
   */
  inputType?: TypeConverterInputTypeOpts;
  /**
   * You can make fields as NonNull if they have default value in mongoose model.
   */
  defaultsAsNonNull?: boolean;
};

This is opts.inputType options for default InputTypeObject which will be provided to all resolvers for filter and input args.

type TypeConverterInputTypeOpts = {
  /**
   * What should be input type name.
   * By default: baseTypeName + 'Input'
   */
  name?: string;
  /**
   * Provide arbitrary description for generated type.
   */
  description?: string;
  /**
   * You can leave only whitelisted fields in type via this option.
   * Any  

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap