Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/acro5piano/knex-graphql-utils
Set of useful functions for Knex + GraphQL - Batch Loading, Column reducer
https://github.com/acro5piano/knex-graphql-utils
graphql knex postgresql typescript
Last synced: 11 days ago
JSON representation
Set of useful functions for Knex + GraphQL - Batch Loading, Column reducer
- Host: GitHub
- URL: https://github.com/acro5piano/knex-graphql-utils
- Owner: acro5piano
- License: mit
- Created: 2021-07-24T07:56:41.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2022-01-20T13:28:50.000Z (almost 3 years ago)
- Last Synced: 2024-10-12T06:43:26.002Z (27 days ago)
- Topics: graphql, knex, postgresql, typescript
- Language: TypeScript
- Homepage: https://www.npmjs.com/package/knex-graphql-utils
- Size: 833 KB
- Stars: 18
- Watchers: 4
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# knex-graphql-utils
[![npm version](https://badge.fury.io/js/knex-graphql-utils.svg)](https://badge.fury.io/js/knex-graphql-utils)
[![test](https://github.com/acro5piano/knex-graphql-utils/actions/workflows/test.yml/badge.svg)](https://github.com/acro5piano/knex-graphql-utils/actions/workflows/test.yml)
[![release](https://github.com/acro5piano/knex-graphql-utils/actions/workflows/release.yml/badge.svg)](https://github.com/acro5piano/knex-graphql-utils/actions/workflows/release.yml)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](https://user-images.githubusercontent.com/10719495/126906832-f991dd7d-b563-4fa2-9313-738f476d46c8.png)
### After
![image](https://user-images.githubusercontent.com/10719495/126906862-71b456bd-82da-46d1-b6d3-acd1c3233885.png)
# 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](https://github.com/graphql/dataloader/issues/231) 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.
```typescript
// app.tsimport 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`.
```typescript
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](https://github.com/acro5piano/knex-graphql-utils/blob/master/demo/index.ts) and [unit tests](https://github.com/acro5piano/knex-graphql-utils/blob/master/tests/BatchLoader.test.ts).