GraphQL batching with logic - graphql

I have the following situation in GraphQL schema:
type User {
id: Float
name: String
cityId: Float
}
type City {
id: Float
country: String
}
On client I need information about User and City. But in order to load City I have to know its id, so I can't just batch these requests. Is it possible to make batch request with logic, so from client I make one request with two queries and maybe add some addition info that when the first request is done take id from it and then make another request.
After that both User and City go to client. It is so to say inner join, so I would like to have one request to load connected data.
I can't change the schema, but I can add libs and so on to client or server.
Thanks.

PS. sorry i have just noticed that you stated that you cannot change
the schema. I will leave it there for future reference, but it cannot
be applied on your problem probably.
i would suggest to rearrange your schema as follows. I do not have enough information if it would satisfy your needs. But i would suggest this.
type User {
id: Float
name: String
city: City
#for this city field there will be additional resolver binded to data loader
}
type City {
id: Float
country: String
}
This way the query will look like as follows
query getUsers {
users {
id
name
city {
id
country
}
}
}
There can be used UserConnection from Relay spec, but let's keep it simple for now.
On server side you will then need to implement two resolvers ... first is request for user list and then resolver for city field. Please note that resolvers have seperate context. In order to avoid N+1 requests problem and batch requests city requests to 1. It would be useful to implement data loader for cities to reduce the number of requests to database. The simple schema would be
User resolver, fetch users and return them in users resolver. CityId
is part of the payload for each use
Because city is second level of selection set you will receive each user in the first argument in the resolver function. You will use countryId to pass it to Countries data loader
Countries data loader will batch requests for each counry together. Data loader will transform the countryIds into country values and return them for each user.
GraphQL server will resolve the whole query and each city will be assigned to each user
This is the best approach that i know to deal with this and will also leads you to better architecture of your schema as you will leverage normalization of your appollo store and in my opinion it is easier to work with this format of the data on frontend as well. I hope that i did not miss something in your post and this information will be useful for you. The whole point is to just nest the country into the user, which leads to N+1 request problem and reduce the performance issue of N+1 problem with data loaders.

Related

How to inject a value via middleware into a graphql query body (prisma-typegraphql)?

I am currently using prisma-typegraphql for generating a schema and it's props.
Problem:
Is there a way to write a middleware that accesses a value from an arbitrary source, then injects that into the graphql-query arguments? It should be passed to the resolver in a way that makes it seem like the data has been in the query from the start and no modification to the resolver is necessary.
More info
Let's say I have three models, one is Client, one is Project and one is User. The Project has a relationship to the client via its id, identifying the client the project belongs to, same for the user
model Client{
id
name
etc...
}
model User{
id
name
clientId / client
etc...
}
model Project{
id
title
clientId / client
etc...
}
I don't want the clientId from the frontend via query, instead I intend to verify the user making the request and then get the clientId from that user.
Currently my Auth Middleware does verify the user and then passes the user object along in the context. However, due to the generated nature of typegraphql-prisma resolvers, I cannot use the context to inject the data into the resolver without extending every single resolver.
I'd much rather inject the value I want into the graphql query before it reaches the resolver, so the resolver has no idea something changed and processes the query as intended.

How to create a GraphQL query that returns data from multiple tables/models within one field using Laravel Lighthouse

Im trying to learn GraphQL with Laravel & Lighthouse and have a question Im hoping someone can help me with. I have the following five database tables which are also defined in my Laravel models:
users
books
user_books
book_series
book_copies
I'd like to create a GraphQL endpoint that allows me to get back an array of users and the books they own, where I can pull data from multiple tables into one subfield called "books" like so:
query {
users {
name
books {
title
issue_number
condition
user_notes
}
}
}
To accomplish this in SQL is easy using joins like this:
$users = User::all();
foreach ($users as $user) {
$user['books'] = DB::select('SELECT
book_series.title,
book.issue_number
book_copies.condition,
user_books.notes as user_notes
FROM user_books
JOIN book_copies ON user_books.book_copy_id = book_copies.id
JOIN books ON book_copies.book_id = books.id
JOIN book_series ON books.series_id = book_series.id
WHERE user_books.user_id = ?',[$user['id']])->get();
}
How would I model this in my GraphQL schema file when the object type for "books" is a mashup of properties from four other object types (Book, UserBook, BookCopy, and BookSeries)?
Edit: I was able to get all the data I need by doing a query that looks like this:
users {
name
userBooks {
user_notes
bookCopy {
condition
book {
issue_number
series {
title
}
}
}
}
}
However, as you can see, the data is separated into multiple child objects and is not as ideal as getting it all in one flat "books" object. If anyone knows how I might accomplish getting all the data back in one flat object, Id love to know.
I also noticed that the field names for the relationships need to match up exactly with my controller method names within each model, which are camelCase as per Laravel naming conventions. Except for my other fields are matching the database column names which are lower_underscore. This is a slight nitpick.
Ok, after you edited your question, I will write the answer here, to answer your new questions.
However, as you can see, the data is separated into multiple child objects and is not as ideal as getting it all in one flat "books" object. If anyone knows how I might accomplish getting all the data back in one flat object, Id love to know.
The thing is, that this kind of fetching data is a central idea of GraphQL. You have some types, and these types may have some relations to each other. So you are able to fetch any relations of object, in any depth, even circular.
Lighthouse gives you out of the box support to eloquent relations with batch loading, avoiding the N+1 performance problem.
You also have to keep in mind - every field (literally, EVERY field) in your GraphQL definition is resolved on server. So there is a resolve function for each of the fields. So you are free to write your own resolver for particular fields.
You actually can define a type in your GraphQL, that fits your initial expectation. Then you can define a root Query field e.g. fetchUsers, and create you custom field resolver. You can read in the docs, how it works and how to implement this: https://lighthouse-php.com/5.2/the-basics/fields.html#hello-world
In this field resolver you are able to make your own data fetching, even without using any Laravel/Eloquent API. One thing you have to take care of - return a correct data type with the same structure as your return type in GraphQL for this field.
So to sum up - you have the option to do this. But in my opinion, you have to write more own code, cover it with tests on you own, which turns out in more work for you. I think it is simpler to use build-in directives, like #find, #paginate, #all in combination with relations-directives, which all covered with tests, and don't care about implementation.
I also noticed that the field names for the relationships need to match up exactly with my controller method names within each model, which are camelCase as per Laravel naming conventions.
You probably means methods within Model class, not controller.
Lighthouse provides a #rename directive, which you can use to define different name in GraphQL for your attributes. For the relation directives you can pass an relation parameter, which will be used to fetch the data. so for your example you can use something like this:
type User {
#...
user_books: [Book!]! #hasMany(relation: "userBooks")
}
But in our project we decided to use snak_case also for relations, to keep GraphQL clean with consistent naming convention and less effort

How to query Apollo GraphQL server with a specific context?

I am writing an Apollo GraphQL API that returns product information from various brands. A simplified version of the schema looks like this:
type Query {
products: [Product]!
}
type Product {
name: String!
brand: String!
}
I want to be able to query products from a specific brand. Normally this would be simple to achieve by adding a brand argument to the Product object:
type Query {
products(brand: String!): [Product]!
}
However, I have multiple GraphQL clients in different apps and each is associated with a specific brand so it seems redundant to always pass the same brand argument in every query. I also have many other objects in my schema (orders, transactions, etc.) that are specific to a brand and would require a brand argument.
Furthermore, my resolvers need to query a different API depending on the brand so even objects in my schema such as User, which are conceptually unrelated to a brand, would potentially need a brand argument so that the resolver knows which API to fetch from.
Is there a way to set the brand context for each client and have this context received by the server? Or maybe there is a better way to achieve this brand separation?
I would probably make Brand be a first-class type in your GraphQL query. That doesn't save you from having to qualify many of the queries you describe by a specific brand, but it at least gives you a common place to start from. Then you'd wind up with an API somewhat like:
type Query {
brand(name: String!): Brand
allProducts: [Product!]!
}
type Brand {
name: String!
products: [Product!]!
# users: [User!]!
}
type Product {
name: String!
brand: Brand! # typical, but not important to your question
}
If the differences between kinds of brands are visible at the API layer, you also could consider using a GraphQL interface to describe the set of fields that all brands have, but actually return a more specific type from the resolver.
The way you describe your application, it could also make sense to run one copy of the service for each brand, each with a different GraphQL endpoint. That would let you straightforwardly parameterize the per-brand internal object configuration and make the "current brand" be process-global context. The big constraints here are that, at a GraphQL level, one brand's objects can never refer to another, and if you have a lot of brands, you need some good way to run a lot of servers.

Is there a good way to combine resolvers for multiple GraphQL objects, preferably using graphql-js?

Suppose you have a GraphQL layer, written on node.js using graphql-js, that communicates with a SQL database. Suppose you have the following simple types and fields:
Store
A single brick-and-mortar location for a chain of grocery stores.
Fields:
id: GraphQLID
region: StoreRegion
employees: GraphQLList(Employee)
StoreRegion
A GraphQLEnumType containing the list of regions into which the chain divides its stores.
Values:
NORTHEAST
MIDATLANTIC
SOUTHEAST
...
Employee
Represents a single employee working at a store.
Fields:
id: GraphQLID
name: GraphQLString
salary: GraphQLFloat
Suppose the API exposes a store query that accepts a Region and returns a list of Store objects. Now suppose the client sends this query:
{
store(region: NORTHEAST) {
employees {
name
salary
}
}
}
Hopefully this is all pretty straightforward so far.
So here's my question, and I hope (expect, really) that it's something that has a common solution and I'm just having trouble finding it because my Google-Fu is weak today: is there a good way that can I write the resolvers for these types such that I can wrap up all the requested fields for all the employees from all the returned stores into a single SQL query statement, resulting in one round-trip to the database of the form:
SELECT name,salary FROM employees WHERE id IN (1111, 1133, 2177, ...)
rather than making one request per employee or even one request per store?
This is really a concrete instance of a more general question: is there a good way to combine resolvers to avoid making multiple requests in cases where they could be easily combined?
I'm asking this question in terms of graphql-js because that's what I'm hoping to work with, and since I figure that would allow for more specific answers, but if there's a more implementation-agnostic answer, that would be cool too.
So, basically you are wondering how you can combine multiple resolvers into fewer database queries. This is trying to solve what they call the N+1 query problem. Here’s at least two ways you can solve this.
DataLoader: This is a more general solution and it's created by Facebook. You could use this to batch multiple queries that query a single item of a single type into a single query that queries multiple items of a single type. In your example you would batch all employees into a single DB query and you would still have a separate query for getting the store. Here's a video by Ben Awad that explains DataLoader
JoinMonster: Specifically for SQL. Will do JOINs to make one SQL query per graphql query. Here's a video by Ben Awad explaining JoinMonster

In my query, could I use the result of a parameter to get more info in that query?

Forgive my terribly-worded question but here's some code to explain what I'm trying to do (slug and value are provided outside this query):
const query = `{
post(slug: "${slug}") {
content
createdAt
id <--- I want this id for my reply query
slug
}
reply(replyTo: "id") { <--- The second query in question
content
createdAt
id
slug
}
user(id: "${value}") {
username
}
}`;
I just got started with GraphQL and I'm loving the fact that I can query multiple databases in one go. It'd be great if I could also perform some "queryception" but I'm not sure if this is possible.
When thinking in terms of GraphQL, it's important to remember that each field for a given type is resolved by GraphQL simultaneously.
For example, when your post query returns a Post type, GraphQL will resolve the content and createdAt fields at the same time. Once those fields are resolved, it moved on to the next "level" of the query (for example, if content returned a type instead of a scalar, it would then try to resolve those fields.
Each of your individual queries (post, reply, and user) are actually fields of the Root Query type, and the same logic applies to them as well. That means there's no way to reference the id returned by post within reply -- both queries will be fired off at the same time.
An exception to the above exists in the form of mutations, which are actually resolved sequentially instead of simultaneously. That means, even though you still wouldn't be able to use the result of post as a variable inside your reply query, you could use context to pass the id from one to the other if both were mutations. This, however, is very hackish and requires the client to request the mutations in a specific order.
A more viable solution would be to simply handle this on the client side by breaking it up into two requests, and waiting to fire the second until the first one returns.
Lastly, you may consider reworking your schema to prevent having to have multiple queries in the first place. For example, your Post type could simply have a replies field that would resolve to all replies that correspond with the returned post's id.

Resources