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.

Stumbled upon this entry and here’s my solution after some research. To return a custom message through graphql, you can throw as below as graphql displays that message instead

if (someLogicIsWrong) {
  throw new Error('your message here')
}

how to run the correct error way? any luck

solution is

updateUser: {
        description: 'Update an existing user',
        resolverOf: 'plugins::users-permissions.user.update',
        resolver: async (obj, options, { context }) => {
          context.params = _.toPlainObject(options.input.where);
          context.request.body = _.toPlainObject(options.input.data);

          await strapi.plugins['users-permissions'].controllers.user.update(context);

          return {
            user: context.body.toJSON ? context.body.toJSON() : context.body,
          };
        },
      },

to

updateUser: {
        description: 'Update an existing user',
        resolverOf: 'plugins::users-permissions.user.update',
        resolver: async (obj, options, { context }) => {
          context.params = _.toPlainObject(options.input.where);
          context.request.body = _.toPlainObject(options.input.data);

          await strapi.plugins['users-permissions'].controllers.user.update(context);
                    let output = context.body.toJSON ? context.body.toJSON() : context.body;

          checkBadRequest(output);

          return {
            user: output,
          };
        },
      },

now you are good to go

The Boom approach works more or less.
I still get Status: 200 OK for all requests, but at least the errors messages will display correctly. Now I have to tweak my Frontend app into throwing those messages with the error object. What a mess.

Strapi v4

const { ApplicationError } = require(’@strapi/utils’).errors;

throw new ApplicationError(‘Not found’,4xx);

1 Like

Hi all.

Still having a bit of trouble with this. Got to the point where the data returned is in the right structure, using what @taka has put in. But what we really need is the request status to be 400, instead of 200. (same as @pedrosimao mentioned)

Is there a way to control this?

Thanks,

Gwyn