Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tutorial - Update Link.comments return type #3545

Merged
merged 3 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions examples/hackernews/src/schema/base/resolvers/Link.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import type { LinkResolvers } from '../../types.generated';

export const Link: LinkResolvers = {
comments: async (parent, _arg, context) => {
const comments = await context.prisma.comment.findMany({
comments: (parent, _arg, context) => {
return context.prisma.comment.findMany({
orderBy: { createdAt: 'desc' },
where: {
linkId: parent.id,
},
});
if (comments.length === 0) {
return null;
}
return comments;
},
};
2 changes: 1 addition & 1 deletion examples/hackernews/src/schema/base/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type Link {
id: ID!
description: String!
url: String!
comments: [Comment!]
comments: [Comment!]!
}

type Comment {
Expand Down
44 changes: 30 additions & 14 deletions website/src/pages/tutorial/basic/08-graph-relations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -392,22 +392,42 @@ type Link {
id: ID!
description: String!
url: String!
comments: [Comment!]
comments: [Comment!]!
}
```

On the Prisma API level, the method call for loading will always return an array. It does not
distinguish between an empty array and an array with comments.

On the GraphQL schema, we could follow the same practice, and make the comments array non-nullable
(`[Comment!]!`).
On the GraphQL schema, we have three options:

In our example, we've choosen the nullable option, as it indicates the API the need to handle that
case, when they will generate the types on their end.
1. `comments: [Comment]`
2. `comments: [Comment!]`
3. `comments: [Comment!]!`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We even have a fourth option: comments: [Comment]!. I don't know if it's worth explaining this one too ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what would you write about that?
can you add a text similar to that?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option 4, [Comment]!, shares semantics with option 1 and 3. Like 1 the array elements can be null or Comment, but like 3, the field itself will always be an array, never null. This type formulation is rarely used because simply omitting a value from the array is typically sufficient to indicate its non-valueless.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would maybe alter the documentation here to talk about two aspects which cut across all four combinations:

  1. nullable array elements
  2. nullable array

After talking about those two aspects, one can simply reference which aspects apply to which combinations.

Then, a final word about which formulation is the most popular and why ([X!]! because it provides the simplest way to sufficiently model empty states blah blah).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shit I missed your comment!
can you create a short PR just with some words?
I can then take the time to turn it into something readable


Up next, you implement the corresponding `Link.comments` resolver. For this, you need to touch the
`Link` object types resolver map. Because of the nullable type, we will first check if there are any
comments and if there are none, we'll return null.
Let's try to explain the differences between the options. We'll also describe the Typescript return
value of each option (which will be automatically generated in the later Codegen chapter).

Option 1, `[Comment]`, means that each element of the array can be Comment or null and the array
itself can be null. The Typescript return type of that resolver would be
`Array<Comment | null> | null`. It means there are too many ways and cases to describe when we
return an empty array. The client will have to deal with many cases.

Option 2, `[Comment!]`, means each element of the array must be Comment, but the array can return
empty or null. The Typescript return type of that resolver would be `Array<Comment> | null`. If the
backend returns null or an empty array [], they can both mean
`there's no Comment related to the Link`. Meaning we have two ways to represent the same concept -
which makes it harder for the client to handle that case.

Option 3, `[Comment!]!`, means each element of the array must be Comment. The Typescript return type
for of resolver would be `Array<Comment>`. With `[Comment!]!`, the server can only return an empty
array `[]`. This means it's easier for the client to handle because there's only one way to
represent the `there's no Comment related to the Link` concept.

For the reasons above we'll chose option `3` for our implementation.

Now let's implement the corresponding `Link.comments` resolver. For this, you need to touch the
`Link` object types resolver map.

As we also have the createdAt field on comments, let's sort the comments by that field when we
return them to the user.
Expand All @@ -419,17 +439,13 @@ const resolvers = {
// ... other resolver maps ...
Link: {
// ... other field resolver functions
async comments(parent: Link, args: {}, context: GraphQLContext) {
const comments = await context.prisma.comment.findMany({
comments: (parent: Link, args: {}, context: GraphQLContext) => {
return context.prisma.comment.findMany({
orderBy: { createdAt: 'desc' },
where: {
linkId: parent.id
}
})
if (comments.length === 0) {
return null
}
return comments
}
}
// ... other resolver maps ...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type Link {
id: ID!
description: String!
url: String!
comments: [Comment!]
comments: [Comment!]!
}

type Comment {
Expand Down Expand Up @@ -230,17 +230,13 @@ const resolvers = {
id: (parent: Link) => parent.id,
description: (parent: Link) => parent.description,
url: (parent: Link) => parent.url,
async comments(parent: Link, args: {}, context: GraphQLContext) {
const comments = await context.prisma.comment.findMany({
comments: (parent: Link, args: {}, context: GraphQLContext) => {
return context.prisma.comment.findMany({
orderBy: { createdAt: 'desc' },
where: {
linkId: parent.id
}
})
if (comments.length === 0) {
return null
}
return comments
}
}
}
Expand Down Expand Up @@ -303,17 +299,13 @@ Now, you can simply remove the comments, and migrate the existing `comments` res
import type { LinkResolvers } from './../../types.generated'

export const Link: LinkResolvers = {
async comments: async (parent, _arg, context) => {
const comments = await context.prisma.comment.findMany({
comments: (parent, _arg, context) => {
return context.prisma.comment.findMany({
orderBy: { createdAt: 'desc' },
where: {
linkId: parent.id
}
})
if (comments.length === 0) {
return null
}
return comments
}
}
```
Expand Down
Loading