Strapi V4 controllers customization

System Information
  • Strapi Version: 4.0.0-beta.13
  • Operating System: macOS Monterey 12.0.1
  • Database: PostgreSQL
  • Node Version: v14.17.1
  • Yarn Version: 1.22.17

I am customize the controller and override the create action. I have follow the beta documentation and get the error.

I would like to have the complete example for reference how to customize the controller, thank you.

[2021-11-17 17:58:57.488] error: parseBody is not defined
ReferenceError: parseBody is not defined
'use strict';

/**
 * A set of functions called "actions" for `customer`
 */

module.exports = {
  // exampleAction: async (ctx, next) => {
  //   try {
  //     ctx.body = 'ok';
  //   } catch (err) {
  //     ctx.body = err;
  //   }
  // }

  create: async (ctx) => {
    const { query } = ctx.request;

    const { data, files } = parseBody(ctx);

    const entity = await service.create({ ...query, data, files });

    return transformResponse(sanitize(entity));
  }
};

I ran into a similar problem when trying to write a custom controller for the find query, but with transformResponse not being defined - I assume it’s missing to include some module where this is defined, and I tried this but it didn’t seem to work:
const { transformResponse } = require('@strapi/strapi');

Another thing I found was that I had to modify the service call as follows to make the query work (where blog is the name of my collection type):
const { results, pagination } = await strapi.service('api::blog.blog').find(query);

I agree that a proper sample of customizing a controller in v4 would be most helpful, so far I’ve only had limited success with my trial & error approach to figure it out.

2 Likes

Agreed - a demo of how to hook into the pagination handler when making a DB query with the Query Engine API would be much appreciated.

I’m currently learning strapi and digging through a lot of tutorial material. I encountered the same issue with this.transformResponse, and also with this.santizeOutput (this at v 4.2.7). When I look at how I had created the custom controller, it looks like a problem with “this”.

The first way I tried, via copy paste from several tutorials and applying workarounds found on SO to deal with the problem functions.

'use strict';
const utils = require("@strapi/utils");
const {sanitize} = utils;

const sanitizeOutput = (data, ctx) => {
  const schema = strapi.getModel('api::event.event');
  const {auth} = ctx.state;
  return sanitize.contentAPI.output(data, schema ,{auth});
};

/**
 *  event controller
 */

const {createCoreController} = require('@strapi/strapi').factories;

module.exports = createCoreController('api::event.event', ({strapi}) => ({
  me: async (ctx, next) => {
    const user = ctx.state.user
    console.log(this)
    if (!user) {
      return ctx.badRequest(null, [{messages: [{id: "No auth header found"}]}])
    }

    const data = await strapi.entityService.findMany("api::event.event", {
      populate: 'image',
      filters: {
        "users_permissions_user": {
          "id": user.id
        }
      },

    });

    if (!data) {
      return ctx.notFound();
    }

    const sanitizedEvents = sanitizeOutput(data, ctx);

    return (sanitizedEvents);
  }
}));

Note how the object is returned via me: property. Within the above if you log “this” its an empty object. Also even though you get your data, and can santize it, it’s not in the same shape as you get from the standard collection controllers. no data or meta, just an array of your data.

After a bit more digging and some experiments if I rewrite the above to return the object in a different way we get this as expected, and can access transformResponse and santizeOutput as shown in lots of reference code.

The way that works as expected. Left the above in but commented out in case it’s useful to compare.

'use strict';
// const utils = require("@strapi/utils");
// const {sanitize} = utils;
//
// const sanitizeOutput = (data, ctx) => {
//   const schema = strapi.getModel('api::event.event');
//   const {auth} = ctx.state;
//   return sanitize.contentAPI.output(data, schema ,{auth});
// };

/**
 *  event controller
 */

const {createCoreController} = require('@strapi/strapi').factories;

// module.exports = createCoreController('api::event.event', ({strapi}) => ({
//   me: async (ctx, next) => {
//     const user = ctx.state.user
//     console.log(this)
//     if (!user) {
//       return ctx.badRequest(null, [{messages: [{id: "No auth header found"}]}])
//     }
//
//     const data = await strapi.entityService.findMany("api::event.event", {
//       populate: 'image',
//       filters: {
//         "users_permissions_user": {
//           "id": user.id
//         }
//       },
//
//     });
//
//     if (!data) {
//       return ctx.notFound();
//     }
//
//     const sanitizedEvents = sanitizeOutput(data, ctx);
//
//     return (sanitizedEvents);
//   }
// }));



module.exports = createCoreController("api::event.event", ({strapi}) => ({
  async me(ctx, next) {
    const user = ctx.state.user
    console.log(this)
    if (!user) {
      return ctx.badRequest(null, [{messages: [{id: "No auth header found"}]}])
    }

    const data = await strapi.entityService.findMany("api::event.event", {
      populate: 'image',
      filters: {
        "users_permissions_user": {
          "id": user.id
        }
      },

    });
  console.log(data);
    if (!data) {
      return ctx.notFound();
    }

    const sanitizedEvents = await this.sanitizeOutput(data, ctx);

    return this.transformResponse(sanitizedEvents);
  }
}));

when doing it the above way this is logged populated rather than an empty object and the issues resolving this.santizeOutput and this.transformResponse go away

{
  me: [AsyncFunction: me] { [Symbol(__type__)]: [ 'content-api' ] },
  find: [AsyncFunction: find] { [Symbol(__type__)]: [ 'content-api' ] },
  findOne: [AsyncFunction: findOne] { [Symbol(__type__)]: [ 'content-api' ] },
  create: [AsyncFunction: create] { [Symbol(__type__)]: [ 'content-api' ] },
  update: [AsyncFunction: update] { [Symbol(__type__)]: [ 'content-api' ] },
  delete: [AsyncFunction: delete] { [Symbol(__type__)]: [ 'content-api' ] }
}

Hopefully that helps someone.

I was able to add a pagination handler to custom controllers. Adding it here in case someone comes looking in the future:

1. Add the pagination logic to get – a) total count, b) page size, c) page count and d) current page. I wanted to call the items exactly as default so I don’t have to muck around with the front-end code that was already implemented. Added all of this to the meta that would be returned by the controller:

      params._limit = params._limit || 25; // Set the default limit to 25
      params._start = params._start || 0; // Set the default start to 0
      params._sort = params._sort || 'publishedAt:DESC'; // Set the default sort to publishedAt:DESC

2. Now populate the meta:

const meta = {
        total: collection.length, // gets the total number of records
        pageSize: params._limit, // gets the limit we set earlier
        pageCount: Math.ceil(collection.length / params._limit), // gives us the number of total pages
        currentPage: params._start / params._limit + 1, // returns the current page      
      };

3. Include the meta to be returned along with whatever else you are returning, like so:

return { something_you_return, meta };

I hope this helps! BTW, I am learning Strapi new as well! Love it!

[Edit: Simplified the code]

1 Like