0.0.7 • Published 2 years ago

knex-graphql-utils v0.0.7

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

knex-graphql-utils

npm version test release

Set of useful functions for Knex + GraphQL.

  • BatchLoader Loads and paginates relationship without N+1 problem.
  • SelectionFilter Filters selected columns based on GraphQL field selection set.

Note: Only PostgreSQL is supported for now

tl;dr

Before

image

After

image

Motivation

Creating a GraphQL service with a Relational Database is a hard thing. We should take care of:

  • Performance for querying relations. N+1 problem will happen if you don't use Dataloader.
  • select * make your server slow, but hard to filter columns based on requests.
  • Pagination. Dataloader pattern is hard to implement pagination without a hacky 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.

Install

npm install --save knex-graphql-utils

Or if you use Yarn:

yarn add knex-graphql-utils

Getting Started

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)$/)
})

Integrate 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/)

Limitation & Todo

  • Only PostgreSQL is supported for now.
  • Primary key is always assumed to be id.
  • Add where clause with paginating relationship.

Further reading

For more details, please visit demo and unit tests.

0.0.7

2 years ago

0.0.5

3 years ago

0.0.6

3 years ago

0.0.4

3 years ago

0.0.3

3 years ago

0.0.2

3 years ago

0.0.1

3 years ago