Generate GraphQL MongoDB CRUD and Pagination Operations (10 minutes or less)

GraphQL and its schema driven development methodology allows for a lot of the common tasks around data resources to be abstracted.
ts-mongo-codegen uses your GraphQL schema and with the inclusion of (opt-in) directives generates all of the CRUD operations for a seamless integration between GraphQL types and MongoDB.
- Insert Resource
insertMountain
- Insert Many Resources
insertManyMountains
- Find Resource By ObjectID
findMountainById
- Find Many Resources By ObjectIDs
findMountainsByIds
- Paginated and Sorted Queries
findMountains
- Filtered Totals
findMountains
- Update Resource By ObjectID
updateMountain
- Update Many Resources By ObjectIDs
updateManyMountains
- Remove Resource By ObjectID
removeMountain
- Remove Many Resources By ObjectIDs
removeManyMountains
With these basic yet useful patters generated it allows the developer to focus on how the end user interacts with the data.
Less time as a result is spent doing error prone tasks like manipulating data types, and more time is spent delivering visible value.
Let's move some mountains!
You can follow along by forking the mountains-api.
Type Defs
Define the Mountain
type that we will be augmenting.
import gql from 'graphql-tag'
export const mountainSchema = gql`
type Mountain @collection(name: "mountains", crud: true) {
id: ObjectId
name: String @insert @set @filter
meters: Float @insert @set @filter
feet: Float @insert @set @filter
location: String @insert @set @filter
}
`
Directives
@collection
With this directive declared ts-mongo-codegen will generate interfaces and factory functions that can be made executable when starting the GraphQL server.
Options
- name: The name of the mongo collection
- crud: Should generate crud types and resolvers
@insert
The @insert
directive tells ts-mongo-codegen which fields should be included in the CREATE types.
@filter
The @filter
directive tells ts-mongo-codegen which fields should be included in the READ types.
@set
The @set
directive tells ts-mongo-codegen which fields should be included in the UPDATE types.
Generate
Now that we have our Mountain
type defined. We will set up our codegen.json
which defines the options for our GraphQL Codegen. More documentation can be found here.
{
"schema": ["./node_modules/ts-mongo-codegen/dist/types/mongo-types.ts", "./src/gql/*.ts"],
"generates": {
"./src/types.generated.ts": {
"plugins": [
"typescript",
"typescript-operations",
"typescript-resolvers",
"ts-mongo-codegen"
]
}
}
}
Using the above configuration we are able to generate the necessary resolvers we will need to implement CRUD and fulfill our GraphQL schema.
yarn install
yarn generate
With the code generated we can put it all together into an executable schema.
import { makeExecutableSchema } from '@graphql-tools/schema'
import { addResolveFunctionsToSchema } from 'apollo-server'
import { graphqlTypeDate, graphqlTypeObjectId, makeAugmentedSchema, mongoTypeDefs } from 'ts-mongo-codegen'
// The following import is the file generated in previous step
import { mountainMutationResolvers, mountainQueryResolvers, mountainResolvers } from './types.generated'
import { composeResolvers } from '@graphql-tools/resolvers-composition'
import { mountainSchema } from './gql/mountains'
import { isAuthenticated } from './auth'
// Make an executable schema with the mongo types and our custom mountain schema type
const executableSchema = makeExecutableSchema({
typeDefs: [mongoTypeDefs, mountainSchema],
})
// Add CRUD operations to the Mountain type by augmenting the schema
export const schema = makeAugmentedSchema(executableSchema)
// The mountainResolvers, mountainMutationResolvers, and mountainQueryResolvers are generated types
// Run `yarn generate` to update types or add more
const resolvers = composeResolvers(
{
Date: graphqlTypeDate,
Mountain: mountainResolvers,
Mutation: {
...mountainMutationResolvers,
},
ObjectId: graphqlTypeObjectId,
Query: {
...mountainQueryResolvers,
},
},
{
// Make sure mutation requests are authenticated
Mutation: [isAuthenticated()],
}
)
// Finally we add our generated resolvers to the schema
addResolveFunctionsToSchema({
resolvers,
schema,
})
And with that we are done. We can view our results in the GraphQL playground.
Playground
Conclusion
In this post we generated all of the basic CRUD operations with very little effort. Our clients whether that be web or mobile will now have a very predictable backend backed by MongoDB.
In this article we did not go into creating the context dependency necessary for the resolvers. We will go over that in a different post but if you are interested, you can find the implementation here.