GraphQL with apollo-client, Is it possible to have same id's and different typenames - graphql

I had a question and can't find it in documentation.
For example I have a list
products with ids 1,2,3,4
And I have another list, categories with ids 1,2,3,4.
example query
{
products {
id
name
categories {
id
name
}
}
}
We can see that they both have same ids but different typename inside apollo.
Will it create any problem while caching the data? As Apollo normalizes our data with the id's,
Help would be appreciated.

Apollo normalizes using both the __typename and id (or _id) fields, so having a Product and a Category with the same id will normally not cause any problems.
The client normally appends the __typename for every selection set in your query -- so you do not have to actually add the __typename field yourself.
The config object passed to InMemoryCache includes a addTypename property, which defaults to true. If you set this to false, then the __typename field will not be added and you will see issues with the cache in this scenario unless you have universally unique IDs.

Related

Graphql- How to fetch result based on the condition of a field?

I have a query that look like this:
query MyQuery {
products {
edges {
node {
featured
id
image {
altText
mediaItemUrl
slug
}
productId
name
onSale
}
}
}
}
What I want is only fetch the result that featured field is true, if the featured is false, then it never shown in the result.
Something like query like below in mysql:
SELECT id,image,name, featured FROM products WHERE featured = 'false'
But in graphql query above, I can't query the featured = false.
I tried:
query MyQuery {
products {
edges {
node {
featured #include(if: false)
id
... other field I need
}
}
}
}
But what this query do is, if featured field is true, then included the featured field in the result, else don't included the field in the result.This is not what I want.
What I want is,
If featured field of a product is true, then include the products into the result, else, remove the whole product from the result.
How can I achieve this in the MyQuery above?
The #include and #skip directives are used for field selection, not filtering. GraphQL has no built-in way of doing filtering, sorting or pagination. It's up to each individual service to implement these features by exposing the appropriate arguments.
In this case, products could expose an argument named filter or isFeatured to add the ability to filter the results by the featured value. The field's resolver should then use that argument value to determine the correct value to return.
If you're writing client queries and consuming a schema you did not write, check your API's documentation to determine what arguments are available for the products field. If the schema doesn't expose this capability and you don't have a way to change it, then as a client you don't have many options. At best, you can handle the filtering yourself after the result is fetched, but this is troublesome if you also use pagination.

Data normalization in GraphQL query

I'm using GraphQL to query a database that has two data types: User and Group.
Groups have a field users which is an array of User objects which are in that group. I have one field at root named groups which returns an array of all of my groups.
A typical query might look something like this:
{
groups {
id,
name,
users {
id,
name,
address,
email,
phone,
attitude,
job,
favoriteQuote,
favoriteColor,
birthday
}
}
}
The problem is that a lot of those users can belong to multiple groups and, seeing as User has a lot of fields, this can make responses quite large.
Is there any way to get one set of fields for the first instance of an object, and a different set for every other instance in the response?
I only need name, job, email etc etc once per user in the response, and just the id thereafter (I can do my own normalization afterwards).
alternatively
Is there any way to only get id fields for all users in groups and return a separate array of all unique User objects that have been referenced in the query (which is not all User objects)?
Is there any way to get one set of fields for the first instance of an object, and a different set for every other instance in the response?
No. The same set of fields will be returned for each item in a list unless the type of the individual item is different, since a separate selection set can be specified for each type returned at runtime.
Is there any way to only get id fields for all users in groups and return a separate array of all unique User objects that have been referenced in the query (which is not all User objects)?
You could design your schema to accommodate this. Something like
{
groups {
nodes {
id
name
users {
id
}
}
uniqueUsers {
id
# other fields
}
}
}
Your groups resolver would need to handle all the normalization and return the data in the appropriate shape. However, a simpler solution might be to just invert your relationship:
{
users {
id
name
address
email
phone
attitude
job
favoriteQuote
favoriteColor
birthday
groups {
id
name
}
}
}
Generally - usually
... normalization ... of course ... f.e. using apollo and it's normalized cache.
All records returned from API has to be the same shape.
You can get data and render some <MembersList/> component using query for ids and names only (full/paginated).
Later you can render details in some <UserProfile/> component with own query (hook useQuery inside) to fetch additional data from cache/api (controllable).
Your specific requirements - possible
1st option:
Usually response is of one common shape (as requested), but you can decide on resolver level what to return. This requires query structure changes that allows (API, backend) to null-ify some properties. F.e.
group {
id
name
users {
id
name
profile {
photo
email
address
With profile custom json type ... you can construct users resolver to return full data only for 1st record and null for all following users.
2nd option:
You can use 2 slightly different queries in one request. Use aliases (see docs), in short:
groupWithFullMember: group ( groupId:xxx, limitUsers:1 ) {
id
name
users {
id
name
address
email
...
}
}
groupMembers: group ( groupId:xxx ) {
id
name // not required
users {
id
name
}
}
Group resolver can return it's child users ... or users resolver can access limitUsers param to limit response/modify db query.

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.

How to update Saleor's Graphql responses with newly added DB table fields?

First, Saleor with GraphQL is fantastic. Just love it.
The products we are selling have additional metadata we need to get from Graphql. Out of the box, the Graphql queries work fine, such as:
{
product (id: "UHJvZHVjdDo3Mg==") {
id
name
description
}
}
What I need to do is expose data from my products table with additional columns, such as productInfo1, productInfo2, and productInfo3. This part is easy of course.
However, I am struggling with how to update the Saleor Graphql so I can run a query like the following:
{
product (id: "UHJvZHVjdDo3Mg==") {
id
name
description {
productInfo1
productInfo2
productInfo3
}
}
}
I have been through the Saleor docs, Stack Overflow, and a variety of blogs... I've attempted some logical approaches myself, without any success.
I'm eager to start working on these types of updates for our needs here. Any suggestions or links to "how to" locations would be greatly appreciated!
If you'd like to add subfields to description there is a couple of things you have to do:
Create new description object type which contains the subfields you want, e.g.:
class ProductDescription(graphene.ObjectType):
productInfo1 = graphene.String()
productInfo2 = graphene.String()
productInfo3 = graphene.String()
Set the description field with the new type under Product type:
class Product(CountableDjangoObjectType):
...
description = graphene.Field(ProductDescription)
Add resolver for description under Product type:
def resolve_description(self, info):
return ProductDescription(
productInfo1=self.description,
productInfo2='Some additional info',
productInfo3='Some more additional info',
)
Saleor's GraphQL API is based on the Graphene framework. You can find more about resolvers and object types here: https://docs.graphene-python.org/en/latest/types/objecttypes/#resolvers.

Apollo/React mutating two related tables

Say I have two tables, one containing products and the other containing prices.
In Graphql the query might look like this:
option {
id
price {
id
optionID
price
date
}
description
}
I present the user with a single form (in React) where they can enter the product detail and price at the same time.
When they submit the form I need to create an entry in the "product" table and then create a related entry in the "price" table.
I'm very new to Graphql, and React for that matter, and am finding it a steep learning curve and have been following an Apollo tutorial and reading docs but so far the solution to this task is remaining a mystery!
Could someone put me out of my misery and give me, or point me in the direction of, the simplest example of handling the mutations necessary for this?
Long story short, that's something that should actually be handled by your server if you want to optimize for as few requests as possible.
Problem: The issue here is that you have a dependency. You need the product to be created first and then with that product's ID, relate that to a new price.
Solution: The best way to implement this on the server is by adding another field to Product in your mutation input that allows you to input the details for Price as well in the same request input. This is called a "nested create" on Scaphold.
For example:
// Mutation
mutation CreateProduct ($input: CreateProductInput!) {
createProduct(input: $input) {
changedProduct {
id
name
price {
id
amount
}
}
}
}
// Variables
{
input: {
name: "My First Product",
price: {
amount: 1000
}
}
}
Then, on the server, you can parse out the price object in your resolver arguments and create the new price object while creating the product. Meanwhile, you can also relate them in one go on the server as well.
Hope this helps!

Resources