I've been looking all over for an answer to this and I've been banging my head on the wall. I wrote a cursor based pagination example that works well with graphql and the thing is I thought I would do the same thing with authors, that I did with books and the only way I can figure out how to do this is to completely duplicate everything. On the root query there is quite a long chunk of code handling the pagination and I would hate to do that all over for the authors endpoint but I can't seem to find a way to do this while reusing the code
Here is the code
const express = require('express')
const { graphqlHTTP } = require('express-graphql')
const {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLList,
GraphQLInt,
GraphQLNonNull
} = require('graphql')
const {
PageType,
convertNodeToCursor,
convertCursorToNodeId
} = require('./pagination')
const app = express()
const authors = [
{ id: 1, name: "Author 1"},
{ id: 2, name: "Author 2"},
{ id: 3, name: "Author 3"}
]
const books = [
{ id: 1, title: "Book 1", authorId: 1 },
{ id: 2, title: "Book 2", authorId: 1 },
{ id: 3, title: "Book 3", authorId: 1 },
{ id: 4, title: "Book 4", authorId: 2 },
{ id: 5, title: "Book 5", authorId: 2 },
{ id: 6, title: "Book 6", authorId: 2 },
{ id: 7, title: "Book 7", authorId: 3 },
{ id: 8, title: "Book 8", authorId: 3 },
{ id: 9, title: "Book 9", authorId: 3 }
]
const Book = new GraphQLObjectType({
name: 'Book',
description: 'this is a book',
fields: () => ({
id: { type: GraphQLNonNull(GraphQLInt) },
title: { type: GraphQLNonNull(GraphQLString) },
authorId: { type: GraphQLNonNull(GraphQLInt) },
author: {
type: Author,
resolve: ({authorId}) => {
return authors.find(author => author.id === authorId)
}
}
})
})
const Author = new GraphQLObjectType({
name: 'Author',
description: 'this represents the author of a book',
fields: () => ({
id: { type: GraphQLNonNull(GraphQLInt) },
name: { type: GraphQLNonNull(GraphQLString) },
books: {
type: GraphQLList(Book),
resolve: ({id}) => {
return books.filter(book => book.authorId === id)
}
}
})
})
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
description: 'this is the root query',
fields: () => ({
book: {
type: Book,
description: 'a single book',
args: {
id: { type: GraphQLInt }
},
resolve: (_, { id }) => {
return books.find(book => book.id === id)
}
},
author: {
type: Author,
description: 'a single author',
args: {
id: { type: GraphQLInt },
},
resolve: (_, { id }) => {
return authors.find(author => author.id === id)
}
},
books: {
type: PageType(Book),
description: 'a list of books',
args: {
first: { type: GraphQLInt },
afterCursor: { type: GraphQLString }
},
resolve: (_, { first, afterCursor }) => {
let afterIndex = 0
if (typeof afterCursor === 'string') {
let nodeId = convertCursorToNodeId(afterCursor)
let nodeIndex = books.findIndex(book => book.id === nodeId)
if (nodeIndex >= 0) {
afterIndex = nodeIndex + 1
}
}
const slicedData = books.slice(afterIndex, afterIndex + first)
console.log('sliced data: ', slicedData)
const edges = slicedData.map(node => ({
node,
cursor: convertNodeToCursor(node)
}))
let startCursor = null
let endCursor = null
if (edges.length > 0) {
startCursor = convertNodeToCursor(edges[0].node)
endCursor = convertNodeToCursor(edges[edges.length - 1].node)
}
let hasNextPage = books.length > afterIndex + first
return {
totalCount: books.length,
edges,
pageInfo: {
startCursor,
endCursor,
hasNextPage
}
}
}
}
})
})
const schema = new GraphQLSchema({
query: RootQuery
})
app.use('/graphql', graphqlHTTP({
schema,
graphiql: true
}))
app.listen(3000, () => console.log('app running at http://localhost:3000/graphql'))
and I handle the pagination in another file here:
const {
GraphQLString,
GraphQLInt,
GraphQLBoolean,
GraphQLObjectType,
GraphQLList,
} = require('graphql')
const Edge = (itemType) => {
return new GraphQLObjectType({
name: 'EdgeType',
fields: () => ({
node: { type: itemType },
cursor: { type: GraphQLString }
})
})
}
const PageInfo = new GraphQLObjectType({
name: 'PageInfoType',
fields: () => ({
startCursor: { type: GraphQLString },
endCursor: { type: GraphQLString },
hasNextPage: { type: GraphQLBoolean }
})
})
const PageType = (itemType) => {
return new GraphQLObjectType({
name: 'PageType',
fields: () => ({
totalCount: { type: GraphQLInt },
edges: { type: new GraphQLList(Edge(itemType)) },
pageInfo: { type: PageInfo }
})
})
}
const convertNodeToCursor = (node) => {
// Encoding the cursor value to Base 64 as suggested in GraphQL documentation
return Buffer.from((node.id).toString()).toString('base64')
}
const convertCursorToNodeId = (cursor) => {
// Decoding the cursor value from Base 64 to integer
return parseInt(Buffer.from(cursor, 'base64').toString('ascii'))
}
module.exports = {
PageType,
convertNodeToCursor,
convertCursorToNodeId
}
Now if I copy and paste the books endpoint and change it to authors, and change the type to PageType(Author) then I get another error:
Schema must contain uniquely named types but contains multiple types named "PageType".
So this clearly isn't a solution either