GraphQL Relay Connections Specification for Apollo - graphql

I'm trying to implement Relay Connections in Apollo, but I have some problems with the specification.
Schema looks like this:
type PageInfo {
startCursor: String!,
endCursor: String!,
hasNextPage: Boolean!,
hasPreviousPage: Boolean!
}
type User : Node {
id: ID!
firstname: String
lastname: String
}
type UserConnection : Connection {
pageInfo: PageInfo!
edges: [UserEdge!]!
}
type UserEdge : Edge {
cursor: String!
node: User!
}
extend type Query {
users(first: Int, after: ID, order: String): UserConnection
}
Let's say I have 100 users in db;
When I query with limit ex.users(first: 10), PageInfo.endCursor refers to the 10nth edge or the 100th?
More Questions:
1.Should I make another query to get hasPreviousPage, hasNextPage.
2.Are there any code examples(with resolvers) that have a similar structure?
3.Are there any graphql chats (gitter, irc)?

endCursor refers to the 10th, i.e. it's equal to the cursor field of the last edge in the result.
Generally-speaking, cursor-based pagination like this isn't bi-directional. So you can't know both hasNextPage and hasPreviousPage. But whichever you're fetching, you don't need to do issue it in another query, fetch as much data as you can in one query, that's the point!
There must be loads of examples of Relay's pagination around by now, I don't know any off the top of my head.
There's a slack team for Apollo and for GraphQL itself. apollographql.slack.com and graphql.slack.com

Related

Overcome repetition in graphql

Suppose my graphql API allows me to manage ModelA, ModelB, ModelC.
Those models have very simple typeDefs:
type ModelA {
id: ID!
tags: [SomeComplexTagType!]
}
type ModelB {
id: ID!
tags: [SomeComplexTagType!]
}
type ModelC {
id: ID!
tags: [SomeComplexTagType!]
}
In order for users of the API to add tags to ModelA, ModelB, ModelC the following mutations are provided:
type Mutation {
addTagToModelA(id: ID!, tag: SomeComplexTagType!): Boolean
addTagToModelB(id: ID!, tag: SomeComplexTagType!): Boolean
addTagToModelC(id: ID!, tag: SomeComplexTagType!): Boolean
}
What is the graphql recommended way to get rid of the repetive nature in this API design?
As far as I understand mutations are always top level elements of the Mutation type in graphql. That means, my resolver function for addTagToModelX will never be passed an instance of ModelX as its parent, i.e. something like this will never work:
type Mutation {
ModelA(id: ID!) {
addTagToX(tag: SomeComplexTagType!): Boolean
}
ModelB(id: ID!) {
addTagToX(tag: SomeComplexTagType!): Boolean
}
ModelC(id: ID!) {
addTagToX(tag: SomeComplexTagType!): Boolean
}
}
I found a good solution while digging into the GitHub API.
Summary:
They use global node ids which allow them to conclude the object type from the ID, because the ID is base64 encoded and contains that information.
For my scenario this would mean:
2.1) Make ModelA, ModelB, ModelC implement Taggable
2.2) Introduce mutation addTagToTaggable accepting an ID from Taggable
2.3) In the resolver extract the object type from ID, query object from database, add Tag etc.
2.4) Profit

GraphQL query error -- variable is never used in operation

I am performing a request for an individual post from Apollo Server / Express backend.
In the Apollo GraphQL sandbox, the query works and retrieves the correct post, however, the query has a red squiggle identifying an error which reads -
Variable "$getPostId" is never used in operation "Query".
The query is as follows -
query Query($getPostId: ID!) {
getPost(id:"20c9b3ac-afe6-4faa-a3f9-e00ef1b38ccf") {
title
author
id
}
}
The schema is as follows -
module.exports = gql`
type Post {
id: ID!
title: String!
author: String!
}
type Query {
getPosts: [Post]!
getPost(id: ID!): Post
}
...
`
The closest post which seems to address a similar problem I could find is here. However, I can't translate the resolution to my problem.
Why is the error showing (particularly when the query runs successfully)? What needs to be done to stop the error from showing?
Many thanks!
It sounds like
query Query($getPostId: ID!) {
getPost(id:"20c9b3ac-afe6-4faa-a3f9-e00ef1b38ccf") {
title
author
id
}
}
is supposed to be
query Query($getPostId: ID!) {
getPost(id: $getPostId) {
title
author
id
}
}
Or if your query is actually meant to hard-code the ID, then you want
query Query {
getPost(id:"20c9b3ac-afe6-4faa-a3f9-e00ef1b38ccf") {
title
author
id
}
}

GraphQL query with multiple nested resolvers and mapping fields to arguments

From GraphQL Client's perspective, how do I perform a query with multiple nested resolvers where the fields from the parent are passed as arguments to the child resolver?
Here is a minimal example:
GraphQL Schema:
type Author {
id: ID!
name: String!
}
type Book {
id: ID!
title: String!
releaseDate: String!
}
type Query {
// Returns a list of Authors ordered by name, 'first' indicates how many entries to return
getAllAuthors(first: Int!): [Author]!
// Returns a list of Books ordered by releaseDate, 'first' indicates how many entries to return
getBooksByAuthorId(first: Int! authorId: ID!): [Book]!
}
Is it possible to write a query to get all authors and their last released book? Something around the lines:
query GetAuthorsWithLastBook($first: Int!) {
getAllAuthors(first: $first) {
authorId: id
name
lastBook: getBooksByAuthor(1, authorId) {
title
}
}
}
In the example above, I attempted to alias getAllAuthors.id as authorId and pass the alias down as argument to getBooksByAuthor(...) but that didn't work.
The key aspect of the problem is that I don't know the authorIds beforehand. I could fetch the authors first and build a query to fetch their last book but that will result in multiple queries and that is something I would like to avoid.
Update
A Java Kickstarter example is available here: https://www.graphql-java-kickstart.com/tools/schema-definition/
yes, on the graphql definition, you need to add lastBook in the Author
type Author {
id: ID!
name: String!
lastBook: [Book]
}
Next up u need to write the resolver for the lastBook
const resolvers = {
Query: {
Author {
lastBook: (parent, args) {
const userId = parent.id;
return getBooksByAuthor(userId, 1);
},
}
}
};

FaunaDB - How to bulk update list of entries within single graphQL mutation?

I want to bulk update list of entries with graphQL mutation in faunaDB.
The input data is list of coronavirus cases from external source. It will be updated frequently. The mutation should update existing entries if the entry name is present in collectio and create new ones if not present.
Current GRAPHQL MUTATION
mutation UpdateList($data: ListInput!) {
updateList(id: "260351229231628818", data: $data) {
title
cities {
data {
name
infected
}
}
}
}
GRAPHQL VARIABLES
{
"data": {
"title": "COVID-19",
"cities": {
"create": [
{
"id": 22,
"name": "Warsaw",
"location": {
"create": {
"lat": 52.229832,
"lng": 21.011689
}
},
"deaths": 0,
"cured": 0,
"infected": 37,
"type": "ACTIVE",
"created_timestamp": 1583671445,
"last_modified_timestamp": 1584389018
}
]
}
}
}
SCHEMA
type cityEntry {
id: Int!
name: String!
deaths: Int!
cured: Int!
infected: Int!
type: String!
created_timestamp: Int!
last_modified_timestamp: Int!
location: LatLng!
list: List
}
type LatLng {
lat: Float!
lng: Float!
}
type List {
title: String!
cities: [cityEntry] #relation
}
type Query {
items: [cityEntry!]
allCities: [cityEntry!]
cityEntriesByDeathFlag(deaths: Int!): [cityEntry!]
cityEntriesByCuredFlag(cured: Int!): [cityEntry!]
allLists: [List!]
}
Everytime the mutation runs it creates new duplicates.
What is the best way to update the list within single mutation?
my apologies for the delay, I wasn't sure exactly what the missing information was hence why I commented first :).
The Schema
An example of a part of a schema that has arguments:
type Mutation {
register(email: String!, password: String!): Account! #resolver
login(email: String!, password: String!): String! #resolver
}
When such a schema is imported in FaunaDB there will be placeholder functions provided.
The UDF parameters
As you can see all the function does is Abort with the message that the function still has to be implemented. The implementation starts with a Lambda that takes arguments and those arguments have to match what you defined in the resolver.
Query(Lambda(['email', 'password'],
... function body ...
))
Using the arguments is done with Var, that means Var('email') or Var('password') in this case. For example, in my specific case we would use the email that was passed in to get an account by email and use the password to pass on to the Login function which will return a secret (the reason I do the select here is that the return value for a GraphQL resolver has to be a valid GraphQL result (e.g. plain JSON
Query(Lambda(['email', 'password'],
Select(
['secret'],
Login(Match(Index('accountsByEmail'), Var('email')), {
password: Var('password')
})
)
))
Calling the UDF resolver via GraphQL
Finally, how to pass parameters when calling it? That should be clear from the GraphQL playground as it will provide you with the docs and autocompletion. For example, this is what the auto-generated GraphQL docs tell me after my schema import:
Which means we can call it as follows:
mutation CallLogin {
login (
email: "<some email>"
password: "<some pword>"
)
}
Bulk updates
For bulk updates, you can also pass a list of values to the User Defined Function (UDF). Let's say we would want to group a number of accounts together in a specific team via the UI and therefore want to update multiple accounts at the same time.
The mutation in our Schema could look as follows (ID's in GraphQL are similar to Strings)
type Mutation { updateAccounts(accountRefs: [ID]): [ID]! #resolver }
We could then call the mutation by providing in the id's that we receive from FaunaDB (the string, not the Ref in case you are mixing FQL and GraphQL, if you only use GraphQL, don't worry about it).
mutation {
updateAccounts(accountRefs: ["265317328423485952", "265317336075993600"] )
}
Just like before, we will have to fill in the User Defined Function that was generated by FaunaDB. A skeleton function that just takes in the array and returns it would look like:
Query(Lambda(['arr'],
Var('arr')
))
Some people might have seen an easier syntax and would be tempted to use this:
Query(Lambda(arr => arr))
However, this currently does not work with GraphQL when passing in arrays, it's a known issue that will be fixed.
The next step is to actually loop over the array. FQL is not declarative and draws inspiration from functional languages which means you would do that just by using a 'map' or a 'foreach'
Query(Lambda(["accountArray"],
Map(Var("accountArray"),
Lambda("account", Var("account")))
))
We now loop over the list but don't do anything with it yet since we just return the account in the map's body. We will now update the account and just set a value 'teamName' on there. For that we need the Update function which takes a FaunaDB Reference. GraphQL sends us strings and not references so we need to transform these ID strings to a reference with Ref as follows:
Ref(Collection('Account'), Var("account"))
If we put it all together we can add an extra attribute to a list of accounts ids as follows:
Query(Lambda(["accountArray"],
Map(Var("accountArray"),
Lambda("account",
Do(
Update(
Ref(Collection('Account'), Var("account")),
{ data: { teamName: "Awesome live-coders" } }
),
Var("account")
)
)
)
))
At the end of the Map, we just return the ID of the account again with Var("account") in order to return something that is just plain JSON, else we would be returning FaunaDB Refs which are more than just JSON and will not be accepted by the GraphQL call.
Passing in more complex types.
Sometimes you want to pass in more complex types. Let's say we have a simple todo schema.
type Todo {
title: String!
completed: Boolean!
}
And we want to set the completed value of a list of todos with specific titles to true. We can see in the extended schema generated by FaunaDB that there is a TodoInput.
If you see that extended schema you might think, "Hey that's exactly what I need!" but you can't access it when you write your mutations since you do not have that part of the schema at creation time and therefore can't just write:
type Mutation { updateTodos(todos: [TodoInput]): Boolean! #resolver }
As it will return the following error.
However, we can just add it to the schema ourselves. Fauna will just accept that you already wrote it and not override it (make sure that you keep the required fields, else your generated 'createTodo' mutation won't work anymore).
type Todo {
title: String!
completed: Boolean!
}
input TodoInput {
title: String!
completed: Boolean!
}
type Mutation { updateTodos(todos: [TodoInput]): Boolean! #resolver }
Which means that I can now write:
mutation {
updateTodos(todos: [{title: "test", completed: true}])
}
and dive into the FQL function to do things with this input.
Or if you want to include the ID along with data you can define a new type.
input TodoUpdateInput {
id: ID!
title: String!
completed: Boolean!
}
type Mutation { updateTodos(todos: [TodoUpdateInput]): Boolean! #resolver }
Once you get the hang of it and want to learn more about FQL (that's a whole different topic) we are currently writing a series of articles along with code for which the first one appeared here: https://css-tricks.com/rethinking-twitter-as-a-serverless-app/ which is probably a good gentle introduction.

Pagination in apollo-graphql server

I want to implement cursor based pagination in Apollo graphql server. I have prepared schema with pagination requirement. But i am stuck at resolver side.
Here is my schema
const typeDefinitions = `
input CreateDeveloperInput {
# An arbitrary string value with no semantic meaning. Will be included in the
# payload verbatim. May be used to track mutations by the client.
clientMutationId: String
developer: DeveloperInput!
}
type CreateDeveloperPayload {
clientMutationId: String
developerEdge(orderBy: DevelopersOrderBy = PRIMARY_KEY_ASC): DevelopersEdge
query: Query
}
input DeleteDeveloperByIdInput {
# An arbitrary string value with no semantic meaning. Will be included in the
# payload verbatim. May be used to track mutations by the client.
clientMutationId: String
id: Int!
}
input DeleteDeveloperInput {
clientMutationId: String
nodeId: ID!
}
type DeleteDeveloperPayload {
clientMutationId: String
developer: Developer
deletedDeveloperId: ID
# Our root query field type. Allows us to run any query from our mutation payload.
query: Query
}
type Developer implements Node {
nodeId: ID!
id: Int!
name: String!
place: String
salary: Int
joiningDate: String
}
input DeveloperCondition {
id: Int
name: String
place: String
salary: Int
joiningDate: String
}
input DeveloperInput {
id: Int
name: String!
place: String
salary: Int
joiningDate: String
}
input DeveloperPatch {
id: Int
name: String
place: String
salary: Int
joiningDate: String
}
type DevelopersConnection {
# Information to aid in pagination.
pageInfo: PageInfo!
totalCount: Int
edges: [DevelopersEdge]
nodes: [Developer!]
}
type DevelopersEdge {
# A cursor for use in pagination.
cursor: String
node: Developer!
}
enum DevelopersOrderBy {
PRIMARY_KEY_ASC
PRIMARY_KEY_DESC
NATURAL
ID_ASC
ID_DESC
NAME_ASC
NAME_DESC
PLACE_ASC
PLACE_DESC
SALARY_ASC
SALARY_DESC
JOINING_DATE_ASC
JOINING_DATE_DESC
}
# The root mutation type which contains root level fields which mutate data.
interface Node {
# A globally unique identifier. Can be used in various places throughout the system to identify this single value.
nodeId: ID!
}
# Information about pagination in a connection.
type PageInfo {
# When paginating forwards, are there more items?
hasNextPage: Boolean!
# When paginating backwards, are there more items?
hasPreviousPage: Boolean!
# When paginating backwards, the cursor to continue.
startCursor: String
# When paginating forwards, the cursor to continue.
endCursor: String
}
# The root query type which gives access points into the data universe.
type Query implements Node {
allDevelopers(
# Read all values in the set before (above) this cursor.
before: String,
# Read all values in the set after (below) this cursor.
after: String, first: Int, last: Int, offset: Int,
# A condition to be used in determining which values should be returned by the collection.
condition: DeveloperCondition): DevelopersConnection
# Exposes the root query type nested one level down. This is helpful for Relay 1
# which can only query top level fields if they are in a particular form.
nodeId: ID!
}
schema {
query: Query
}
`;
export default [typeDefinitions];
Is it possible to resolve in resolvers? If yes, can any one please tell me how to implement it
If you're using Mongo and Mongoose with Apollo Express GraphQL I have found three ways to do pagination:
You can create a cursor field on your schema and implement your resolver with the pagination logic, but I don't recommend this method if you have a complex schema that contain unions, interfaces and different nested objects on the type, but if you want to implement it by yourself here's the reference
You can use a module which will nest your Mongoose model and provide you an pagination interface, but this requires a few changes on your schema and resolvers, more work then the third method
You can use sort, skip and limit from Mongo / Mongoose, but if you do this by yourself you can find some issues, e.g if the document was sorted by a date key which can have different documents with the same date it can duplicate documents and mess up with your pagination, so I recommend that you install a pagination plugin on your Mongoose schema and use it like any other method from Mongoose

Resources