For a mutation addVoucher there are a limited list of potential errors that can occur.
Voucher code invalid
Voucher has expired
Voucher has already been redeemed
At the moment I'm throwing a custom error when one of these occurs.
// On the server:
const addVoucherResolver = () => {
if(checkIfInvalid) {
throw new Error('Voucher code invalid')
}
return {
// data
}
}
Then on the client I search the message description so I can alert the user. However this feels brittle and also the GraphQL API doesn't automatically document the potential errors. Is there a way to define the potential errors in the GraphQL schema?
Currently my schema looks like this:
type Mutation {
addVoucherResolver(id: ID!): Order
}
type Order {
cost: Int!
}
It would be nice to be able to do something like this:
type Mutation {
addVoucherResolver(id: ID!): Order || VoucherError
}
type Order {
cost: Int!
}
enum ErrorType {
INVALID
EXPIRED
REDEEMED
}
type VoucherError {
status: ErrorType!
}
Then anyone consuming the API would know all the potential errors. This feels like a standard requirement to me but from reading up there doesn't seem to be a standardises GraphQL approach.
It's possible to use a Union or Interface to do what you're trying to accomplish:
type Mutation {
addVoucher(id: ID!): AddVoucherPayload
}
union AddVoucherPayload = Order | VoucherError
You're right that there isn't a standardized way to handle user-visible errors. With certain implementations, like apollo-server, it is possible to expose additional properties on the errors returned in the response, as described here. This does make parsing the errors easier, but is still not ideal.
A "Payload" pattern has emerged fairly recently for handling these errors as part of the schema. You see can see it in public API's like Shopify's. Instead of a Union like in the example above, we just utilize an Object Type:
type Mutation {
addVoucher(id: ID!): AddVoucherPayload
otherMutation: OtherMutationPayload
}
type AddVoucherPayload {
order: Order
errors: [Error!]!
}
type OtherMutationPayload {
something: Something
errors: [Error!]!
}
type Error {
message: String!
code: ErrorCode! # or a String if you like
}
enum ErrorCode {
INVALID_VOUCHER
EXPIRED_VOUCHER
REDEEMED_VOUCHER
# etc
}
Some implementations add a status or success field as well, although I find that making the actual data field (order is our example) nullable and then returning null when the mutation fails is also sufficient. We can even take this one step further and add an interface to help ensure consistency across our payload types:
interface Payload {
errors: [Error!]!
}
Of course, if you want to be more granular and distinguish between different types of errors to better document which mutation can return what set of errors, you won't be able to use an interface.
I've had success with this sort of approach, as it not only documents possible errors, but also makes it easier for clients to deal with them. It also means that any other errors that are returned with a response should serve as an immediately red flag that something has gone wrong with either the client or the server. YMMV.
You can use scalar type present in graphql
just write scalar JSON and return any JSON type where you want to return it.
`
scalar JSON
type Response {
status: Boolean
message: String
data: [JSON]
}
`
Here is Mutation which return Response
`
type Mutation {
addVoucherResolver(id: ID!): Response
}
`
You can return from resolver
return {
status: false,
message: 'Voucher code invalid(or any error based on condition)',
data: null
}
or
return {
status: true,
message: 'Order fetch successfully.',
data: [{
object of order
}]
}
on Front end you can use status key to identify response is fetch or error occurs.
Related
Simple table with one two rows. ID (incrementing int) and test_value (TEXT, nullable).
1. Query
query getData($test_value:String!) {
testtable(where: {test_value: {_eq: $test_value}}) {
test_value
}
}
Variables
{"test_value":null}
Result
{
"errors": [
{
"extensions": {
"path": "$.selectionSet.testtable.args.where.test_value._eq",
"code": "validation-failed"
},
"message": "unexpected null value for type \"String\""
}
]
}
This is a correct that I expect.
2. Mutation
mutation InsertData($test_value: String!) {
insert_testtable(objects: {test_value: $test_value}) {
affected_rows
}
}
Variables
{"test_value":null}
Result
{
data: {
insert_testtable: {
affected_rows: 1
}
}
}
I expect and error (because of test_value:String! declaration), but I don't get it.
Why?
P.S.
testable schema looks like this:
id: Int!
test_value: String
My understanding of your issue: Your schema has a mutation insert_testtable that takes a nullable String argument. When you submit a named mutation operation with a non-nullable String! variable, the GraphQL server does not respond with an error.
The GraphQL spec says that is the expected behaviour for mutations and queries. The spec says that if the type in the schema is nullable and of the same type as the variable, the operation is to be considered valid. This is what's happening for your mutation.
If you are not seeing the same behaviour for the query, it is possible that your GraphQL server implementation differs from the spec. You could check your server docs or their GitHub Issues.
For what it's worth, I checked that AppSync, AWS's GraphQL implementation, produces the expected behaviour for both queries and mutations.
I am quite new to GraphQL so I am struggling a little bit to understand how to write a proper Query on the front-end.
So, this is the Mutation I've on the server-side
type Mutation {
addTerminal(terminal: TerminalInput): Terminal
// other stuff not related
}
type Terminal {
terminalId: String!
merchantId: String
terminalDesignator: String
}
input TerminalInput {
terminalId: String!
merchantId: String
terminalDesignator: String
}
I believe it is using the right structure, but when I try to connect with the client-side im a bit confused.
This is the query I've on the front-end.
export const ADD_TERMINAL_MUTATION = () => (
mutation addTerminalMutation($terminalId: TerminalInput) {
addTerminal(terminal: { terminalId: $terminalId }) {
terminalId,
merchantId,
terminalDesignator,
}
}
);
and when I fire it to the server, I receive the following feedback:
Variable "$terminalId" of type "TerminalInput" used in position expecting type "String!".
So I changed to this:
addTerminal(terminal: { terminalId: "123" }) {
and got the error
Variable "$terminalId" is never used in operation "addTerminalMutation".
If I change to
mutation addTerminalMutation($terminalId: String!) {
It says that the TerminalId wasnt provided, but if I log it, it can be seen
So, what is the right way to write this ?
Thanks.
You need to change addTerminalMutation($terminalId: TerminalInput) to addTerminalMutation($terminalId: String!) to indicate the correct type.
First, the
Variable "$terminalId" is never used in operation "addTerminalMutation".
is caused by passing the variable in line
mutation addTerminalMutation($terminalId: TerminalInput)
and never referencing it later.
The problem in the second part seems that the terminalId parameter is not decomposed.
The parameter in the line
addTerminal(terminal: { terminalId: $terminalId })
is expected to be a String!.
You could do something like this:
addTerminal(input: $terminalId)
to pass in the whole terminal id object.
See this for further info:
https://blog.apollographql.com/designing-graphql-mutations-e09de826ed97
I'm using AWS AppSync's GraphQL server with the following (simplified) schema:
type Query {
getIssue(id: String!): Issue
}
type Issue {
id: String!
data: IssueData!
}
type Event {
id: String!
time: AWSDateTime!
status: [String]
}
type Payment {
id: String!
amount: Int!
status: String
}
union IssueData = Event | Payment
When I make a query that includes inline fragments to select the status as a child of either an Event or Payment type in the Issue/data field, I get a FieldsConflict error:
query getIssue($id: String!) {
getIssue(id: $id) {
id
data {
... on Event {
time
status
}
... on Payment {
amount
status
}
}
}
}
Validation error of type FieldsConflict: status: fields have different list shapes # 'getIssue/data'
This is presumably caused by the Event/status field returning an array of strings, while the Payment/status field returns a single string.
Why does GraphQL consider this to be a conflict? How should I construct my query to allow access to the status field on both data types?
Note that I'm using a union rather than an extended interface because the Issue and Payment types have no common data structure.
From the spec:
If multiple field selections with the same response names are encountered during execution, the field and arguments to execute and the resulting value should be unambiguous. Therefore any two field selections which might both be encountered for the same object are only valid if they are equivalent.
You can resolve the issue by providing a field alias for one or both fields:
query getIssue($id: String!) {
getIssue(id: $id) {
id
data {
... on Event {
time
eventStatus: status
}
... on Payment {
amount
status
}
}
}
}
Renaming one or both fields in your schema would obviously also resolve the issue.
I have a problem I don't know how to solve properly.
I'm working on a project where we use a graphql server to communicate with different apis. These apis are old and very difficult to update so we decided to use graphql to simplify our communications.
For now, two apis allow me to get user data. I know it's not coherent but sadly I can't change anything to that and I need to use the two of them for different actions. So for the sake of simplicity, I would like to abstract this from my front app, so it only asks for user data, always on the same format, no matter from which api this data comes from.
With only one api, the resolver system of graphql helped a lot. But when I access user data from a second api, I find very difficult to always send back the same object to my front page. The two apis, even though they have mostly the same data, have a different response format. So in my resolvers, according to where the data is coming from, I should do one thing or another.
Example :
API A
type User {
id: string,
communication: Communication
}
type Communication {
mail: string,
}
API B
type User {
id: string,
mail: string,
}
I've heard a bit about apollo-federation but I can't put a graphql server in front of every api of our system, so I'm kind of lost on how I can achieve transparency for my front app when data are coming from two different sources.
If anyone has already encounter the same problem or have advice on something I can do, I'm all hear :)
You need to decide what "shape" of the User type makes sense for your client app, regardless of what's being returned by the REST APIs. For this example, let's say we go with:
type User {
id: String
mail: String
}
Additionally, for the sake of this example, let's assume we have a getUser field that returns a single user. Any arguments are irrelevant to the scenario, so I'm omitting them here.
type Query {
getUser: User
}
Assuming I don't know which API to query for the user, our resolver for getUser might look something like this:
async () => {
const [userFromA, userFromB] = await Promise.all([
fetchUserFromA(),
fetchUserFromB(),
])
// transform response
if (userFromA) {
const { id, communication: { mail } } = userFromA
return {
id,
mail,
}
}
// response from B is already in the correct "shape", so just return it
if (userFromB) {
return userFromB
}
}
Alternatively, we can utilize individual field resolvers to achieve the same effect. For example:
const resolvers = {
Query: {
getUser: async () => {
const [userFromA, userFromB] = await Promise.all([
fetchUserFromA(),
fetchUserFromB(),
])
return userFromA || userFromB
},
},
User: {
mail: (user) => {
if (user.communication) {
return user.communication.mail
}
return user.mail
}
},
}
Note that you don't have to match your schema to either response from your existing REST endpoints. For example, maybe you'd like to return a User like this:
type User {
id: String
details: UserDetails
}
type UserDetails {
email: String
}
In this case, you'd just transform the response from either API to fit your schema.
I've run into an issue while trying to extend my API to include a GraphQL endpoint. The application I'm working on is a kind of forum with Messages. A message can contain comments of type Message. If a message is a comment it has a parent of type Message. Simplified, the schema looks like this:
type Message {
id: String
content: String
comments: [Message]
parent: Message
}
type RootQuery {
message(id: String): Message
messages: [Message]
}
The problem with this schema is that it allows for queries like this:
{
messages {
comments {
parent {
comments {
parent {
comments {
parent {
id
content
}
}
}
}
}
}
}
}
Keep in mind that I may want to allow for arbitrarily deep nesting of comments. In that case the following query should be allowed:
{
messages {
comments {
comments {
comments {
id
content
}
}
}
}
}
So, my question is this: Should I introduce a new type - Comment - to the API that do not know of its parent? Or are there any other ways of restricting this kind of unwanted behaviour?
Also, would the use of a Comment-type prohibit me from using the fragment messageFields on Message syntax in my queries? Perhaps this is the time to introduce interfaces to the schema?
Suggestion to a solution if I introduce the type Comment (I have not tried this):
interface Message {
id: String
content: String
comments: [Message]
}
type DefaultMessage : Message {
id: String
content: String
comments: [Comment]
parent: Message
}
type Comment : Message {
id: String
content: String
comments: [Message]
}
type RootQuery {
message(id: String): Message
messages: [Message]
}
Just in case anyone else ends up here wondering how to do recursive types in graphql-js, there's a useful hint in graphql-js's code:
* When two types need to refer to each other, or a type needs to refer to
* itself in a field, you can use a function expression (aka a closure or a
* thunk) to supply the fields lazily.
*
* Example:
*
* var PersonType = new GraphQLObjectType({
* name: 'Person',
* fields: () => ({
* name: { type: GraphQLString },
* bestFriend: { type: PersonType },
* })
* });
*
*/
https://github.com/graphql/graphql-js/blob/master/src/type/definition.js#L274
If a message is a comment it has a parent of type Message.
Looks like the parent field should be under type Comment, not DefaultMessage. That still wouldn't prevent a parent - comments - parent query but if you're worried about this for a DDOS reason, there are many other types of requests that are difficult to compute, even with REST APIs, and you should have other measures to detect such an attack.
Recursive nodes
However, you pose a very interesting question with the nested comments. How would you know how many times you need to nest the comment in the query to get all nested responses? I don't think it's currently possible with GraphQL to specify recursive objects.
I'd probably go around this limitation by fetching each nested comment one by one (or by X levels at a time) starting from the last comment as the node
{
messages {
comments {
id
content
}
}
}
followed by
{
node(commendId) {
comment {
id
content
}
}
}
I suppose you have a depth attribute of the Comment data structure, which should be pretty useful, for example, to limit the max nested depth when the users are posting comments.
So that your problem could be solved like this: in the resolver of the comments attribute, check the depth, return nothing if the depth is going illegal, otherwise fetch the comments and return.