Set of useful functions for Knex + GraphQL - Batch Loading, Column reducer
MIT License
Set of useful functions for Knex + GraphQL.
Note: Only PostgreSQL is supported for now
Creating a GraphQL service with a Relational Database is a hard thing. We should take care of:
select *
make your server slow, but hard to filter columns based on requests.union
or window functions. knex-graphql-utils
uses row_number()
window function to do it.With knex-graphql-utils
, You can build performant GraphQL servers without hassle.
npm install --save knex-graphql-utils
Or if you use Yarn:
yarn add knex-graphql-utils
In this example, I use mercurius
but this can be applied to any GraphQL frameworks.
// app.ts
import Fastify from 'fastify'
import mercurius from 'mercurius'
import { BatchLoader, SelectionFilter } from 'knex-graphql-utils'
import { knex } from './knex' // Your knex instance
const app = Fastify()
const schema = `
type Query {
user: User
}
type User {
id: ID!
name: String!
posts(page: Int!): [Post!]!
}
type Post {
id: ID!
title: String!
user: User!
}
`
const selectionFilter = new SelectionFilter(knex)
const resolvers = {
Query: {
user: (user, args, ctx, info) =>
knex('users')
.select(selectionFilter.reduce({ info, table: 'users' }))
.first(),
},
User: {
posts: (user, args, ctx) =>
ctx.batchLoader
.getLoader({
type: 'hasMany',
foreignKey: 'userId',
targetTable: 'posts',
page: {
offset: ((args.page || 1) - 1) * 10,
limit: 10,
},
orderBy: ['createdAt', 'asc'],
queryModifier: (query) => {
query.select(selectionFilter.reduce({ info, table: 'posts' }))
},
})
.load(user.id),
},
Post: {
user: (post, _args, ctx, info) =>
ctx.batchLoader
.getLoader({
type: 'belongsTo',
foreignKey: 'userId',
targetTable: 'users',
queryModifier: (query) => {
query.select(selectionFilter.reduce({ info, table: 'users' }))
},
})
.load(post.userId),
},
}
app.register(mercurius, {
schema,
resolvers,
context: () => ({
batchLoader: new BatchLoader(knex),
}),
})
app.listen(8877).then(async () => {
await selectionFilter.prepare(['users', 'posts'], /(_id)|(Id)$/)
})
BatchLoader
and SelectionFilter
You can tell BatchLoader
to use SelectionFilter
, and the loader automatically reduces the selection based on info
.
import { BatchLoader, SelectionFilter } from 'knex-graphql-utils'
import { knex } from './knex' // Your knex instance
const selectionFilter = new SelectionFilter(knex)
const context = {
batchLoader: new BatchLoader(knex).useSelectionFilter(selectionFilter), // Attach SelectionFilter into batch loader
}
const resolver = {
User: {
posts: (user, args, ctx, info) =>
ctx.batchLoader
.getLoader({
type: 'hasMany',
foreignKey: 'userId',
targetTable: 'posts',
info, // By passing `info`, loader automatically reduces the selection
})
.load(user.id),
},
}
await selectionFilter.prepare(['users'], /_id/)
id
.where
clause with paginating relationship.For more details, please visit demo and unit tests.