Schema design to avoid nested pagination - graphql

First of all, I am using a simplified version of "Relay" pagination where "List" is a basic equivalent of "connection" and "limit" is the equivalent to "first" and "cursor" is the equivalent to "after".
This is the relevant part of my schema:
extend type Query {
artist(id: ID!): Artist
artists(limit: Int!, cursor: String): ArtistList!
}
type Artist {
id: ID!
account: Account!
photos(limit: Int!, cursor: String): PhotoList!
website: String
instagram: String
facebook: String
created: Date!
updated: Date
about: String
}
type ArtistList {
nodes: [Artist]!
page: Page!
}
I want avoid nested pagination, so want permit get next nodes of photos on the query artist and not permit get next nodes of photos on the query artists (For example, permit the argument cursor of photos on query artist and ban it on query artists). But I can't see how to express that in the GraphQL schema.

There is no way to do what you want to do specifically. You could return different types from artist (e.g. ArtistWithPagination) and from artists (e.g. ArtistWithoutPagination). I am not sure if this would be worth the tradeoff of having two very similar types.
Alternatively, you could throw a runtime error when a user attempts to query the API in this undesired way (with a custom validation rule). If your GraphQL API is internal you could even develop a linter, that warns developers if they do the double pagination in their queries.

Related

Is it possible a schema type that gets the value of one property of already defined schema type

I have a schema that looks like this.
exports.typeDefs = gql`
type User {
userid: ID!
name: String
}
type Post {
post_id: ID!
post_category: String!
post_type: String!
post_hashtag: String
user: User
}
`;
Now post have the field named "post_hashtag".
I want to define another schema type and get that post_hashtag property for all the nodes in post type.
of post.
I tried the below type hashtag and put a cipher query on that.
type hashtag{
post_hashtag: String
#cypher[statement: "MATCH (n:Test_Temp) RETURN n.post_hashtag"]
}
But it returns only the one first found hashtag and save it on hashtag node. This is not what I want. I want all the hashtags that are available in any post_hashtag node.
Example: If I query
query{
hashtag{
post_hashtag
}
}
This should give all the hashtags that are available in any of the post node but instead it return only one hashtag.
I've been trying this since few days. going through different solutions but none worked.
Any suggestions Please.
I've figured out the problem. Actually, we just need to use the collect method of neo4j to return an array of hashtags and save it in the hashtag node. And it will save all the hashtags on the node and we can retrieve them later.
First, change the schema a little bit like this.
type hashtag{
post_hashtag: [String]
#cypher[statement: "MATCH (n:Test_Temp) RETURN
n.post_hashtag"]
}
Notice: I've changed the post_hashtag type and made it an array of strings.
Then just the cipher query will change by the one down below.
MATCH (n:Test_Temp) RETURN collect(n.post_hashtag)
And it is done.

GraphQL Pagination | The very first request

According to the connection based model for pagination using graphQL, I have the following simplified schema.
type User {
id: ID!
name: String!
}
type UserConnection {
totalCount: Int
pageInfo: PageInfo
edges: [UserEdge]
}
type UserEdge {
cursor: String
node: User
}
type PageInfo {
lastCursor: Int
hasNextPage: Boolean
}
type Query {
users(first: Int, after: String): UserConnection
}
Consider the following router on within SPA front-end:
/users - once the user hit this page, I'm fetching first 10 records right up from the top of the list and further I'm able to paginate by reusing a cursor that I've retrieved from the first response.
/user/52 - here I'd like to show up 10 records that should go right from the position of user52.
Problem What are the possible ways to retrieve a particular subset of records on the very first request? On this moment I don't have any cursor to construct something similar to
query GetTenUsersAfter52 {
users(first: 10, after: "????") { # struggling to pass anything as a cursor...
edges {
node {
name
}
}
}
}
What I've already tried(a possible solution) is that I know that on a back-end the cursor is encoded value of an _id of the record in the DB. So, being on /users/52 I can make an individual request for that particular user, grab the value of id, then on the front-end I can compute a cursor and pass it to the back-end in the query above.
But in this case personally, I found a couple of disadvantages:
I'm exposing the way of how my cursor is computed to the front-end, which is bad since if I needed to change that procedure I need to change it on front-end and back-end...
I don't want to make another query field for an individual user simply because I need its id to pass to the users query field.
I don't want to make 2 API calls for that as well...
This is a good example of how Relay-style pagination can be limiting. You'll hit a similar scenario with create mutations, where manually adding a created object into the cache ends up screwing up your pagination because you won't have a cursor for the created object.
As long as you're not actually using Relay client-side, one solution is to just abandon using cursors altogether. You can keep your before and after fields, but instead simply accept the id (or _id or whatever PK) value instead of a cursor. This is what I ended up doing on a recent project and it simplified things significantly.

Can one have different types for same field between Prisma GraphQL schema and datamodel?

I'm a newbie to Prisma/GraphQL. I'm writing a simple ToDo app and using Apollo Server 2 and Prisma GraphQL for the backend. I want to convert my createdAt field from the data model to something more usable on the front-end, like a UTC date string. My thought was to convert the stored value, which is a DateTime.
My datamodel.prisma has the following for the ToDo type
type ToDo {
id: ID! #id
added: DateTime! #createdAt
body: String!
title: String
user: User!
completed: Boolean! #default(value: false)
}
The added field is a DataTime. But in my schema.js I am listing that field as a String
type ToDo {
id: ID!
title: String,
added: String!
body: String!
user: User!
completed: Boolean!
}
and I convert it in my resolver
ToDo: {
added: async (parent, args) => {
const d = new Date(parent.added)
return d.toUTCString()
}
Is this OK to do? That is, have different types for the same field in the datamodel and the schema? It seems to work OK, but I didn't know if I was opening myself up to trouble down the road, following this technique in other circumstances.
If so, the one thing I was curious about is why accessing parent.added in the ToDo.added resolver doesn't start some kind of 'infinite loop' -- that is, that when you access the parent.added field it doesn't look to the resolver to resolve that field, which accesses the parent.added field, and so on. (I guess it's just clever enough not to do that?)
I've only got limited experience with Prisma, but I understand you can view it as an extra back-end GraphQL layer interfacing between your own GraphQL server and your data (i.e. the database).
Your first model (datamodel.prisma) uses enhanced Prisma syntax and directives to accurately describe your data, and is used by the Prisma layer, while the second model uses standard GraphQL syntax to implement the same object as a valid, standard GraphQL type, and is used by your own back-end.
In effect, if you looked into it, you'd see the DateTime type used by Prisma is actually a String, but is likely used by Prisma to validate date & time formats, etc., so there is no fundamental discrepancy between both models. But even if there was a discrepancy, that would be up to you as you could use resolvers to override the data you get from Prisma before returning it from your own back-end.
In short, what I'm trying to say here is that you're dealing with 2 different GraphQL layers: Prisma and your own. And while Prisma's role is to accurately represent your data as it exists in the database and to provide you with a wide collection of CRUD methods to work with that data, your own layer can (and should) be tailored to your specific needs.
As for your resolver question, parent in this context will hold the object returned by the parent resolver. Imagine you have a getTodo query at the root Query level returning a single item of type ToDo. Let's assume you resolve this to Prisma's default action to retrieve a single ToDo. According to your datamodel.prisma file, this query will resolve into an object that has an added property (which will exist in your DB as the createdAt field, as specified by the #createdAt Prisma directive). So parent.added will hold that value.
What your added resolver does is transform that original piece of data by turning it into an actual Date object and then formatting it into a UTC string, which conforms to your schema.js file where the added field is of type String!.

How to use the query for custom fields on GraphQL?

I have this schema on my graphcool:
type User #model {
id: ID! #isUnique
name: String!
email: String!
password: String!
}
Using playground, I can execute this properly:
query {
User(id: "1234") {
id
name
}
}
But this query:
query {
User(name: "Thomas") {
id
name
}
}
throws error:
Unknown argument 'name' on field 'User' of type 'Query'. (line 2,
column 8):
User(name: "Thomas").
Why? And how to fix this? From my pov, anything that's already on the model, can be queried immediately, right? Btw, I'm very newbie in graphQL, and there's almost no article talk about this error (every tutorial just like assume that this will immediately works), so please give some more elaborate answer if necessary.
GraphQL does not intrinsically allow arbitrary queries against objects.
Somewhere in your schema there will be an additional declaration like
type Query {
User(id: ID!): User
}
The names in the Query type are the top-level queries you can run, and the arguments listed in that query are the only arguments they accept. (There is a corresponding Mutation type for top-level mutations, which can change the underlying state, and use the mutation keyword in a query.)
If you control the server implementation, you could add a parameter or an additional top-level query
userByName(name: String!): User
but you'd also have to provide an implementation of this query or handle the additional parameter, which is a code change.

GraphQL: Are either of these two patterns better/worse?

I'm relatively new to GraphQL, and I've noticed that you can select related fields in one of two different ways. Let's say we have a droids table and a humans table, and droids have an owner which is a record in the humans table. There's (at least) two ways you can express this:
query DroidsQuery {
id
name
owner {
id
}
}
or:
query DroidsQuery {
id
name
ownerId # this resolves to owner.id
}
At first glance the former seems more idiomatic, and obviously if you're selecting multiple fields it has advantages (owner { id name } vs. having to make a new ownerName so you can do ownerId ownerName). However, there's a certain explicitness to the ownerId style, as you're expressing "here's this thing I specifically expected you to select".
Also, from an implementation standpoint, it seems like owner { id } would lend itself to the resolver making an unnecessary JOIN, as it would translate owner { id } as the id column of the humans table (vs. an ownerId field which, with its own resolver, knows it doesn't need a JOIN to get the owner_id column of the droids table).
As I said, I'm new to GraphQL, so I'm sure there's plenty of nuances to this question that I'd appreciate if I'd been using it longer. Therefore, I was hoping for insight from someone who has used GraphQL into the upsides/downsides of either approach. And just to be clear (and to avoid having this answer closed) I'm looking for explicit "here's what is objectively bad/good about one approach over the other", not subjective "I prefer one approach" answers.
You should understand GraphQL is just a query language + execution semantics. There are no restrictions on how you present your data and how you resolve your data.
Nothing stops you from doing what you describe, and returning both owner object and ownerId.
type Droid {
id: ID!
name: String!
owner: Human! # use it when you want to expand owner detail
ownerId: ID! # use it when you just want to get id of owner
}
You already pointed out the main problem: the former implementation seems more idiomatic. No you don't make a idiomatic code, you make practical code.
A real world example as you design field pagination in GraphQL:
type Droid {
id: ID!
name: String!
friends(first: Int, after: String): [Human]
}
The first time, you query a droid + friends, and it is fine.
{
query DroidsQuery {
id
name
friends(first: 2) {
name
}
}
}
Then, you click more to load more friends; it hits DroidsQuery one more time to query the previous droid object before resolving the next friends:
{
query DroidsQuery {
id
friends(first: 2, after: "dfasdf") {
name
}
}
}
So it is practical to have another DroidFriendsQuery query to directly resolve friends from droid id.

Resources