Contents
Extend your controller in strapi v4
Hello everyone! I will be showing you how to extend your controllers and routes in strapi v4.
Get Started
Let's create a project first of all. I think that if you want to customize the api, you probably know how to set up a strapi enviroment so I am going to skip this part.
// npm
npx create-strapi-app@latest my-project --quickstart
// yarn
yarn create strapi-app my-project --quickstart
Awesome after the initial setup we can quickly create a collection type called Hello
, You can name this anything you want.
Now let's add a text field called title
Click finish, save your collection type and wait for the strapi app to restart.
Now we should have the following collection type:
Customizing the api
Alright let's get to work! Just kidding, it's really simple. follow the folder structure:
my-project > src > api > hello
Here we have 4 folders available.
- [content-types, controllers, routes, services]
Open up the javascript files inside the controllers and routes folders. They should both have the same name as your api. In my case
Hello.js
Inside the controllers > hello.js replace the code with the following:
// src/api/hello/controllers/hello.js
"use strict";
const { createCoreController } = require("@strapi/strapi").factories;
module.exports = createCoreController("api::hello.hello", ({ strapi }) => ({
async findAll(ctx) {
const entries = await strapi.db.query("api::hello.hello").findMany();
ctx.body = entries;
},
}));
Here we are extending the core controller to accept an extra function called findAll. Of course the core controller already has such a method called find. This is just for simplicity sake.
Now let's move to the routes folder and create a new file called custom-hello.js or something...
Inside we are going to define the routes: method, path and handler.
// src/api/hello/routes/custom-hello.js
module.exports = {
routes: [
{
method: "GET",
path: "/all-hellos",
handler: "hello.findAll",
},
],
};
And.. That's it! Now if we start our strapi app we can go to settings > roles > public > hello* and there we will see our new route!
Check findAll then save the changes. Create some entries inside the hellos collection and go to http://localhost:1337/api/all-hellos
to view your collection! 🎉
Nice! super easy eh?
Conclusion
Customizing your api may seem like a hassle and in some cases unneccesary. But I think that everyone will, at one point run into an issue where something is simply not possible with the core api.
For example, specifically updating the stock of a product by id without passing too many parameters.
// Addition to controlers/apiname.js
async updateStock(ctx) {
const { id } = ctx.params;
const { stock } = ctx.request.query;
const entries = await strapi.db.query("api::product.product").update({
where: { id },
data: {
stock,
},
});
ctx.body = entries;
},
and for the routes
// Addition to the custom routes file
{
method: "PUT",
path: "/update-stock/:id",
handler: "product.updateStock",
},
Final PUT route
http://localhost:1337/api/update-stock/2?stock=5
In this example I update the stock field of a product inside the product collection. Sometimes you want some extra control for specific tasks and extending the core controller / routes will help you with this.
Thank you for reading and Happy Coding 🥷💻
Top comments (3)
Thanks, It really helped understanding the custom API creation in Strapi. I was struggling with it but your post helped me in getting started.
Hey m'dude, thanks for the post. I couldn't get it to work though, get following error:
Error creating endpoint GET /products/:slug: Cannot read property 'findSlug' of undefined
TypeError: Error creating endpoint GET /products/:slug: Cannot read property 'findSlug' of undefined
There apparently is a workaround for registering routes (haven't tried it) here forum.strapi.io/t/how-to-add-custo...
I just found it hacky and was hoping your way would work.
It does beg the question though:
The core controller takes the next parameter, say /hello/1, it infers the 1 is the id.
If I now register /hello/:name as a custom route, how's the controller gonna know whether that's the core or the custom route? It's gonna fail on core because name won't be a number but even in the best of cases it'd have to try both, which is inefficient (and I'm sure the logic isn't build like that)
Could you maybe point to the source of your solution? It really seems like something is missing in the core routes file
Regards!
It's works for me, Thanks!