2.3.3 • Published 4 years ago

mongo-to-gql v2.3.3

Weekly downloads
2
License
MIT
Repository
github
Last release
4 years ago

Mongo-To-GQL

Auto-generator for the MongoDB model to GraphQL type definition and query resolvers. Just write your mongoose model code, then I generate gql code for you!

Current - 2.3.3 - (feat): aaronwlee

  • enabled the conversion with the list of models and mutations.
  • enabled the conversion models and mutations to GQL ready to use string and object.
  • changed the mutation naming method because of the unsupported method when built by Webpack. (bug fix)
  • test and sample files have rewritten (minor)

2.2.13 - (fix): aaronwlee

  • subdirectory supporting has updated (bug fix)
  • getting a total query has enhanced (bug fix)
  • authentication method for the apollo server has added
  • auth boolean option has added into the gqloption
  • Now you able to connect with the typescript model and mutation in development mode. Set the 'devWithTs' option and use the nodemon.
  • embedded many supported
  • embedded search implemented
  • customizable apollo server options supported
  • customizable resolvers and typeDefs options supported
  • file upload support via Upload

Installing

$ npm i mongo-to-gql

Or

$ yarn add mongo-to-gql

Getting Started

Mongo model basic

  • just write your basic mongoose model with gqlOption ex) src/model/user.model.ts
type UserDocument = Document & {
  email: string;
  age: number;
  name: string;
  password: string;
  picture: string[];
  follower: string[];
  product: string;
};

const schema: Schema<UserDocument> = new mongoose.Schema<UserDocument>({
  email: { type: String, unique: true },
  age: Number,
  name: { type: String, required: true },
  password: String,
  picture: [String],
  follower: [{ type: Schema.Types.ObjectId, ref: 'User' }],   // has many => this field will have `User` type which is from `ref`
  product: { type: Schema.Types.ObjectId }                    // has one without ref => this field will be `ID` type if there is no Populate options.
}, {                                                          // if the Populate options have `path` and `model` param, it will be `JSON` type.
  timestamps: true,
  // virtual requirements
  toObject: { virtuals: true },
  toJSON: { virtuals: true }
});

// JSON type
schema.virtual("fullName").get(function () {
  return ({
    name: this.name,
    email: this.email
  })
})

// JSON type
schema.virtual("extra").get(async function () {
  const users = await this.model("User").findOne({ name: "Aaron" })
  return users
})

// if you use has many or has one, you must export this option with exact name of const 
export const gqlOption: IgqlOption = {
  // if you didn't declare `ref` in the schema, you have to specify the target model.
  Populate: [{ path: "product", model: Product, select: "name" }, { path: "follower" }],
  Auth: true    // authentication option. if the apollo server context option sends an appropriate 'user', this model's GQL query can be accessible.  
}

export default mongoose.model<UserDocument>("User", schema);

After using our mongo-to-gql, you'll get auto-generated this queries and gql definitions

query getUsers {
  Users(
    page: 0, 
    limit: 4, 
    filter: {name_has: "a", email_in: ["mongo@gql.com", "gql@mongo.com"]}, 
    sort: updatedAt_asc
    ) {
    data {
      _id
      name
      email
      fullName
      follower {
        name
      }
      product         // This field is JSON type because of the schema that does not have "ref".
    }
    page
    total
  }
}

query UserByID {
  User(_id: "5dc8aa758ac3cd40e7ea0c7f") {
    name
    email
    follower {
      name
    }
  }
}

Result

// query getUsers
{
  "data": {
    "Users": {
      "data": [
        {
          "name": "aaron",
          "_id": "5dc8aa758ac3cd40e7ea0c7f",
          "email": "mongo@gql.com",
          "fullName": "aaron mongo@gql.com",
          "follower": [
            {
              "name": "Aaron Wooseok Lee"
            }
          ],
          "product": {
            "_id": "5dda18db51cf6d338ac7822e",
            "name": "This is a product",
            "field1": "test field",
          }
        },
        {
          "name": "ace",
          "_id": "5dc8aa7e34b45241582cbd52",
          "email": "gql@mongo.com",
          "follower": [
            {
              "name": "Eric"
            },
            {
              "name": "aaron"
            }
          ],
          "product": {
            "_id": "5dda18db51cf6d338ac7822e",
            "name": "This is a product",
            "field1": "test field",
          }
        }
      ],
      "page": 0,
      "total": 2
    }
  }
}

// query UserByID
{
  "data": {
    "User": {
      "name": "aaron",
      "email": "mongo@gql.com",
      "follower": [
        {
          "name": "Aaron Wooseok Lee"
        }
      ],
    }
  }
}

Let's start it!

Start with ApolloServer

  • Development mode | start with nodemon your main ts file
import { executeApolloServer } from 'mongo-to-gql';
import express from 'express';

const app = express();

executeApolloServer({
    app: app,
    devWithTs: true,                // if you're using typescript with nodemon and ts-node.
    modelFolderPath: 'src/model',       // pure .ts path
    mutationFolderPath: 'src/mutation', // pure .ts path
    path: "/myRouter",
}).then(result => {
  console.log(result.pureTypeDefs)  // display result
})
  • Production Mode - built by Typescript
import { executeApolloServer } from 'mongo-to-gql';
import express from 'express';

const app = express();

executeApolloServer({
    app: app,
    modelFolderPath: 'dist/model',        // built .js path
    mutationFolderPath: 'dist/mutation',  // built .js path
    path: "/myRouter",
}).then(result => {
  console.log(result.pureTypeDefs)  // display result
})
  • Production Mode - built by Webpack
import { executeApolloServer } from 'mongo-to-gql';
import express from 'express';
import * as User from './model/user';             // this is important to import an entire module's contents
import * as addUser from './mutation/addUser';    // more info - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import

const app = express();

executeApolloServer({
    app: app,
    modelList: {
      User
    },
    mutationList: {
      addUser
    }
    path: "/myRouter",
}).then(result => {
  console.log(result.pureTypeDefs)  // display result
})

Start with only Converting

  • Development mode | start with nodemon your main ts file
import { convertToGQL } from 'mongo-to-gql';
import express from 'express';

const app = express();

convertToGQL({
    devWithTs: true,                // if you're using typescript with nodemon and ts-node.
    modelFolderPath: 'src/model',       // pure .ts path
    mutationFolderPath: 'src/mutation', // pure .ts path
}).then(result => {
  console.log(result.converted)  // use it to init your gql server
})
  • Production Mode - built by Typescript
import { convertToGQL } from 'mongo-to-gql';
import express from 'express';

const app = express();

convertToGQL({
    modelFolderPath: 'dist/model',        // built .js path
    mutationFolderPath: 'dist/mutation',  // built .js path
}).then(result => {
  console.log(result.converted)  // use it to init your gql server
})
  • Production Mode - built by Webpack
import { convertToGQL } from 'mongo-to-gql';
import express from 'express';
import * as User from './model/user';             // this is important to import an entire module's contents
import * as addUser from './mutation/addUser';    // more info - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import

const app = express();

convertToGQL({
    modelList: {
      User
    },
    mutationList: {
      addUser
    }
}).then(result => {
  console.log(result.converted)  // use it to init your gql server
})

options for the executeApolloServer({})

const options: ImongoToGQLOptions = {
  app: app,

  // one of these is a must.
  modelFolderPath: 'dist/model',
  modelList: {
    User: userModel
  }
  
  // optional
  devWithTs: true,                  // option for using typescript folder path. for the auto build in development, you must use nodemon.
  mutationFolderPath: 'dist/mutation',  // when you use modelFolderPath only
  mutationList: {                       // when you use modelList only
    addUser
  }
  path: '/thisisnotdefaultpath',
  context: ({ req }: any) => {      // https://www.apollographql.com/docs/apollo-server/security/authentication/
    const user = req.session.user;

    return { user };
  }
  apolloOptions: {                  // https://www.apollographql.com/docs/apollo-server/api/apollo-server/
    cacheControl: {
      defaultMaxAge: 5,
    }
  },  
  customTypeDefs: `
  type Custom {
    something: String
  }
  `
  customResolvers: {
    Query: {
        asd: () => {
            console.log("hi")
        }
    },
    Mutation: {...}
    File: "Somthing"
  }
}
  • app is your express app
  • modelFolderPath should be your pure js model folder which is starting with pwd path | but if you set devWithTs option to true, then you can able to use the ts model folder.
  • modelList should be a list of your imported models, and it must be an entire module's contents. ex) import * as
  • mutationFolderPath should be your pure js mutation folder which is starting with pwd path | but if you set devWithTs option to true, then you can able to use the ts mutation folder.
  • mutationList should be a list of your imported mutations, and it must be an entire module's contents. ex) import * as
  • devWithTs this option is enable to use your typescript folder path only for development environment. must use nodemon.
  • path is for graphql path config, default router is /graphql
  • context is for authenticating your resolvers. you can basically pass the user parameter or token.
  • apolloOptions this option for ApolloServer's param
  • customResolvers is for the extra resolvers
  • customTypeDefs is for the extra typeDefs, but it must be string

options for the convertToGQL({})

const options: ImongoToGQLConverterOptions = {
  modelFolderPath: 'dist/model',
  
  // optional
  devWithTs: true,                  // option for using typescript folder path. for the auto build in development, you must use nodemon.
  mutationFolderPath: 'dist/mutation',
  customTypeDefs: `
  type Custom {
    something: String
  }
  `
  customResolvers: {
    Query: {
        asd: () => {
            console.log("hi")
        }
    },
    Mutation: {...}
    File: "Somthing"
  }
}
  • modelFolderPath should be your pure js model folder which is starting with pwd path | but if you set devWithTs option to true, then you can able to use the ts model folder.
  • modelList should be a list of your imported models, and it must be an entire module's contents. ex) import * as
  • mutationFolderPath should be your pure js mutation folder which is starting with pwd path | but if you set devWithTs option to true, then you can able to use the ts mutation folder.
  • mutationList should be a list of your imported mutations, and it must be an entire module's contents. ex) import * as
  • devWithTs this option is enable to use your typescript folder path only for development environment. must use nodemon.
  • customResolvers is for the extra resolvers
  • customTypeDefs is for the extra typeDefs, but it must be string

Promise results

executeApolloServer(options)
  • Initializing, building and connect apollo server with MongoToGQLOptions
  • After your express server executed, apollo server will start with /graphql router or your path config
interface IresultType {
  converted: {
    typeDefs: any,
    resolvers: any
  },
  pureTypeDefs: string,
  pureResolvers: any
}
  • this interface is the promise results of executeApolloServer
convertToGQL(options)
  • it converts your models and mutations to GQL ready to use string and object.
interface IresultType {
  converted: {
    typeDefs: any,
    resolvers: any
  },
  pureTypeDefs: string,
  pureResolvers: any
}
  • this interface is the promise results of convertToGQL

Examples

Mongo model with mongoosejs

Model sample (in model folder)

Path - src/model/user.model.ts

  • exporting the model as a default is mandatory!
  • gqlOption is for joining the foreign table, without this the queries will return the just _ids

Just write your mongoose model code, then I generate gql code for you!

import mongoose, { Schema, Document, Model } from "mongoose";
import { IgqlOption } from 'mongo-to-gql'

const schema: Schema = new mongoose.Schema({
  email: { type: String, unique: true },
  age: Number,
  name: { type: String, required: true },
  password: String,
  picture: [String],
  geolocation: {
    latitude: Number,                                         // embaded many => this will be field JSON type
    longitude: Number 
  }
  follower: [{ type: Schema.Types.ObjectId, ref: 'User' }],   // has many => this field will have `User` type which is from `ref`
  product: { type: Schema.Types.ObjectId }                    // has one without ref => this field will be `ID` type if there is no Populate options.
}, {                                                          // if the Populate options have `path` and `model` param, it will be `JSON` type.
  timestamps: true,
  // virtual requirements
  toObject: { virtuals: true },
  toJSON: { virtuals: true }
});

// JSON type
schema.virtual("fullName").get(function () {
  return ({
    name: this.name,
    email: this.email
  })
})

// JSON type
schema.virtual("extra").get(async function () {
  const users = await this.model("User").findOne({ name: "Aaron" })
  return users
})

// if you use has many or has one, you must export this option with exact name of const 
export const gqlOption: IgqlOption = {
  // if you didn't declare `ref` in the schema, you have to specify the target model.
  Populate: [{ path: "product", model: Product, select: "name" }, { path: "follower" }],
  Auth: true     // authentication option. if the apollo server context option sends an appropriate 'user', this model's GQL query can be accessible.
}

export default mongoose.model("User", schema);

model auto-generate results will be

paramsDescription
pagePagination
limitpage per
filtereach field name with in (array values are in data), has (regex), ne (array values are not in data); basic field name like name for finding exact data
filter field JSONThis field only able to filtering exact same JSON structure
filter subSearchyou can search the Embedded structure with a JSON string ex) subSearch: "{'geolocation.latitude': 12.3}"
sorteach field name with asc or desc, but you can't get sortkey from JSON type
resultDescription
dataresult data object
pagecurrent page
totalresult data count
query getUsers {
  Users(page: 0, limit: 4, filter: {name_has: "a", email_in: ["mongo@gql.com", "gql@mongo.com"], subSearch: "{'geolocation.latitude': 12.3}"}, sort: updatedAt_asc) {
    data {
      _id
      name
      email
      follower {
        name
      }
      product         // This field is JSON type because of the schema that does not have "ref".
    }
    page
    total
  }
}

query UserByID {
  User(_id: "5dc8aa758ac3cd40e7ea0c7f") {
    name
    email
    follower {
        name
      }
    product         // This field is JSON type because of the schema that does not have "ref".
  }
}

Mutation sample (in mutation folder)

Path - src/mutation/addUser.ts

  • inputType and resolver are mandatory! Try to use Imutation interface, it'll be easier.
  • Your mutation function name will save as starting with a lowercase.
  • Make this sure all extra input types must be declared! ex) ProductInputType
  • The resolver should be an async method, but it's doesn't matter.
import crypto from 'crypto';
import User from "../model/user.model";
import { Imutation, graphType, IreturnType } from 'mongo-to-gql'
import Product from '../model/product.model';

// class name is your mutation's name
class AddUser implements Imutation {
    public inputType = {
        name: graphType.String,
        email: graphType.StringRequire,
        address: graphType.JSON,
        password: graphType.String,
        follower: graphType.ID,
        someJson: graphType.Json,             // json type param
        product: graphType.CustomRequire("CustomProductInputType") // this will automatically try to find this name of this element in your class
    }

    public CustomProductInputType = {       // automation target 
        name: graphType.String
    }

    // this must be returned as Promise<ReturnType>
    public resolver = (_: any, { input }: any, { user }: any): Promise<IreturnType> => {
        return new Promise(async (resolve, reject) => {
            try {
                // if you set the context option and passed an appropriate user, you can validate the authentication via the third parameter of the resolver.
                if(!user) {
                  throw "auth required"
                }
                // 
                const UserModel = new User().model;
                const ProductModel = new Product().model;
                const foundUser = await UserModel.findOne({ _id: input.follower })
                const newProduct = await ProductModel.create({ name: input.product.name })
                const result = await UserModel.create({
                    name: input.name,
                    address: input.address,
                    email: input.email,
                    // hash it before!
                    password: input.password,
                    picture: getAvatar(input.email),
                    follower: [foundUser],
                    thisisProducts: newProduct
                })
                resolve({
                    done: result,
                    error: {}
                })
            } catch (error) {
                if (error.code === 11000) {
                    resolve({ done: false, error: "email already used" })
                }
                else {
                    resolve({ done: false, error: `${error} : bad happend while we are saving user data` })
                }
            }
        })
    }
}

const getAvatar = (Email: string, size: number = 200): string => {
    const md5 = crypto.createHash("md5").update(Email).digest("hex");
    return `https://gravatar.com/avatar/${md5}?s=${size}&d=retro`;
}

export default AddUser

resolver return type

interface IreturnType {
  done: any;             // JSON type
  error: any;            // JSON type
}

mutation auto-generate results will be

paramsDescription
inputmutation class's inputType as a gql definition
resultDescription
doneanything for a result as JSON
errorerror as a JSON type
mutation AddUser{
	addUser(input: {
    name:"Wooseok Lee", 
    email: "aaron.lee@wooseok.com", 
    password: "123",
    address: "somewhere",
    follower: "5dd60a152b451e03159d2ead"
    somejson: {any: "json", types: "available"}
    product: {name: "PerfectOne"}
  }){
    done
    error
  }
}

Authors

License

This project is licensed under the MIT License - see the LICENSE.md file for details

2.3.3

4 years ago

2.3.2

4 years ago

2.3.1

4 years ago

2.3.0

4 years ago

2.2.13

4 years ago

2.2.12

4 years ago

2.2.11

4 years ago

2.2.3

4 years ago

2.2.5

4 years ago

2.2.4

4 years ago

2.2.7

4 years ago

2.2.6

4 years ago

2.2.10

4 years ago

2.2.9

4 years ago

2.2.8

4 years ago

2.2.2

4 years ago

2.2.1

4 years ago

2.2.0

4 years ago

2.1.2

4 years ago

2.1.1

5 years ago

2.1.0

5 years ago

2.0.2

5 years ago

2.0.1

5 years ago

1.7.1

5 years ago

1.7.0

5 years ago

1.6.14

5 years ago

1.6.13

5 years ago

1.6.12

5 years ago

1.6.11

5 years ago

1.6.10

5 years ago

1.6.9

5 years ago

1.6.8

5 years ago

1.6.7

5 years ago

1.6.6

5 years ago

1.6.5

5 years ago

1.6.4

5 years ago

1.6.3

5 years ago

1.6.2

5 years ago

1.6.1

5 years ago

1.6.0

5 years ago

1.5.9

5 years ago

1.5.8

5 years ago

1.5.7

5 years ago

1.5.6

5 years ago

1.5.5

5 years ago

1.5.4

5 years ago

1.5.3

5 years ago

1.5.1

5 years ago

1.5.0

5 years ago

1.4.3

5 years ago

1.4.2

5 years ago

1.4.1

5 years ago

1.4.0

5 years ago

1.3.3

5 years ago

1.3.1

5 years ago

1.3.0

5 years ago

1.2.2

5 years ago

1.2.1

5 years ago

1.2.0

5 years ago

1.1.0

5 years ago

1.0.0

5 years ago