Strapi provides a set of powerful APIs to let the developers create custom API end-points. In this article, we will be creating a custom API endpoint from scratch.
Prerequisites
Before you can jump into this content, you need to have the following:
- Basic knowledge of JavaScript
- Basic understanding of Strapi - get started here.
Installing Strapi
The following command will install a new Strapi instance with some ready-made configurations and data.
npx create-strapi-app plugins-v4-1 --template corporate
With --template corporate
flag, you need to choose Custom
as the installation type
. Choose sqlite
as the default database.
? Choose your installation type Custom (manual settings)
? Choose your default database client sqlite
? Filename: .tmp/data.db
Make sure that the project directory is the current directory in the terminal. Start the development server.
cd plugins-v4-1/
yarn develop --watch-admin
The --watch-admin
flag starts the application with the autoReload enabled and the front-end development server. It allows developers to customize the administration panel.
Once the installation is complete, the browser automatically opens a new tab. If not, navigate to http://localhost:8000/admin/
.
If port 8000
is already used by an another process, please check the alternate port mentioned mentioned in the CLI output. Most of the time, it would be 8080
.
Complete the form to create the first administrator user of this Strapi application.
The --watch-admin flag starts the application with the autoReload enabled and the front-end development server. It allows developers to customize the administration panel.
The corporate template generates components, collection types, and a single type out of the box.
Components:
- User
- Page
- Lead From submission
Single Types:
- Global
Components:
- A lot of components for Elements, Layout, Links, Meta, and Sections
The template also provides us with some pre-filled data to play around with. All of these happen just by providing the --template corporate
flag at the time of Strapi installation.
Setting-up a Use Case
We have a collection type page
, pre-filled with some data.
On a get request to http://localhost:1337/api/pages?populate=*
, Strapi returns all the page
, populated with all of its components and dynamic-zone components data.
The requirement is to create a custom API Endpoint GET /api/pages-report
which will provide a simplified report of the pages.
[
{ "id": 1, "name": "Home", "metaTitle": "Strapi corporate site starter" },
{ "id": 2, "name": "Pricingg", "metaTitle": "Pricing" },
{ "id": 3, "name": "Secret", "metaTitle": "Secret page" },
{ "id": 4, "name": "Contact", "metaTitle": "Contact" }
]
This is exactly what we will be building in this article.
Generating a Basic API
The npx strapi generate
command starts an interactive CLI session. In case you have yarn
and strapi
installed globally in your system, feel free to replace the npx
command with yarn
.
The following options may be selected. Please, notice that we have named our API pages-report
.
This command creates a directory called pages-reports
within src/api
directory. The pages-report
contains three directories called controllers
, routes
, and services
.
Routes
Requests sent to Strapi on any URL are handled by routes. By default, Strapi generates routes for all the content-types (see REST API documentation). Routes can be added and configured. Once a route exists, reaching it executes some code handled by a controller.
Replace the code in src/api/pages-report/routes/pages-report.js
with the following lines of code.
module.exports = {
routes: [
{
method: 'GET',
path: '/pages-report',
handler: 'pages-report.findAll',
config: {
policies: [],
middlewares: [],
},
},
],
};
Controllers vs Services
Controllers are JavaScript files that contain a set of methods called actions, reached by the client according to the requested route. Whenever a client requests the route, the action performs the business logic code and sends back the response. Controllers represent the C in the model-view-controller (MVC) pattern. In most cases, the controllers will contain the bulk of a project's business logic. But as a controller's logic becomes more and more complicated, it's a good practice to use services to organise the code into re-usable parts.
Services are a set of reusable functions. They are particularly useful to respect the DRY (don’t repeat yourself) programming concept and to simplify controllers logic.
Creating a Service
Services are basically not aware of the Koa’s ctx
object(request and the response). It is supposed to be a flexible and reusable function.
Replace the contents of /src/api/pages-report/services/pages-report.js
with the following lines of code:
'use strict';
module.exports = {
pagesReport: async () => {
try {
// fetching the data
// we dont really need contentSections for this example.
// its kept here, just for your reference
const entries = await strapi.entityService.findMany('api::page.page', {
fields: ['id', 'shortName'],
populate: {
metadata: {
fields: ['metaTitle']
},
contentSections: {
populate: '*'
}
}
});
// reducing the data to a simple array
let entriesReduced;
if (entries && Array.isArray(entries)) {
entriesReduced = entries.reduce((acc, item) => {
acc = acc || [];
console.log(acc);
acc.push({
id: item.id,
name: item.shortName || '',
metaTitle: item.metadata?.metaTitle || ''
});
return acc;
}, [])
// returning the reduced data
return entriesReduced;
}
} catch (err) {
return err;
}
}
}
In this code we are using Strapi’s Entity Service API to fetch all pages. To learn more about the flexible ways of populating the response, read the Populating the Entity Service API documentation.
The pagesReport
service will return the simplified (reduced) data:
[
{ "id": 1, "name": "Home", "metaTitle": "Strapi corporate site starter" },
{ "id": 2, "name": "Pricingg", "metaTitle": "Pricing" },
{ "id": 3, "name": "Secret", "metaTitle": "Secret page" },
{ "id": 4, "name": "Contact", "metaTitle": "Contact" }
]
Once a service is created, it's accessible from controllers or from other services:
// access an API service
strapi.service('api::apiName.serviceName');
// access a plugin service
strapi.service('plugin::pluginName.serviceName');
The list of services available can be logged from controllers and services:
console.log('strapi.services ', strapi.services);
console.log('pages-report', strapi.service('api::pages-report.pages-report'));
Creating a Controller
Controllers are functions that have direct access to the Koa’s ctx
, hence a controller function is responsible for invoking our service on request
and returning the results to the response
.
Replace the code in src/api/pages-report/controllers/pages-report.js
with the following lines of code:
'use strict';
module.exports = {
async findAll(ctx, next) {
try {
const data = await strapi.service('api::pages-report.pages-report').pagesReport();
ctx.body = data;
} catch (err) {
ctx.badRequest('Page report controller error', { moreDetails: err })
}
}
};
Setting Permissions for the API
That’s all the code required for the use case. Send a get request to /api/pages-report
. You will most probably get a 403 error.
In order to be able to publicly access the end-point, please go to the Setting > Roles > Public in the Strapi dashboard. Please, check the final for the pages-report route and hit the save button.
Now, the custom API is ready for the public:
Conclusion
In this article, we used Strapi’s Entity Service API to fetch and filter the results. It’s not the only way to accomplish this task. In Strapi, the fetching and filtering of results can be done using Rest API, Query Engine API, and GraphQL API as well.
The Entity Service is the layer that handles Strapi's complex data structures like components and dynamic zones and uses the Query Engine API under the hood to execute database queries. This makes it a reasonable choice for this use case. Our example can be made as flexible as we need using the filtering & advanced population techniques of the Entity Service API.
The source code written for this article can be explored at the git commit. Let me know if you have any suggestions and what you will be building with the knowledge.