Graphql, nodejs, how to resolve non-root level query field based on if it is queried? - graphql

I'd like to resolve a field called 'outstandingBalance' in Client type. If front-end query:
query {
Client {
id
name
outstandingBalance
}
}
The resolver function for outstandingBalance is expensive to run. If front-end query:
query {
Client {
id
name
}
}
Then, don't trigger the resolver for 'outstandingBalance'. I have basic understanding of graphql and read most of its official document. But have not seen an answer to this pattern, or this way of using Graphql is not allowed?
Question
Is there a thing called "Nont-root level resolver" for graphql? like the 'outstandingBalance' field in Client type?
Question: How to implement in graphql? Especially using resolver:
async function outstandingBalance(obj, args, context, info) {
console.log('called...')
}
to query one field in one type based on if this field is queried?

Related

Sending dynamic number of mutations at once

Is there a standard way to send a dynamic number of mutations in the same request with Apollo client ?
I have to deal with a Graphql API that only expose a single delete mutation, and I'd like to call it with multiple ids. Here's how it's defined:
mutation DeleteItemById($id: Int) {
delete_item(id: $id) {
id
}
}
From what I read, I could do something like
mutation DeleteItemById($id_1: Int, $id_2: Int) {
delete_item_1: delete_item(id: $id_1) {
id
}
delete_item_2: delete_item(id: $id_2) {
id
}
}
But how could I generate such a query dynamically ? Is it a good practice anyway ? I always read it was not a good idea to dynamically generate graphql queries.
Plus, I'm using graphql-codegen and statically defining queries in .graphql files, so I imagine it will have trouble parsing dynamic ones.
In general it's a bad idea to generate GraphQL queries dynamically. A good way to deal with this is to create a new mutation that supports multiple ids, validate and delete all in the same batch, like:
type Mutation {
deleteItems(ids: [String!]!): Boolean!
}

How to approach a GraphQL query that returns a boolean value?

Need to check whether an email is available or taken during the user sign-up process. The goal is to quickly query, using GraphQL, the API server and have it tell us if the email is available or taken.
What is the general best practice on a simple boolean-ish type of situation using GraphQL?
Below is what I have come up with but I am unsure if this is a good practice or not and want to hear feedback on a better practice on queries like this.
Request:
query {
emailExists(email:"jane#doe.com") {
is
}
}
Response:
{
"data": {
"emailExists": {
"is": true
}
}
}
A "query" is just a field on what happens to be the Query type. A field can return any output type, including scalars -- it doesn't need to return an object. So it's sufficient to have a schema like:
type Query {
emailExists(email: String!): Boolean!
}
The only reason to prefer an object type would be if you anticipated wanting to add additional fields in the future (i.e. something other than your current is field).

Query by relationship for GraphQL?

How would you use graphQL to query by a "relational" entity value?
For instance, lets say we have a bunch of person-objects. Each "person" then has a relation to an interest/hobby which then has a property called "name".
Now lets say that we want to query for the name of each person with a specific interest, how would such a query be "conducted" using GraphQL?
Using OData it would be something like Persons?$select=name&$expand(Interests($filter=name eq 'Surfing')).. what would be the equivalent for GraphQL?
There is no one equivalent. With the exception of introspection, the GraphQL specification does not dictate what types a schema should have, what fields it should expose or what arguments those fields should take. In other words, there is no one way to query relationships or do things like filtering, sorting or pagination. If you use Relay, it has its own spec with a bit more guidance around things like pagination and connections between different nodes, but even Relay is agnostic to filtering. It's up to the individual service to decide how to implement these features.
As an example, if we set up a Graphcool or Prisma server, our query might look something like this:
query {
persons(where: {
interest: {
name: "Surfing"
}
}) {
name
}
}
A query to a Hasura server might look like this:
query {
persons(where: {
interest: {
name: {
_eq: "Surfing"
}
}
}) {
name
}
}
But there's nothing stopping you from creating a schema that would support a query like:
query {
persons(interest: "Surfing") {
name
}
}

Multiple field resolver resolves same rest API with different query parameters

We are planning to use graphql for orchestrations (For e.g. UI client invokes graphql service which goes to multiple rest endpoint and return the result). Problem here is from one rest endpoint we have to pass different types of query parameters based on the field requested by our client.
We use spring-boot-graphql and graphql-java-tools libraries to initialize graphql
type Query{
user(id: ID): User
}
type User{
phone: [Phone]
address: [Address]
}
type Phone{...}
type Address{...}
My code resolves user field and invoke rest endpoint to fetch phone and address information in a single call like
https:restservice.com\v1\user\123?fields=phone,address
How to resolve two fields expecting data from same rest service. I want something like when client request for phone then i needs to send fields in request parameters as phone alone without address. Can we do that? or is there any other way to define schema to solves this problem?
query {
user(userId : "xyz") {
name
age
weight
friends {
name
}
}
}
Knowing the field selection set can help make DataFetchers more efficient. For example in the above query imagine that the user field is backed by an SQL database system. The data fetcher could look ahead into the field selection set and use different queries because it knows the caller wants friend information as well as user information.
DataFetcher smartUserDF = new DataFetcher() {
#Override
public Object get(DataFetchingEnvironment env) {
String userId = env.getArgument("userId");
DataFetchingFieldSelectionSet selectionSet = env.getSelectionSet();
if (selectionSet.contains("user/*")) {
return getUserAndTheirFriends(userId);
} else {
return getUser(userId);
}
}
};
https://www.graphql-java.com/documentation/v12/fieldselection/

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

Resources