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.
Related
Possibly exposing my ignorance of apollo-server but hoping someone can help: so ATM I have some schemas stitched together with #graphql-tools; all very simple, cool. I can make queries without problem.
There's a desire to add custom fields to given queries, so that we add extra data from other sources into the requested existing query template.
To explain by example: say the schema looks like this:
type User {
id
projectId
}
I'm trying to develop something so that the query getUserById($id...) can provide a template like so:
query userById($id: ID!) {
userById(id: $id) {
id
project {
id
name
# whatever other fields I want from Project type
}
}
}
And then apollo/graphql would then make a separate, asynchronous request to fetch the project for that given User.
As I understand graphql-tools, I can see resolvers allow the ability to make async requests for extra data ... but my problem is by defining project { within the query template, an error is thrown because - of course - project is not defined in the actual Schema itself.
Is there a way to filter and remove fields from a given query, somewhere in the chain of events? A custom apollo-server plugin perhaps? As I said I'm exposing my ignorance here but I've got a little lost in how apollo behaves in tandem with GraphQl.
There's one thing I have never understood about Graphql. I think I'll paint by problem by giving some examples.
Let's say I have an Account type and a Chat type. An Account can have multiple Chats associated with it, but a Chat can only be associated with a single Account. The schema might look something like this:
type Account {
id: String!
username: String!
chats: [Chat!]!
}
type Chat {
id: String!
text: String!
account: Account!
}
Let's say I am exposing the following queries:
type Query {
account(id: String!): Account
accounts: [Account!]!
}
Now, I will query my schema:
accounts {
id
username
chats {
id
text
}
}
This is a pretty straigthforward query - I simply what to grab all the chats by all the accounts.
But what if I have a deeper query like this:
accounts {
id
username
chats {
id
text
account {
id
username
chats {
id
text
}
}
}
}
I know that in reality, querying this data would be ridiculous, but still, it should be possible, right?
Now let's switch our view from the consumer of the API to the actual developer of the API.
How is the graphql developer meant to sustain these, potentially, infinite queries? Does Graphql resolve these automatically (it doesn't seem to)? Or am I just missing something out?
Thanks for any help.
How is the graphql developer meant to sustain these, potentially, infinite queries?
Not possible in having "infinite" nested queries.
GraphQL schema should be acyclic - meaning the relationships between types should be unidirectional.
Just a rule that the schema should be based on the query patterns in the client application.
So having a nested account within the chat would simply mean the use case and sequence are not properly defined.
What are all the different ways of updating the Apollo InMemoryCache after a mutation? From the docs, I can see:
Id-based updates which Apollo performs automatically
Happens for single updates to existing objects only.
Requires an id field which uniquely identifies each object, or the cache must be configured with a dataIdFromObject function which provides a unique identifier.
"Manual" cache updates via update functions
Required for object creation, deletion, or updates of multiple objects.
Involves calling cache.writeQuery with details including which query should be affected and how the cache should be changed.
Passing the refetchQueries option to the useMutation hook
The calling code says which queries should be re-fetched from the API, Apollo does the fetching, and the results replace whatever is in the cache for the given queries.
Are there other ways that I've missed, or have I misunderstood anything about the above methods?
I am confused because I've been reading the code of a project which uses Apollo for all kinds of mutations, including creations and deletions, but I don't see any calls to cache.writeQuery, nor any usage of refetchQueries. How does the cache get updated after creations and deletions without either of those?
In my own limited experience with Apollo, the cache is not automatically updated after an object creation or deletion, not even if I define dataIdFromObject. I have to update the cache myself by writing update functions.
So I'm wondering if there is some secret config I've missed to make Apollo handle it for me.
The only way to create or delete a node and have Apollo automatically update the cache to reflect the change is to return the parent field of whatever field contains the updated List field. For example, let's say we have a schema like this:
type Query {
me: User
}
type User {
id: ID!
posts: [Post!]!
}
type Post {
id: ID!
body: String!
}
By convention, if we had a mutation to add a new post, the mutation field would return the created post.
type Mutation {
writePost(body: String!): Post!
}
However, we could have it return the logged in User instead (the same thing the me field returns):
type Mutation {
writePost(body: String!): User!
}
by doing so, we enable the client to make a query like:
mutation WritePost($body: String!){
writePost(body: $body) {
id
posts {
id
body
}
}
}
Here Apollo will not only create or update the cache for all the returned posts, but it will also update the returned User object, including the list of posts.
So why is this not commonly done? Why does Apollo's documentation suggest using writeQuery when adding or deleting nodes?
The above will work fine when your schema is simple and you're working with a relatively small amount of data. However, returning the entire parent node, including all its relations, can be noticeably slower and more resource-intensive once you're dealing with more data. Additionally, in many apps a single mutation could impact multiple queries inside the cache. The same node could be returned by any number of fields in the schema, and even the same field could be part of a number of different queries that utilize different filters, sort parameters, etc.
These factors make it unlikely that you'll want to implement this pattern in production but there certainly are use cases where it may be a valid option.
This is a very basic question but how do you call an extended type or interface?
All the documentations points to using extend type Person to add fields based on Person.
I would expect it to work like this
Employee extend type Person {
salary: Int!
}
But the documentation suggests it's like this:
extend type Person{
salary: Int!
}
So, how do I query for an Employee salary? What if there are multiple extensions of Person, e.g. Employee and Renter? I think I might be hampered by traditional thinking but I would expect the extension to result in something named and queryable.
The extend keyword is effectively used to modify an existing type within a schema. This is most commonly used in two scenarios:
1. Concatenating multiple strings that represent a single schema. You can have your schema broken up across multiple files, divided by domain. You can then do something like:
#base.graphql
type Query {
viewer: User
}
# user.graphql
extend type Query {
users: [User!]!
}
# post.graphql
extend type Query {
post: [Post!]!
}
This results in a schema that's effectively the same as:
type Query {
viewer: User
users: [User!]!
post: [Post!]!
}
2. Extending from a base schema. You might have multiple schemas that build on top of some base schema. Or you might be stitching together remote schemas. In these scenarios, we often want to add fields specific to our new schema that don't exist on the base types. This can be used to implement directives that are missing from the base schema as well:
extend type SomeType #customDirective
The extend keyword can only modify existing types; it is not a vehicle for inheritance. In fact, GraphQL does not support type inheritance. Interfaces provide a level of abstraction over existing types, but types that implement an interface do not inherit any fields from that interface. There's no way to do that, unless you use some library like graphql-s2s.
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.