I'm implementing a graphql prisma datamodel. Here I have a type called BankAccount . I may need to update and delete them as well. I'm implementing this as immutable object. So, when updating I'm adding updating the existing record as IsDeleted and add a new record. And when updating an existing record I need to keep the id of the previous record to know which record is updated. So, I've came up with a type like this
type BankAccount {
id: ID! #unique
parentbankAccount: String!
bankName: String!
bankAccountNo: String!
isDeleted: Boolean! #default(value: "false")
}
Here the parentBankAccount keeps the id of previous BankAccount. I'm thinking when creating a bank account, setting the parentBankAccount as same as the id as it doesn't have a parent. The thing is I'm not sure it's possible. I'm bit new to GraphQL. So, any help would be appreciated.
Thanks
In GraphQL, generally if one object refers to another, you should directly refer to that object; you wouldn't embed its ID. You can also make fields nullable, to support the case where some relationship just doesn't exist. For this specific field, then, this would look like
type BankAccount {
parentBankAccount: BankAccount
...
}
and that field would be null whenever an account doesn't have a parent.
At an API level, the layout you describe seems a little weird. If I call
query MyBankAccount {
me { accounts { id } }
}
I'll get back some unique ID. I'd be a little surprised to later call
query MyBalance($id: ID!) {
node(id: $id) {
... on BankAccount {
name
isDeleted
balance
}
}
}
and find out that my account has been "deleted" and that the balance is from a week ago.
Using immutable objects in the underlying data store makes some sense, particularly for auditability reasons, but that tends to not be something you can expose out through a GraphQL API directly (or most other API layers: this would be equally surprising in a REST framework where the object URL is supposed to be permanent).
Related
I'm trying to run a GraphQL query in the AWS AppSync console:
query MyQuery {
getUserInfoById(id: "1234566789") {
account {
id // need this value for getAvailableCourses
}
}
getAvailableCourses(accountId: "", pageNumber: 0) {
data {
id
name
type
}
}
}
Basically I need the value account.id in getUserInfoById for getAvailableCourses. I'm obviously new to GraphQL. How would I go about this?
To the best of my knowledge, there can be two ways you can do this.
You can handle this in your frontend by getting user's id
from the session info and pass it to the other query.
You can also merge these two queries and make it one. You will also have to change the respective fields. Then attach a resolver with AvailableCourses and use $ctx.source.id in the resolver to get further details. Schema would look something like this
type Account {
id : ID!
availableCourses: AvailableCourses
..
}
type AvailableCourses {
name: String!
type: String!
..
}
type Query {
getUserInfoById(id: ID!): Account
}
Using the returned fields as inputs for a second query into your datasource is precisely what field resolvers are for. I can't say for sure since I don't know your schema or access patterns but it looks like you need to make available courses a sub field of the user.
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!.
This feels basic, so I would expect to find this scenario mentioned, but I have searched and can't find an example that matches my scenario. I have 2 end points (I am using HTTP data sources) that I'm trying to combine.
Class:
{
id: string,
students: [
<studentID1>,
<studentID2>,
...
]
}
and Student:
{
id: String,
lastName: String
}
What I would like is a schema that looks like this:
Student: {
id: ID!
lastName: String
}
Class: {
id: ID!,
studentDetails: [Student]
}
From reading, I know that I need some sort of resolver on Class.studentDetails that will return an array/List of student objects. Most of the examples I have seen show retrieving the list of Students based on class ID (ctx.source.id), but that won't work in this case. I need to call the students endpoint 1 time per student, passing in the student ID (I cannot fetch the list of students by class ID).
Is there a way to write a resolver for Class/studentDetails that loops through the student IDs in Class and calls my students endpoint for each one?
I was thinking something like this in the Request Mapping Template:
#set($studentDetails = [])
#foreach($student in $ctx.source.students)
#util.qr(list.add(...invoke web service to get student details...))
#end
$studentDetails
Edit: After reading Lisa Shon's comment below, I realized that the batch resolver for DynamoDB data sources that does this, but I don't see a way to do that for HTTP data sources.
It's not ideal, but you can create an intermediate type.
type Student {
id: ID!
lastName: String
}
type Class {
id: ID!,
studentDetails: [StudentDetails]
}
type StudentDetails {
student: Student
}
In your resolver template for Class, create a list of those student ids
#foreach ($student in $class.students)
$util.qr($studentDetails.add({"id": "$student.id"}))
#end
and add it to your response object. Then, hook a resolver to the student field of StudentDetails and you will then be able to use $context.source.id for the individual student API call. Each id will be broken out of the array and be its own web request.
I opened a case with AWS Support and was told that the only way they know to do this is to create a Lambda Resolver that:
Takes an array of student IDs
Calls the students endpoint for each one
Returns an array of student details information
Instead of calling your student endpoint in the response, use a pipeline resolver and stitch the response from different steps using stash, context (prev.result/result), etc.
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.
All of the answers I have found relate to graphql. I need to know how to update the cache on the client using apollographql.
Given this Friend type and mutation.
type Friend {
id: String
name: String
friends: [Friend]
}
type Mutation {
createFriend (
friends: [FriendInput]
): [Friend]
}
The friends array is circular. How do you represent this in the response and how do you update the clients cache?
If you're interested in the friends of a specific person, your store probably contains a bunch of Friend objects (I would actually call them Person, and friends is just a field on the Person type). For doing the mutation, it should be enough to provide the id of each friend of that new person, unless you want to create not just one person at a time in these mutations, but multiple.
For the mutation response, just include the data that you need for each friend. If you need the name and id of each of the persons friends, then include that as well. Most likely you won't need to go two levels deep, but if you want to, you can do that as well.
In Apollo Client, you don't actually need to do anything special to have this data be properly written into your store, because Apollo Client automatically normalizes by the id field and stores each friend only once. So if you're sure that you already have all the persons on the client, it will be enough to ask only for the id of each friend, so for example:
{
createFriend( friends: [{ name: 'Joe', friends: [{ id: 1}, {id: 4}] }]) {
id
name
friends {
id
name
}
}
}