Graphql query only not null objects - express

im trying to perform a query like this:
{
people{
pet{
name
}
}
}
result:
{
"people": {
"pet": null
}
},
{
"people": {
"pet": {
name: "steve"
}
}
}
What i want is to get only people that contains a pet, is there any way to achieve this not coding on my resolver ?

Actually, it is possible with the filter: { pet: {ne: null} } filtering:
query allPeople(filter: { people: { pet: {ne: null} } }) {
people {
pet
}
}

This is not possible the way you describe. GraphQL will call resolve functions to fetch the data. If you don't want certain data in your response, you have to filter it somewhere on the server. The only thing you have control over is the query, the schema and the resolve functions.
There is no way to express your requirement purely in the query. If you put it in the schema, you would no longer be able to query for people without pets. So the only way to do it is to write it in your resolve function. You could for example add a boolean argument called hasPet to your people field, and do this in the resolver:
people(root, { hasPet }){
// get allPeople
if (typeof hasPet === 'undefined'){
return allPeople
}
return allPeople.filter((person) => person.hasPet() === hasPet)
}
The unfortunate thing is that this will require you to 'look ahead' and figure out if a person has a pet in the first place, but if you cache backend or DB requests with something like DataLoader, this isn't actually costly, because you would have to fetch the pet anyway. This way you just fetch it a bit earlier.
If you're fetching your people from a database, it would of course make sense to already filter them there, and not in the resolve function.

Related

BigCommerce Stencil - GraphQL query using front matter not returning anything

I'm not sure if it's a bug, but I'm not able to make GraphQL work in the Cornerstone template. I'm expecting an error or something getting returned at least, but nothing is being rendered at all from gql.
I am on the pages/product.html template, and I even tried this example from the docs:
---
product:
videos:
limit: {{theme_settings.productpage_videos_count}}
reviews:
limit: {{theme_settings.productpage_reviews_count}}
related_products:
limit: {{theme_settings.productpage_related_products_count}}
similar_by_views:
limit: {{theme_settings.productpage_similar_by_views_count}}
gql: "query productById($productId: Int!) {
site {
product(entityId: $productId) {
variants(first: 25) {
edges {
node {
sku
defaultImage {
url(width: 1000)
}
}
}
}
}
}
}
"
My goal is to have access to the paths/URL on each of the product's category because product.category is just an array of category names. Here's the query I am able to make work on the GraphQL playground (86 to be replaced by $productId in the front matter GraphQL query, I think?):
query getProductCategories {
site {
product(entityId: 86) {
categories {
edges {
node {
name
path
}
}
}
}
}
}
If there's no way around this, maybe I'll just try to do the fetching in the client side.
This now works correctly, as of 20-Sep-2021.
There was a bug, tracked as an issue here: https://github.com/bigcommerce/stencil-cli/issues/732 which has been resolved and closed.

Is smart query custom variable name possible?

I'm using Vue alongside with Apollo in order to query a GraphQL endpoint in my project. Everything's fine but I want to start programming generic components to ease and fasten the development.
The thing is, in most views, I use the Smart Query system.
For instance, I use :
apollo: {
group: {
query: GROUP_QUERY,
variables () { return { id: this.groupId } },
skip () { return this.groupId === undefined },
result ({ data }) {
this.form.name = data.group.name
}
}
}
With the GROUP_QUERY that is :
const GROUP_QUERY = gql`
query groupQuery ($id: ID) {
group (id: $id) {
id
name
usersCount
userIds {
id
username
}
}
}
`
So my group variable in my apollo smart query has the same name as the query itself group (id: $id). It is this mechanism that is quite annoying for what I try to achieve. Is there a way to avoid that default mechanism ?
I'd like for instance to be able to give a generic name such as record, and it would be records for queries that potentially return multiple records.
With that, I would be able to make generic components or mixins that operate either on record or records.
Or have I to rename all my queries to record and records which would be annoying later on in case of troubleshooting with error messages ?
Or maybe there's another way to achieve that and I didn't think about it ?
Thanks in advance.
You can, in fact, rename the variable of Apollo smart queries using the update option, as seen here in the Vue Apollo documentation. Your example would look like:
apollo: {
record: {
query: GROUP_QUERY,
variables () { return { id: this.groupId } },
update: (data) => data.group,
skip () { return this.groupId === undefined },
result ({ data }) {
this.form.name = data.group.name
}
}
}
You should notice that the Apollo object will create a record variable in your component, and the update statement shows where to get the group for the record.
By doing so :
const GROUP_QUERY = gql`
query groupQuery ($id: ID) {
record: group (id: $id) {
id
name
usersCount
userIds {
id
username
}
}
}
`
If the GROUP_QUERY is used at several places, the result will be accessible under the record name, because it is defined as an alias over group.
See documentation for Aliases.

Read query from apollo cache with a query that doesn't exist yet, but has all info stored in the cache already

I have a graphql endpoint where this query can be entered:
fragment ChildParts {
id
__typename
}
fragment ParentParts {
__typename
id
children {
edges{
node {
...ChildParts
}
}
}
query {
parents {
edges
nodes {
...ParentParts
}
}
}
}
When executed, it returns something like this:
"data": {
"edges": [
"node": {
"id": "<some id for parent>",
"__typename": "ParentNode",
"children": {
"edges": [
node: {
"id": "<some id for child>",
"__typename": "ChildNode"
},
...
]
}
},
...
]
}
Now, with apollo client, after a mutation, I can read this query from the cache, and update / add / delete any ParentNode, and also any ChildNode, but I have to go over the structure returned by this query.
Now, I'm looking for a possibility to get a list of ChildNodes out of the cache (which has those already, as the cache is created as a flat list), to make the update of nested data a bit easier. Is there a possibility of reading a query out of the cache, without having read the same query from the server before?
You can use the client's readFragment method to retrieve any one individual item from the cache. This just requires the id and a fragment string.
const todo = client.readFragment({
id,
fragment: gql`
fragment fooFragment on Foo {
id
bar
qax
}
`,
})
Note that id here is the cache key returned by the dataIdFromObject function -- if you haven't specified a custom function, then (provided the __typename and id or _id fields are present) the default implementation is just:
${result.__typename}:${result.id || result._id}
If you provided your own dataIdFromObject function, you'll need to provide whatever id is returned by that function.
As #Herku pointed out, depending on the use case, it's also possible to use cache redirects to utilize data cached for one query when resolving another one. This is configured as part of setting up your InMemoryCache:
const cache = new InMemoryCache({
cacheRedirects: {
Query: {
book: (_, args, { getCacheKey }) =>
getCacheKey({ __typename: 'Book', id: args.id })
},
},
})
Unfortunately, as of writing this answer, I don't believe there's any method to delete a cached item by id. There's on going discussion here around that point (original issue here).

GraphQL, Dataloader, [ORM or not], hasMany relationship understanding

I'm using for the first time Facebook's dataloader (https://github.com/facebook/dataloader).
What I don't understand is how to use it when I have 1 to many relationships.
Here it is a reproduction of my problem: https://enshrined-hydrant.glitch.me.
If you use this query in the Playground:
query {
persons {
name
bestFriend {
name
}
opponents {
name
}
}
}
you get values.
But if you open the console log here: https://glitch.com/edit/#!/enshrined-hydrant you can see these database calls I want to avoid:
My Person type is:
type Person {
id: ID!
name: String!
bestFriend: Person
opponents: [Person]
}
I can use dataloader good for bestFriend: Person but I don't understand how to use it with opponents: [Person].
As you can see the resolver has to return an array of values.
Have you any hint about this?
You need to create batched endpoints to work with dataloader - it can't do batching by itself.
For example, you probably want the following endpoints:
GET /persons - returns all people
POST /bestFriends, Array<personId>` - returns an array of best friends matchin the corresponding array of `personId`s
Then, your dataloaders can look like:
function batchedBestFriends(personIds) {
return fetch('/bestFriends', {
method: 'POST',
body: JSON.stringify(personIds))
}).then(response => response.json());
// We assume above that the API returns a straight array of the data.
// If the data was keyed, you could add another accessor such as
// .then(data => data.bestFriends)
}
// The `keys` here will just be the accumulated list of `personId`s from the `load` call in the resolver
const bestFriendLoader = new DataLoader(keys => batchedBestFriends(keys));
Now, your resolver will look something like:
const PersonType = new GraphQLObjectType({
...
bestFriend: {
type: BestFriendType,
resolve: (person, args, context) => {
return bestFriendLoader.load(person.id);
}
}
});

Load only the data that's needed from database with Graphql

I'm learning graphql and I think I've spot one flaw in it.
Suppose we have schema like this
type Hero {
name: String
friends: [Person]
}
type Person {
name: String
}
and two queries
{
hero {
name
friends {
name
}
}
}
and this
{
hero {
name
}
}
And a relational database that have two corresponding tables Heros and Persons.
If my understanding is right I can't resolve this queries such that for the first query the resulting sql query would be
select Heros.name, Persons.name
from Heros, Persons
where Hero.name = 'Some' and Persons.heroid = Heros.id
And for the second
select Heros.name, Persons.name from Heros
So that only the fields that are really needed for the query would be loaded from the database.
Am I right about that?
Also if graphql would have ability to return only the data that's needed for the query, not the data that's valid for full schema I think this would be possible, right?
Yes, this is definitely possible and encouraged. However, the gist of it is that GraphQL essentially has no understanding of your storage layer until you explicitly explain how to fetch data. The good news about this is that you can use graphql to optimize queries no matter where the data lives.
If you use javascript, there is a package graphql-fields that can simplify your life in terms of understanding the selection set of a query. It looks something like this.
If you had this query
query GetCityEvents {
getCity(id: "id-for-san-francisco") {
id
name
events {
edges {
node {
id
name
date
sport {
id
name
}
}
}
}
}
}
then a resolver might look like this
import graphqlFields from 'graphql-fields';
function getCityResolver(parent, args, context, info) {
const selectionSet = graphqlFields(info);
/**
selectionSet = {
id: {},
name: {},
events: {
edges: {
node: {
id: {},
name: {},
date: {},
sport: {
id: {},
name: {},
}
}
}
}
}
*/
// .. generate sql from selection set
return db.query(generatedQuery);
}
There are also higher level tools like join monster that might help with this.
Here is a blog post that covers some of these topics in more detail. https://scaphold.io/community/blog/querying-relational-data-with-graphql/
In Scala implementation(Sangria-grahlQL) you can achieve this by following:
Suppose this is the client query:
query BookQuery {
Books(id:123) {
id
title
author {
id
name
}
}
}
And this is your QueryType in Garphql Server.
val BooksDataQuery = ObjectType(
"data_query",
"Gets books data",
fields[Repository, Unit](
Field("Books", ListType(BookType), arguments = bookId :: Nil, resolve = Projector(2, (context, fields) =>{ c.ctx.getBooks(c.arg(bookId), fields).map(res => res)}))
)
)
val BookType = ObjectType( ....)
val AuthorType = ObjectType( ....)
Repository class:
def getBooks(id: String, projectionFields: Vector[ProjectedName]) {
/* Here you have the list of fields that client specified in the query.
in this cse Book's id, title and author - id, name.
The fields are nested, for example author has id and name. In this case author will have sequence of id and name. i.e. above query field will look like:
Vector(ProjectedName(id,Vector()), ProjectedName(title,Vector()),ProjectedName(author,ProjectedName(id,Vector()),ProjectedName(name,Vector())))
Now you can put your own logic to read and parse fields the collection and make it appropriate for query in database. */
}
So basically, you can intercept specified fields by client in your QueryType's field resolver.