I'm currently writing a POC GraphQL server. However the client UI needs fields that are transient (not part of the DB model), and formed or queried on the fly. For example: A Post can be liked, so I would like to put a flag isLiked: Boolean in graphQL. Depending on the caller this flag would be true if the Post is liked, or false if not.
However this feels not right, as it is strictly speaking not part of the Post-type and is a form of UI-coupling (something we wanted to solve with GraphQL). Also I have the feeling there could be a better way that provides the Like as a type (as it also has a date for example). Would it be a good idea to have caller dependent fields defined in the type which are basically sub-queries?
GraphQL has some limitations, and I don't think it's wrong to add special-case fields to the schema to work around these limitations, particularly if they represent some query some actual application will make (and frequently).
Conversely, since a caller always specifies which specific fields they want out of an object, adding extra fields shouldn't really cost you anything in terms of performance or database queries. So: do both!
scalar DateTime
interface Node { id: ID! }
type Like implements Node {
id: ID!
post: Post!
date: DateTime!
}
type Post implements Node {
id: ID!
title: String!
date: DateTime!
likes: [Like!]!
hasLike: Bool!
}
If you don't do this then the client's only choice is to query for specific like objects and pick some field out of them. If you add limit-type parameters to the field you can minimize the cost, but it still feels a little awkward
query PostSummary($id: ID!) {
node(id: $id) {
... on Post {
title
date
likes(limit: 1) { id }
}
}
}
If this is a real use case for your application, just adding the hasLike field seems like a more reasonable API, even if it's somewhat "specialized to the UI".
I agree with David's answer, but just to offer a different perspective, there's something to be said for keeping user-specific fields out of types that otherwise are not. There's another alternative available, and that's to move such fields into the user or viewer query that already returns the data specific to the logged-in user. For example, you could have
type User {
id: ID!
username: String!
likedPosts: [Post!]!
# or better yet
likedPostIds: [ID!]!
}
Of course, the downside to this approach is your client has to be "smart" enough to use the above to then derive whether the post was liked, which adds complexity on the front end.
The upside is, if you perform a logout or switch users, you only have to refetch the one query -- you don't have to blow away your entire cache because it's peppered with user-specific data that will now have to be refetched.
This kind of approach can also help performance. Any relational field, whether user-specific or not, will incur an additional cost. With this approach, your login query may be bloated and slower, but any subsequent queries will be that much faster. As your data grows, both in terms of breadth and depth, those performance gains can be significant.
Related
My situation is this: I have multiple components in my view that ultimately depend on the same data, but in some cases the view state is derived from the data. How do I make sure my whole view stays in sync when the underlying data changes? I'll illustrate with an example using everyone's favorite Star Wars API.
First, I show a list of all the films, with a query like this:
# ALL_FILMS
query {
allFilms {
id
title
releaseDate
}
}
Next, I want a separate component in the UI to highlight the most recent film. There's no query for that, so I'll implement it with a client-side resolver. The query would be:
# MOST_RECENT_FILM
query {
mostRecentFilm #client {
id
title
}
}
And the resolver:
function mostRecentFilmResolver(parent, variables, context) {
return context.client.query({ query: ALL_FILMS }).then(result => {
// Omitting the implementation here since it's not relevant
return deriveMostRecentFilm(result.data);
})
}
Now, where it gets interesting is when SWAPI gets around to adding The Last Jedi and The Rise of Skywalker to its film list. We can suppose I'm polling on the list so that it gets periodically refetched. That's great, now my list UI is up to date. But my "most recent film" UI isn't aware that anything has changed — it's still stuck in 2015 showing The Force Awakens, even though the user can clearly see there are newer films.
Maybe I'm spoiled; I come from the world of MobX where stuff like this Just Works™. But this doesn't feel like an uncommon problem. Is there a best practice in the realm of Apollo/GraphQL for keeping things in sync? Am I approaching this problem in entirely the wrong way?
A few ideas I've had:
My "most recent film" query could also poll periodically. But you don't want to poll too often; after all, Star Wars films only come out every other year or so. (Thanks, Disney!) And depending on how the polling intervals overlap there will still be a big window where things are out of sync.
Instead putting the deriveMostRecentFilm logic in a resolver, just put it in the component and share the ALL_FILMS query between components. That would work, but that's basically answering "How do I get this to work in Apollo?" with "Don't use Apollo."
Some complicated system of keeping track of the dependencies between queries and chaining refreshes based on that. (I'm not keen to invent this if I can avoid it!)
In Apollo observables are (in components) over queried values (cached data 'slots') but your mostRecentFilm is not an observable, is not based on cached values (they are cached) but on one time fired query result (updated on demand).
You're only missing an 'updating connection', f.e. like this:
# ALL_FILMS
query {
allFilms {
id
title
releaseDate
isMostRecentFilm #client
}
}
Use isMostRecentFilm local resolver to update mostRecentFilm value in cache.
Any query (useQuery) related to mostRecentFilm #client will be updated automatically. All without additional queries, polling etc. - Just Works? (not tested, it should work) ;)
For my GraphQL app I'd like to save logs of certain resolved fields. Because the users can view these logs themselves, should that be considered apart of a mutation instead of a query?
Since it's not the application's focus I'd assume that using a mutation is overkill, but I'm not sure if there's some sort of side effects I'm going to run into by modeling it in such a way.
The other questions I've read didn't really answer this question, so sorry if this seems like a duplicate.
Conceptually Graphql Queries & Mutations do the same thing but however differ in the way the resolvers are executed.
For the following Queries:
{
user {
name
}
posts {
title
}
}
The GraphQL implementation has the freedom to execute the field entries in whatever order it deems optimal. see here.
For the following Mutations:
{
createUser(name: $String) {
id
}
addPost(title: $String) {
id
}
}
The GraphQL implementation would execute each Mutation sequentially. see here
Par from this, the Mutation keyword is just a bit of syntax to say "hey this is gonna edit or create something". I think here, in your case, its a better decision to perform a Query & store the event in your Audit log. Exposing the fact that the Query stores an audit log is an implementation-specific detail & clients shouldn't know about it.
In my app I have alerts. Partial schema:
type Alert {
id: ID!
users: [User]
}
type User {
id
username
... many more calculated fields
}
Each alert can return a list of Users. This User type is computationally expensive to build and we really only need a couple of user fields to display on an alert. However, if we only partially build the user object, Apollo will cache these partial user objects and break other parts of the app that depend on "complete" User objects. Avoiding this would require fetchPolicy: "no-cache", and I'd like to retain that caching.
So I'm trying to avoid returning entire User objects just to support an Alert. I've run into this issue with other types as well and am struggling with the best way to architect for this. The only solution I've come up with is to create "partial types", which would be separate types with a subset of fields. For example:
type Alert {
id: ID!
users: [PartialUser]
}
type PartialUser {
id
username
name
}
This feels hacky and like it violates DRY principles.
Another way might be to manually update the cache after a query. This would avoid the caching of partial User objects, but still feels hacky. I also think that only mutations support the options.update method to manipulate the cache. So I'm a bit stuck and haven't found any guidance in the docs.
Are there any recommendations for approaching this problem?
Can the below be achieved with graph ql:
we have getusers() / getusers(id=3) / getusers(name='John). Can we use same query to accept different parameters (arguments)?
I assume you mean something like:
type Query {
getusers: [User]!
getusers(id: ID!): User
getusers(name: String!): User
}
IMHO the first thing to do is try. You should get an error saying that Query.getusers can only be defined once, which would answer your question right away.
Here's the actual spec saying that such a thing is not valid: http://facebook.github.io/graphql/June2018/#example-5e409
Quote:
Each named operation definition must be unique within a document when
referred to by its name.
Solution
From what I've seen, the most GraphQL'y way to create such an API is to define a filter input type, something like this:
input UserFilter {
ids: [ID]
names: [String]
}
and then:
type Query {
users(filter: UserFilter)
}
The resolver would check what filters were passed (if any) and query the data accordingly.
This is very simple and yet really powerful as it allows the client to query for an arbitrary number of users using an arbitrary filter. As a back-end developer you may add more options to UserFilter later on, including some pagination options and other cool things, while keeping the old API intact. And, of course, it is up to you how flexible you want this API to be.
But why is it like that?
Warning! I am assuming some things here and there, and might be wrong.
GraphQL is only a logical API layer, which is supposed to be server-agnostic. However, I believe that the original implementation was in JavaScript (citation needed). If you then consider the technical aspects of implementing a GraphQL API in JS, you might get an idea about why it is the way it is.
Each query points to a resolver function. In JS resolvers are simple functions stored inside plain objects at paths specified by the query/mutation/subscription name. As you may know, JS objects can't have more than one path with the same name. This means that you could only define a single resolver for a given query name, thus all three getusers would map to the same function Query.getusers(obj, args, ctx, info) anyway.
So even if GraphQL allowed for fields with the same name, the resolver would have to explicitly check for whatever arguments were passed, i.e. if (args.id) { ... } else if (args.name) { ... }, etc., thus partially defeating the point of having separate endpoints. On the other hand, there is an overall better (particularly from the client's perspective) way to define such an API, as demonstrated above.
Final note
GraphQL is conceptually different from REST, so it doesn't make sense to think in terms of three endpoints (/users, /users/:id and /users/:name), which is what I guess you were doing. A paradigm shift is required in order to unveil the full potential of the language.
a request of the type works:
Query {
first:getusers(),
second:getusers(id=3)
third:getusers(name='John)
}
Pardon the naive question, but I've looked all over for the answer and all I've found is either vague or makes no sense to me. Take this example from the GraphQL spec:
query getZuckProfile($devicePicSize: Int) {
user(id: 4) {
id
name
profilePic(size: $devicePicSize)
}
}
What is the point of naming this query getZuckProfile? I've seen something about GraphQL documents containing multiple operations. Does naming queries affect the returned data somehow? I'd test this out myself, but I don't have a server and dataset I can easily play with to experiment. But it would be good if something in some document somewhere could clarify this--thus far all of the examples are super simple single queries, or are queries that are named but that don't explain why they are (other than "here's a cool thing you can do.") What benefits do I get from naming queries that I don't have when I send a single, anonymous query per request?
Also, regarding mutations, I see in the spec:
mutation setName {
setName(name: "Zuck") {
newName
}
}
In this case, you're specifying setName twice. Why? I get that one of these is the field name of the mutation and is needed to match it to the back-end schema, but why not:
mutation {
setName(name: "Zuck") {
...
What benefit do I get specifying the same name twice? I get that the first is likely arbitrary, but why isn't it noise? I have to be missing something obvious, but nothing I've found thus far has cleared it up for me.
The query name doesn't have any meaning on the server whatsoever. It's only used for clients to identify the responses (since you can send multiple queries/mutations in a single request).
In fact, you can send just an anonymous query object if that's the only thing in the GraphQL request (and doesn't have any parameters):
{
user(id: 4) {
id
name
profilePic(size: 200)
}
}
This only works for a query, not mutation.
EDIT:
As #orta notes below, the name could also be used by the server to identify a persistent query. However, this is not part of the GraphQL spec, it's just a custom implementation on top.
We use named queries so that they can be monitored consistently, and so that we can do persistent storage of a query. The duplication is there for query variables to fill the gaps.
As an example:
query getArtwork($id: String!) {
artwork(id: $id) {
title
}
}
You can run it against the Artsy GraphQL API here
The advantage is that the same query each time, not a different string because the query variables are the bit that differs. This means you can build tools on top of those queries because you can treat them as immutable.