How to use null safety of data model classes with graphql? - graphql

Let's assume we have the following graphql schema:
type Query {
getPost(id: ID!): Post!
getUserProfile(id: ID!): User!
}
type User {
id: ID!
name: String!
gender: String!
dob: Date!
}
type Post {
id: ID!
creator: User!
caption: String!
}
Here are the graphql queries that we are making on frontend:
getPost(id:"pid1") {
id
creator {
id
name
}
}
getUserProfile(id: "uid1") {
id
name
gender
dob
}
This is the data model class on the frontend side with which we are deserialising the server response:
class UserModel {
String id;
String name;
String? gender; // ? means nullable
String? dob; // ? means nullable
}
My issue is that we have to mark the gender & dob fields as nullable because we aren't fetching them in the getPost query hence absence of them in the server response will make them null. However, gender and dob are always non-nullable in the backend as well as in the GQL API contract defined above. So we cannot use the null-safety feature of the client-side language for these fields and are forced to check if there are null or not. Is there a workaround or better way to treat such fields as null safe?

Related

Apollo GraphiQL browser playground: How to nest queries?

Wish to run a nested query within as Apollo GraphiQL browser playground.
Schema for two queries intended to be nested and a response type:
getByMaxResults(maxResults: Int = null): [ID!]!
appById(id: ID!): AppOutputGraphType
AppOutputGraphType = {
accountIdGuid: ID!
createdAt: DateTime!
description: String!
id: ID!
updatedAt: DateTime!
userId: ID
}
Query getByMaxResults: [ID!]!, the result is an array of element ID:
Query appById(id: ID!) requesting a couple of AppOutputGraphType fields:
Is there a way within the GraphiQL browser sandbox to nest these two Queries?:
query getByMaxResults gather an array of IDs.
Iterate this array and perform multiple queries appById with each ID.
Thank you
Change your definition of the getByMaxResults query to:
getByMaxResults: [AppOutputGraphType!]!
Change your resolver to return an array containing all the fields of the AppOutputGraphType objects and not just the id.
Then just:
query {
getByMaxResults {
id
updatedAt
}
}
There is no way in GraphQL to nest queries but the whole point of the language is to remove the need to do so.
With the proper resolvers you could even get the related account and user for each AppOutputGraphType.
Change:
AppOutputGraphType = {
account: Account <= don't include foreign keys, refer to the related type
createdAt: DateTime!
description: String!
id: ID!
updatedAt: DateTime!
user: User <= don't include foreign keys, refer to the related type
}
Then you can do queries like:
query {
getByMaxResults {
id
updatedAt
account {{
id
name
city
}
user {
id
firstName
lastName
}
}
}

GraphQl One-Direction Relationship

I'd like my user to have a list of workouts (they can be active, completed, todo). Do I have to make a link from Workout to user in order to make this work? Is there no other way? Having a link back seems weird because I'd have a loop user>workout>user>workout...
I'm not sure if this is supposed to be done in graphql, I'm new to graphql. I currently have the following schema:
type User {
email: String #unique
fullName: String
birthDate: Date
weight: Int
height: Int
country: String
trainingFrequency: Int
trainingGoal: String
isOnboarded: Boolean
workouts: [Workout]
}
type Workout {
name: String!
state: State!
}
enum State {
ACTIVE
TODO
COMPLETED
}
type Query {
allUsers: [User!]!
findUserByEmail(email: String!): User
}
With Fauna, a two way relationship is always created for you when you use the #relation directive. In a one-to-many relationship a reference will be stored in all of the many-side Documents (Workout type in your case). Traversing the graph from the one-side (User type) to the many-side is made possible with an Index that is automatically generated for you.
Yes, you can query (nearly) infinitely cyclically, but in practice, there is no benefit to it.
Make sure you include the #relation directive.
type User {
email: String #unique
fullName: String
birthDate: Date
weight: Int
height: Int
country: String
trainingFrequency: Int
trainingGoal: String
isOnboarded: Boolean
workouts: [Workout] #relation
}
type Workout {
name: String!
state: State!
owner: User! #relation
}

Apollo and Prisma Graphql explicit model relationship not queryable

I have begun testing out prisma 2 and graphql in general for a new application. I am running into an issue with an explicit many to many table on being able to query relations.
Here is my apollo schema:
scalar DateTime
type Query {
user(id: String!): User
users: [User]
spaces: [Space]
roles: [Role]
}
type Mutation {
createUser(id: String!, email: String!): User!
createSpace(name: String!): Space!
}
type User {
id: ID!
email: String!
spaces: [UserSpace!]
createdAt: DateTime!
updatedAt: DateTime!
}
type Space {
id: ID!
name: String!
users: [UserSpace!]
createdAt: DateTime!
updatedAt: DateTime!
}
type Role {
id: ID!
name: String!
description: String!
users: UserSpace
createdAt: DateTime!
updatedAt: DateTime!
}
type UserSpace {
id: ID!
user: User!
space: Space!
role: Role!
createdAt: DateTime!
updatedAt: DateTime!
}
Here is my prisma schema:
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// npx prisma migrate dev
// npx prisma generate
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String #id
email String #unique
spaces UserSpace[]
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
model Space {
id Int #default(autoincrement()) #id
name String #unique
users UserSpace[]
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
model Role {
id Int #default(autoincrement()) #id
name String #unique
description String
users UserSpace[]
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
model UserSpace {
id Int #default(autoincrement()) #id
user User #relation(fields: [userId], references: [id])
userId String
space Space #relation(fields: [spaceId], references: [id])
spaceId Int
role Role #relation(fields: [roleId], references: [id])
roleId Int
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
Here is my mutations resolver:
const { prisma } = require(".prisma/client");
async function createUser(parent, args, context, info) {
return await context.prisma.user.create({
data: {
...args,
},
});
}
async function createSpace(parent, args, context, info) {
const isAuthenticated = context.authentication.isAuthenticated;
let role = null;
if(!isAuthenticated) {
throw new Error("Not Authenticated");
}
try {
role = await context.prisma.role.findUnique({
where: {
name: "Space Administrator",
},
});
}
catch(err) {
throw new Error(err);
}
return await context.prisma.space.create({
data: {
...args,
users: {
create: {
role: {
connect: { id: role.id },
},
user: {
connect: { id: context.authentication.auth0Id },
},
},
},
},
});
}
module.exports = {
createUser,
createSpace,
}
Here is my user resolver (I know this is where the problem is however I do not know how to solve the issue):
function spaces(parent, args, context, info) {
return context.prisma.user.findUnique({ where: { id: parent.id } }).spaces();
}
module.exports = {
spaces,
}
Basically when I create the space the user is added as a Space Administrator to the space and then should be able to be queried with the following:
query {
users {
id
email
spaces {
id
role {
name
}
space {
name
}
}
createdAt
}
}
However when I run the query I get the following error:
"message": "Cannot return null for non-nullable field UserSpace.role.",
How in prisma 2 do I make the resolver for the users work with an explicit many to many table and how it has the third relation in there? I am new to prisma and graphql so if there anything else that stands out also I would like to have the input.
I'm using the word type to refer to object-models in your GraphQL schema and model to refer to data-models in your Prisma Schema.
The Problem
I see that you have a User type resolver, that has a resolver function for User.spaces field in your User type. The query that you have defined in your User.spaces resolver will return the relevant userSpace records from the database.
However, these userSpace records do not by default resolve the role field, as it is a relation field. This is how prisma works (relation fields are not resolved by default, unless explicitly stated).
Solution
Create a resolver for the UserSpace type and explicitly define the the resolver function for UserSpace.role field. This is what it will look like
// UserSpace resolver module
function role(parent, args, context, info) {
return context.prisma.userSpace.findUnique({ where: { id: parent.id } }).role();
}
module.exports = {
role,
}
While there are some other ways to solve this problem, the way I have shown (along with the specific syntax) is recommended because under the hood it allows prisma to perform certain optimizations to solve the n+1 query problem. But, if you don't know what that is, you don't necessarily need to worry about it either.
Did you provide the value to users arg? like this: users(id: "string_value"). Because is id is required.

Using Apollo Federated GraphQL, is there a way to sort/paginate on a value from an external entity?

Is it possible to mark a field in an input to a query as requiring a field extended by a different service?
Using the federation-demo to illustrate my question, but keeping a little bit more simple. Having just an Account service and a Reviews service, and adding a karma field to the User, is it possible to filter reviews based on User karma.
Account service, adding a karma int to a User:
extend type Query {
me: User
}
type User #key(fields: "id") {
id: ID!
name: String
username: String
karma: Int!
}
Reviews service, adding a reviews Query:
extend type Query {
reviews(minKarma: Int): [Review!]!
}
type Review #key(fields: "id") {
id: ID!
body: String
author: User #provides(fields: "username")
product: Product
}
extend type User #key(fields: "id") {
id: ID! #external
username: String #external
karma: Int! #external
reviews: [Review]
}
extend type Product #key(fields: "upc") {
upc: String! #external
reviews: [Review]
}
In my resolver for Query.reviews, I want to filter out any review where review.author.karma is less than the specified minKarma.
How do I tell the gateway that when minKarma is specified in the Query input, I want the Account service to be queried first and a representation of users to be passed into the Reviews service, with the karma of each user attached to the review as the author, so that I can do the filter?
Circling back to the question at the top of this post, can I mark the minKarma field as requiring User.karma?
This is the questions plaguing me as well.

GraphQL, Apollo Server Federated schema

I get this error while querying:
"Cannot return null for non-nullable field Transaction.createdAt."
This is the query:
query getMyTransactions {
getMyTransactions {
id
createdAt
}
}
The schema for this query is:
extend type Transaction #key(fields: "id") {
id: ID! #external
}
type Query {
getMyTransactions: [Transaction!]!
}
And the other schema has Transaction type:
type Transaction #key(fields: "id") {
id: ID!
createdAt: String!
}
What's the problem?
EDIT: If I query for:
getMyTransactions {
id
}
Works ok I and get all the id of the query, but if I include another attribute the query fails.
In simple words - you declare createdAt type to be non-null value, but somewhere in your data createdAt is null.
createdAt: String!
! after the type name. This means that our server always expects to
return a non-null value for this field, and if it ends up getting a
null value that will actually trigger a GraphQL execution error,
letting the client know that something has gone wrong. GraphQl docs
Example
For this remote/local data (createdAt missing from the first object):
const Transaction = [
{
id: "1",
/* createdAt: "01/09/2020" */ /* missing from the data */
},
{
id: "2",
createdAt: "02/09/2020"
}
];
Executing query
query{
getMyTransactions {
id
createdAt
}
}
throw error:
"message": "Cannot return null for non-nullable field
Transaction.createdAt.",
To solve this:
Option 1: Add some resolver logic and/or add validation related to your required data.
Option 2: Remove the require from createdAt (return null allowed):

Resources