Correct way to return error in custom service through GraphQL

I’m creating some custom controllers for my content type Order, and need to do some validation along with success and error responses.

One of my controllers looks as following:

async create(ctx) {

    let { product: requested_product, amount: requested_amount, client, deliverytime, type } = ctx.request.body;

    const product = await strapi.services.product.findOne({
      id: requested_product
    })

    if (requested_amount !== product.price)
      return ctx.badRequest(null, [{ messages: [{ id: 'The order request is invalid.' }] }]);

    (...)

}

When the amount differs my GraphQL request results in an undesired error message: "Cannot return null for non-nullable field Order.id.",, when I’d want to return my error message above.

I get why this is happening, but I want to know what is the correct way to respond to the client. Obviously I cannot return an Order object when failing.

Cheers


1 Like

Hello @thor1n

I’m not an expert in GraphQL, but I’m using a custom query & resolver for this approach.

First of all, I’ve create a file at ./api/*/config/schema.graphql.js or for plugins in ./extensions/*/config/schema.graphql.js.
And defined a new query and a resolver for it as shown bellow:

const { sanitizeEntity } = require('strapi-utils');
const _ = require('lodash');
module.exports = {
  query: `
    customOrders(where: JSON): JSON
  `,
  resolver: {
    Query: {
      customOrders: {
        description: 'Find orders',
        resolverOf: 'application::order.order.find', // Will apply the same policy on the custom resolver as the controller's action `find`.
        resolver: async (obj, options, ctx) => {
          //Get orders by using filters from 'where' 
          let entities = await strapi.query('order').find(options.where);

          //Will return an error custom message if there are no orders
          //Also here you can do a custom validation.
          if (_.isEmpty(entities)) {
            return {
              status: '404',
              message: 'No orders found'
            }
          }

          //Will return sanitized orders
          return {
            status: '200',
            orders: entities.map(entity =>
              sanitizeEntity(entity, {
                model: strapi.models.order,
              })
            )
          };
        },
      },
    },
  },
};

Now in GraphQL, I use the newly created Query: customOrders, it returns orders if they are found and custom error message if there are no orders:

Adapted to your needs it will look like this:

module.exports = {
  query: `
    createProduct(input: JSON): JSON
  `,
  resolver: {
    Mutation: { // Depends on your needs, you should use Query(for fetching data) or Mutation (for data modifications)
      createProduct: {
        description: 'Custom create product with validation',
        resolverOf: 'application::product.product.create', // Will apply the same policy on the custom resolver as the controller's action `find`.
        resolver: async (obj, options, ctx) => {
          let { product: requested_product, amount: requested_amount, client, deliverytime, type } = ctx.request.body;
          const product = await strapi.services.product.findOne({id: requested_product});

          //Will return an error custom message the amount differs
          if (requested_amount !== product.price) {
            return { status: '404', message: 'The order request is invalid.'};
          }
          //(other custom code)
        },
      },
    },
  },
};

I was a little unclear: I am using a customer resolver for a Mutation.

But I’m looking at your example and it should work in my project as well.

I’ll try to add it and get back.of it works or not. Thanks for the feedback!!

2 Likes

Now my resolver function returns this:

...

  const product = await strapi.services.product.findOne({
    id: product_id
  })

  if (!product) {
    return { status: '404', message: 'The product does not exist.'};
  }

...

Resulting in the same error:

"message": "Cannot return null for non-nullable field Order.id.",

I assume the schema expects another structure of the GraphQL response, so basically the error message doesn’t fit the schema — right?

What I’m doing to copy Strapi’s own error reporting approach, is:

// HTTP Error library used by Strapi, allows us to display custom error message in the CMS
const Boom = require('boom')
...
resolver: async (_ , { id }) => {
  const book = await strapi.services['books'].findOne({ id })
  if (!book) throw Boom.notFound('No book found by that id.')
  return book
}

Would be interested to hear the Strapi team’s opinion on this approach.