How to add graphql pagination on nested object - graphql

I am writing a graphql api endpoint where I have customer details as below.
{
customer_name:
customer_address:
[
{
address_1:
},
{
address_2:
},
]
}
I need to apply pagination on customer_address which is a list.
Is this possible? Or I can do it only at top level record? Please let me know what would be the best way to do it?

You can possible by using resolver like following
input PageInput{
limit: Int!
page: Int!
}
type CustomerAddressPage {
totalCount: Int!
edges: [CustomerAddress!]!
}
type CustomerAddress {
address: String
}
type Customer {
customerName: String
customerAddress(input: PageInput!): CustomerAddressPage
}
I don't know what kind of framework you use, in nestjs you can be done as follows.
#Resolver(() => Customer)
export class CustomerResolver {
#ResolveField(() => CustomerAddressPage)
customerAddress(
#Parent() customer: Customer,
#Args('input') input: PageInput,
): Promise<CustomerAddressPage> {
return {
};
}
}

Related

Apollo Client 3: how to cache a mutation result into a nested collection? (collection within a collection)

In my Apollo Client 3 app, I am doing a mutation and want to cache the result into a collection which is nested within an item of a collection.
Specifically, I am creating a comment within a list of comments, each list within a post, each post within a list of posts. My app's data hierarchy looks like:
user 1
profile 1
post 1
comment 1.1
comment 1.2
post 2
comment 2.1
comment 2.2
< write mutation result here >
post 3
comment 3.1
comment 3.2
comment 3.3
...
In this situation, how would I best cache a created comment into its parent post's comment-collection? I am looking at the useMutation hook's update or modify config, but am not too sure.
For additional context, here is query that corresponds to the above data hierarchy:
query getUserPosts($userParams: GetUserParams!$postsPaginationParams: CursorPaginationParams!) {
user(params: $userParams) {
id
profile {
id
# ...
ownedPosts(pagination: $postsPaginationParams) {
items {
id
# ...
featuredComments {
id
primaryText
creationTimestamp
owner {
id
name
}
}
}
pagination {
# ...
}
}
}
}
}
And here is my mutation:
input CreateCommentParams {
ownerId: String!
postId: String!
primaryText: String!
}
mutation createComment($params: CreateCommentParams!) {
createComment(params: $params) {
id
owner {
id
name
}
primaryText
creationTimestamp
}
}
And here is what the useMutation is so far:
useMutation(CREATE_COMMENT_MUTATION, {
// ...
update: (cache, { data }) => {
if (data) {
const cacheId = cache.identify(data.createComment);
cache.modify({
fields: {
// ...how to update the comments array of the specific post?
}
})
}
},
})
You need to find the Post you are updating and update its featuredComments field like so:
useMutation(CREATE_COMMENT_MUTATION, {
// ...
update: (cache, { data }) => {
cache.modify({
id: cache.identify({
__typename: 'Post', // Assuming your this is the _typename of your Post type in your schema
id: postId,
}),
fields: {
featuredComments: (previous, { toReference }) => [...previous, toReference(data.createComment)]
},
}),
}),
})

Prisma cursor based pagination

Just finished the excellent tutorial on building Prisma based graphql backend. They explain how to implement first-offset pagination here https://www.howtographql.com/graphql-js/8-filtering-pagination-and-sorting/.
Now I am wondering, how to implement cursor-based pagination?
Here are my types (they are the same as in tutorial):
type User
implements Node {
id: ID!
name: String!
email: String!
password: String!
links(...): [Link!]
votes(...): [Vote!]
}
type Link
implements Node {
id: ID!
createdAt: DateTime!
description: String!
url: String!
postedBy(...): User
votes(...): [Vote!]
}
In the playground I am trying to query user information along with the links, created by the user:
{
user(where: {id:"cjimzqrshb3nf0c29z1p7km0j"}) {
email
links {
id
url
description
}
}
}
It gives me back all the links, created by the user. How can I paginate them? Links object does not have paging information while linksConnection does not fit inside user object.
You can query the linksConnection related to the user, and therefore access cursors :
{
linksConnection(where:{user:{id:"cjimzqrshb3nf0c29z1p7km0j"}}){
pageInfo{
endCursor
startCursor
}
edges{
cursor
}
}
}
You can implement cursor based pagination with Prisma like this:
{
users{
links(first: 10, after:"some-id") {
description
}
}
}
This is possible because the id and the cursor is the same.
Alternatively you can use offset based pagination like this:
{
users{
links(first: 10, skip: 30) {
description
}
}
}

Enumerating all fields from a GraphQL query

Given a GraphQL schema and resolvers for Apollo Server, and a GraphQL query, is there a way to create a collection of all requested fields (in an Object or a Map) in the resolver function?
For a simple query, it's easy to recreate this collection from the info argument of the resolver.
Given a schema:
type User {
id: Int!
username: String!
roles: [Role!]!
}
type Role {
id: Int!
name: String!
description: String
}
schema {
query: Query
}
type Query {
getUser(id: Int!): User!
}
and a resolver:
Query: {
getUser: (root, args, context, info) => {
console.log(infoParser(info))
return db.Users.findOne({ id: args.id })
}
}
with a simple recursive infoParser function like this:
function infoParser (info) {
const fields = {}
info.fieldNodes.forEach(node => {
parseSelectionSet(node.selectionSet.selections, fields)
})
return fields
}
function parseSelectionSet (selections, fields) {
selections.forEach(selection => {
const name = selection.name.value
fields[name] = selection.selectionSet
? parseSelectionSet(selection.selectionSet.selections, {})
: true
})
return fields
}
The following query results in this log:
{
getUser(id: 1) {
id
username
roles {
name
}
}
}
=> { id: true, username: true, roles: { name: true } }
Things get pretty ugly pretty soon, for example when you use fragments in the query:
fragment UserInfo on User {
id
username
roles {
name
}
}
{
getUser(id: 1) {
...UserInfo
username
roles {
description
}
}
}
GraphQL engine correctly ignores duplicates, (deeply) merges etc. queried fields on execution, but it is not reflected in the info argument. When you add unions and inline fragments it just gets hairier.
Is there a way to construct a collection of all fields requested in a query, taking in account advanced querying capabilities of GraphQL?
Info about the info argument can be found on the Apollo docs site and in the graphql-js Github repo.
I know it has been a while but in case anyone ends up here, there is an npm package called graphql-list-fields by Jake Pusareti that does this. It handles fragments and skip and include directives.
you can also check the code here.

Relation GraphQL

Trying to implement join but always getting null.
type User {
id: Int!
username: String!
recipes: [Recipe]
}
type Recipe {
id: Int!
title: String!
author: User
}
So basically i want to get data something like this:
User {
username,
recipes: [{//recipe}, {//recipe}]
}
And for Recipe i expecting
Recipe {
title,
author: {//user}
}
So i have query like below, and i want to get all recipes from database with included user
type Query {
recipes: [Recipe!]!
}
Here is my GraphiQL query
{
recipes {
id,
author {
id,
username
}
}
}
But in response i have author: null
{
"data": {
"recipes": [
{
"id": 1,
"author": null
}]
}
}
Any suggestions? Thanks!
Maybe somebody will face with similar issue.
Have fixed this. As said #Daniel Rearden in comments - yes issue was in resolver.
So you have to add this fields to resolver:
const resolverFunctions = {
User: {
recipes(author) {
return author.getRecipes();
}
},
Recipe: {
author(recipe) {
return recipe.getUser();
}
}
}
After you will get data what i needed above.

Any reason I am getting back the query name in the GraphQL results?

Using the makeExecutableSchema with the following Query definition:
# Interface for simple presence in front-end.
type AccountType {
email: Email!
firstName: String!
lastName: String!
}
# The Root Query
type Query {
# Get's the account per ID or with an authToken.
getAccount(
email: Email
) : AccountType!
}
schema {
query: Query
}
And the following resolver:
export default {
Query: {
async getAccount(_, {email}, { authToken }) {
/**
* Authentication
*/
//const user = security.requireAuth(authToken)
/**
* Resolution
*/
const account = await accounts.find({email})
if (account.length !== 1) {
throw new GraphQLError('No account was found with the given email.', GraphQLError.codes.GRAPHQL_NOT_FOUND)
}
return account
}
}
}
When I query with:
query {
getAccount(email: "test#testing.com") {
firstName
lastName
}
}
I am getting the following result in GraphiQL:
{
"data": {
"getAccount": {
"firstName": "John",
"lastName": "Doe"
}
}
}
So, any reason I am getting this "getAccount" back in the result?
Because getAccount is not a query name. It's just a regular field on the root query type Query.
And having results on the exact same shape as the query is one of the core design principles of GraphQL:
Screenshot from http://graphql.org/ site
Query name in GraphQL goes after query keyword:
query myQueryName {
getAccount(email: "test#testing.com") {
firstName
lastName
}
}

Resources