GraphQL descriptive success messages in mutations - graphql

Currently in GraphQL JS implementation there is no standard way of returning a descriptive success message from mutation, such as "User created successfully", etc. My question has 2 parts:
My thinking is that it is beneficial to return a success message from the API. That way the messages can be uniform across different clients (e.g. web and mobile). Clients don't have to implement their custom messages for each API call. But I'm not sure what the best practice is. Are there any drawbacks of returning a descriptive success message from mutation calls (besides increased response size)?
If I decide to do it, is there a standard way in graphql-js to do this?
For example, is it possible to get back something like:
{
data: {
mutationName: {var1:"val1"}
},
messages: {
mutationName:"User created successfully"
}
}

Reasons not to do this
There is now a considerable amount of client code that depends on the shape of returned results being as they are. You would need to fork those if you were to implement your idea, at the cost of considerable effort, in order to benefit from your change.
Reason to stick with language spec as it is today
Meanwhile, what you want to achieve is (if I have understood you right) entirely achievable within the current spec.
Effective solution to stated problem
Why not do this (we'll make the mutation be user-creation):
schema {
mutation: Mutation
query: Query
}
type Mutation {
newUser(params: NewUserInput): NewUserResult
}
input NewUserInput {
# ...
}
type NewUserResult {
successMessage(languageCode: LanguageCode): String
newUser: User
}
# need to define Query, NewUserInput, User
This allows you to return a success message if that's what your system requires, without needing to change the spec of the GraphQL language.
If you really must...
(Source: graphql errors and status messages in graphql-js)
At least in express-graphql, there is a way to pass in extensions to it: https://github.com/graphql/express-graphql/blob/master/src/index.js#L89

Related

How can I specify maximum cache-time for any data matching a regex in apollo-client with InMemoryCache?

Some fields coming from the graphql server will have the shape short-lived-token-XYZ123. Ideally we wouldn't even have to know the field names ahead of time, as any code we write will live in a library. How can I hook into the InMemoryCache or the ApolloClient object to set the cache time of fields with values matching a regex? Causing them to poll at a set interval would be really ideal, but because polling is query-centric, I dont think that is possible at the field level. Giving them a specific cache time would be enough. Is there a way to hook into the InMemoryCache with a function that gets called on every read?
Another option would be to make these token strings a graphql type Token like
type Token {
id: String
}
and then in the client it might be possible to define a custom cache behavior for this type when initializing the cache like
new InMemoryCache({
typePolicies: {
Token: {
fields: {
id: {
read(cachedVal) {
if (cacheTimeElapsed){
return null
} else {
return cachedVal
}
}
}
},
},
},
But Im also unclear HOW to bust the cache using the read function. What do I return from the function to tell the cache that it is busted and needs to refetch? These docs are...challenging. If I could just call a function on every single read and do what I need to do, that would be ideal.
These fields will also be annotated in the apollo-server with #token(for other reasons), and we could potentially hook in here to somehow tell the client to cache-bust these fields. Not sure how, but it's another option.
I posted the same question on the Apollo forums and received the answer that, remarkably, they don't support setting specific cache times or invalidating the cache from the read function of the typePolicies. It is apparently on the roadmap.
A third party caching library was suggested instead: https://github.com/NerdWalletOSS/apollo-cache-policies
Looking in the "Why does this exist?" in the NerdWallet README, you can see they mention that this is a common pain point with the InMemoryCache

Can Queries be used for data writing?

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.

graphql- same query with different arguments

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)
}

GraphQL: how can I throw a warning after a successful mutation?

Let's imagine I have a createPost mutation that inserts a new post. In a typical app, that mutation can either:
Succeed, returning a Post.
Fail, throwing an error (I use apollo-errors to handle this).
What I'd like to implement is a middle scenario, where the mutation succeeds (returning a Post); but also somehow returns a warning to the user (e.g. Your post is similar to post XYZ or similar).
What would be a good GraphQL pattern to implement this? Adding a warning field to the Post type seems a little weird, but then again I'm not sure how to return both a Post and a Warning in the same mutation? Any ideas?
(Note that I'm using this scenario as an example, I'm interested in the general pattern of returning extra post-mutation data, not finding similar posts specifically)
All my mutations return a wrapping payload type rather than a single value type (e.g. Post in your case), I also don't ever throw in GraphQL unless it's a real system error -- if it's the consequence of user input or is an otherwise expected case, I model it into the return type.
Returning a wrapping payload is generally considered a best practice because a) your mutation should return entry points for everything in the graph that may have changed (not just the new post), and b) it gives you the easy ability to add new fields to the return type at a later time.
Remember, a mutation is essentially a function that takes in some input data and the current graph, and returns a new graph. It's generally a mistake to think in terms of REST-like CRUD operations.
type CreatePostError = {
// Whatever you want
}
type CreatePostSuccess = {
post: Post!
warning: String
}
union CreatePostPayload = CreatePostSuccess | CreatePostError
mutation {
// Other mutations
createPost(/* args /*): CreatePostPayload
}

What is the point of naming queries and mutations in GraphQL?

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.

Resources