GraphQL Helpers - Write Less Code!

I don’t have these open sourced yet but I wanted to share these in case others wanted to spin off something similar in the mean time.

These helpers are really meant for either day 1 training wheels or for after you understand how GraphQL works since they kind of hide the details and make it less flexible.

Also this post might be a good high level overview of setting up CRUD actions for a Post resource (imports hidden for terseness). This flow is all code required to get a CRUD equivalent working (including replacing simple-schema):

// Define three global namespaces in server/lib
Query = {};
Mutation = {};
Types = {};

// Create types for the collection document This Post has
// embedded comments but we'll make a sep type for that too

Types.Post = object('Post', {
  id: string(),
  createdAt: date(),
  description: string(),
  title: string(),
  comments: list(Types.Comment), // array of type below
})

Types.Comment = object('Comment', {
  id: string(),
  createdAt: date(),
  description: string(),
})

// allow users to fetch a single post or all posts they own:

Query.usersPosts = {
  type: listType(Types.Post),
  description: "All posts that a user wrote",
  args: {
    userId: string({required: true}),
    limit: integer(),
  },
  resolve(root, args) {
    return Posts.find({userId: args.userId}, {limit: args.limit})
  }
};

Query.post = {
  type: Types.Post,
  description: "A single post document",
  args: {
    id: string({required: true}),
  },
  resolve: (root, args) => (
    return Posts.findOne({userId: args.userId})
  )
};

// allow a user to update or delete a post, note these are
// not using meteor-graphql auth helpers or the Roles package

UserMutation.createPost = {
  type: Types.Post,
  args: {
    description: string({required: true}),
    title: string({required: true}),
  },
  resolve: (root, args) => {
    args.createdAt = new Date();
    const id = Posts.insert(args);
    return Posts.findOne(id);
  }
};


UserMutation.deletePost = {
  type: Types.Post,
  args: {
    id: string({required: true}),
  },
  resolve: (root, args) => {
    return Posts.remove({id: args.id});
  }
};

// now glue them all together so that the `/graphql` url endpoint is accessible

Meteor.startup(() => (
  GraphHelpers.registerSchema({
    schemaName: 'MyApp',
    queryName: 'MyAppQueries',
    queryFields: Query,
    mutationName: 'MyAppMutations',
    mutationFields: Mutation,
  })
));       

Done! Note, you can also add options into each helper to pass in more attributes like:

userId: string({required: true, desc: "A valid mongo ID"})
5 Likes

I like the idea of NS, I think you might need to change [quote=“SkinnyGeek1010, post:1, topic:17221”]
Mutation = {};
[/quote]
to
UserMutation={}
for the initial assignment

1 Like

Would it be easy to make this work with something like simple schema?

1 Like

Good catch! My internet went down right after I posted and couldn’t get back online to correct it :laughing: That was from my internal app. I also have it setup to have an BackendMutations as well that only an the other microservices can access (server to server).


[quote="sashko, post:3, topic:17221, full:true"] Would it be easy to make this work with something like simple schema? [/quote]

I think so! I might be handy for a drop in replacement. However, long term using the FB schema will be more granular and composable.

You could make your own custom scalar types that would directly run the simple schema per key and would use simple schema for validation:

learning-graphql/7. Deep Dive into GraphQL Type System.md at master · mugli/learning-graphql · GitHub

Here’s the gist of what these helpers are doing:

GraphHelpers = {
  stringType: () => GraphQLString,

  string(opts = {}) {
    var type;
    if (opts.required) {
      type = {type: new GraphQLNonNull(GraphQLString)};
    } else {
      type = {type: GraphQLString };
    }
    return extend(opts, type)
  },
}
2 Likes