Graphql fetching one object from array - graphql

In GraphQL, is it possible to fetch data with a 'filtered' array (e.g. only with a single object) instead of an array with all the (multiple) objects? For instance, by providing the 'id' property for the selected object you want to get in the query.
If yes, what needs to be changed in the below query? Just wondering if this filtering can already be done server side.
const selectedId = 'n32j23h'
const query = gql`
query getData {
dataObj {
bigArray {
id
name
}
}
}
`;

Define a new query in your typeDefs:
query getDataById(id: ID!): returnObjectType
Implement a resolver for that query that uses the id parameter to filter your bigArray

Related

GraphQL Tag dynamic table name in query (apollo)

In my app every customer has a own table for its data. Depending on which customer was selected I want to dynamically build the tablename for the Graphql query.
For example:
// Query for customer 1
gql`
query overviewQuery {
customer_1 {
aggregate {
count
}
}
}
`
// Query for customer 2
gql`
query overviewQuery {
customer_2 {
aggregate {
count
}
}
}
`
I have the customer id in my vuex store and want to insert a variable into the query like in the following pseudocode which is not working.
const tblUserAggregateName = `customer_${this.$store.state.customer.customerId`
gql`
query overviewQuery {
${this.tblUserAggregateName} {
aggregate {
count
}
}
`
Is there an option how to do this or how can this problem be solved?
It is not an option to hardcode all different customer queries and selected them on runtime.
Thanks!
In the answers it was mentioned, that it is against best practice to dynamically change the table name of GraphQL queries.
As changing the complete database structure for all clients (they each have a separate Database with a own PostgreSQL schema name) is not an option, I found a solution the looks a little bit hacky and definitely not best practice but I thought I might be interesting to share for others with the same problem.
The approach is pretty easy: I write the query as a simple string where I could use variables and convert them later to the gql AST Object with graphql-tag
const query = () => {
const queryString = `
{
${customerId}_table_name {
aggregate {
count
}
}
}`
return gql`${queryString}`
}
Works, but I you have a better solution, I am happy to learn!
You can supply variables, but they should not be used to dynamically infer schema object names. This violates core concepts of GraphQL. Try this instead:
gql`
query overviewQuery ($customerId: ID!) {
customerData (id: $customerId) {
tableName
aggregate {
count
}
}
}`
Apollo provides great documentation on how to supply variables into a query here: https://www.apollographql.com/docs/react/data/queries/
The backend can then use the customerId to determine what table to query. The client making requests does not need to know (nor should it) where that data is or how it's stored. If the client must know the table name, you can add a field (as shown in the example)

In GraphQL, how to control DB query by client query?

I learn to use GraphQL these days. In my opinion, To build a query, I need to build three parts:
Schema
type User{
id String
name String
cars [Car!]!
}
type Car{
id String
}
type Query{
user(id: String): User
}
DB Query function
{
user: async function ({id}) {
const user = await DB.user.findOne({id});
const userCars = await DB.car.find({userId: id});
user.cars = userCars;
return cars;
}
}
Client query
{
user (id: "1") {
name
cars {
id
}
}
}
That query returns a user's name and his cars. The DB query function always query for cars.
But sometimes I just need user's info:
{
user (id: "1") {
name
}
}
I don't want to query for cars, so I hope to make my DB query function can auto choose to query for cars or not.
How can I do this?
GraphQL.js will support either object properties or methods for resolver functions; this is discussed in its page on Object Types.
One way to deal with this is just to insert an anonymous function directly into the returned object:
{
user: async function ({id}) {
const user = await DB.user.findOne({id});
user.cars = () => DB.car.find({userId: id});
return cars;
}
}
Another is to create a wrapper object with a class that provides the id property and (asynchronous, lazy) cars method; some examples of this are in the GraphQL.js documentation. This approach tends to work in most GraphQL implementations in most languages.
I think you looking into auto-creating/mapping from GraphQL query into db query.
Every queries are db/project specific, so you should create this mapping. You can easily do that with graphql-fields package.
There is copy pasted WHY section from the package:
An underlying REST api may only return fields based on query params.
{
user {
profile {
firstName
},
id
}
}
should request /api/user?fields=profile,id
while
{
user {
email
}
}
should request /api/user?fields=email
Implement your resolve method like so:
resolve(root, args, context, info) {
const topLevelFields = Object.keys(graphqlFields(info));
return fetch(`/api/user?fields=${topLevelFields.join(',')}`);
}
It's best to avoid squeezing it all into one resolver function. Instead, create a separate ObjectType for Cars which has its own fields and its own resolver function. This way, the car query is only called if that field is requested.
In case you are using a RDS, join monster and data louder can help optimize performance of your queries.
Join Monster which relies on generating one big join query and also solve the problem of only requesting exactly the fields you need from the DB
Cached and Batched SQL Data Source which uses facebook's dataloader under the hood - it wont solve the problem of which fields to query (although the example uses knex, which will make that a lot easier), but instead it can cache and batch your queries

Passing Apollo Client cache data with apollo Query

I have two queries:
const GET_FILTERS = gql`
query getFilters {
filters #client {
id
title
selected
}
}
`
And
const GET_POSTS = gql`
query getPosts {
posts {
id
author
status
}
}
`
First one fetches the data from a local state using apollo-link-state and the second one is a external call.
I have a HOC for fetching posts setup like this:
const withPosts = (Component) => (props) => (
<Query
query={GET_POSTS}
>
{({loading, data, error})} => {
if(loading) return null
if(error) return null
return <Component {...data} {...props}/>
}}
</Query>
)
The fetching of the posts is fine but what I would like to do is to add whatever is returned from GET_FILTERS query with every call to GET_POSTS query?
One thing I could do is to wrap withPost in to another, lets say withFilters HOC, and pass the result in to GET_POSTS query as variables but I have been wondering if there is any other way to access that data via some sort of context and for example cache.readQuery using only withPost HOC.
Apollo has 'native' tools for HOC pattern, you can compose many (named) queries and mutations to enhance component. IMHO it's much more readable and powerfull than using query/mutaions components.
You can pass queried (filter) values as variables via data.fetchMore - of course you can use query component for 'inner query'.
Using cache directly isn't required while query 'cache-first' fetchPolicy option can be used.

GraphQL - fetch a field conditionally

Imagine that I have a following query (I am using Apollo):
const userQuery = gql`
query {
user {
id
name
}
}
`;
I want to fetch name field only if some condition is met (let's say variable shouldFetchName is true). How should I approach this and what is the best practice?
You can pass a variable to your query and use a Directive to fetch a field conditionally, like name #include(if: $shouldFetchName).
See the docs for Directives.

Apollo GraphQl react. How to clear query cache for all variable combinations?

I am using apollo graphql in my react application.
Say I have the following query:
query ListQuery($filter: String!) {
items(filter: $filter) {
id
name
}
}
This query lets me query a list of items using a filter. Say I used filter string A, and then used filter string B. The cache would now contain two entries: ListQuery(A) and ListQuery(B).
Now let's say I use a mutation to add a new item. How can I remove all the cached queries from the cache? So in this case, I want to remove both ListQuery(A) and ListQuery(B) from the cache.
How can I accomplish this?
In your case, you can use the apollo's method
client.resetStore();
It will clear the previous cache and then load the active queries.
Try this:
query ListQuery($filter: String!) {
items(filter: $filter) {
id
name
}
},
fetchPolicy: 'no-cache'
More here:
https://www.apollographql.com/docs/react/caching/cache-interaction/#bypassing-the-cache
Try evicting the particular query object from the cache...
cache.evict({ id: "ROOT_QUERY", fieldName: "listQuery" });
cache.gc();
I got this working after reading this: https://danreynolds.ca/tech/2020/05/04/Apollo-3-Client-Cache/
Seems like what you need is refetchQueries option/prop to be set to array of query names strings. The documentation states that:
If options.refetchQueries is an array of strings then Apollo Client
will look for any queries with the same names as the provided strings
and will refetch those queries with their current variables.
So as an example for using graphql HOC you could try to do:
function SomeComponent(props) {
const performMutation = () => {
props.doStuff({ refetchQueries: ["ListQuery"] })
.then(/* ... */)
.catch(/* ... */)
}
return (/* ... */)
}
export default graphql(DO_STUFF, { name: "doStuff" })(SomeComponent)
Or with the Mutation component:
function SomeComponent(props) {
return (
<Mutation
mutation={DO_STUFF}
refetchQueries={mutationResult => ["ListQuery"]}>
{(doStuff, { loading, error }) => {
/* ... */
}}
</Mutation>
);
}
If this is somehow doesn't do what you need, there is also a workaround using update option.
I was able to solve this problem by first changing a value in my schema from required to not required, which resulted in values being queried again! After that, I reverted the change and have had no problems!
Read about fetch policies
options.fetchPolicy
--Edit:--
But it is only as a trick if you want reset only one query. To reset all store see answer by #Nitin : Set fetchPolicy to network-only. Refresh query. Return it to option of your choice.

Resources