How to Create a Custom API Endpoint in Strapi?

Shada - Apr 19 '22 - - Dev Community

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:

  1. Basic knowledge of JavaScript
  2. 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


Enter fullscreen mode Exit fullscreen mode

Please, check out the create-strapi-app node package page for learning about the different ways to install Strapi.

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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.

starting the application with auto-reload enabled

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.

form to create the first strapi admin user

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.

exploring the pre-filled data of collection type page

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.

default response of page

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" }
    ]


Enter fullscreen mode Exit fullscreen mode

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.

using the cli to generate api

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: [],
         },
        },
      ],
    };


Enter fullscreen mode Exit fullscreen mode

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;
        }
      }
    }


Enter fullscreen mode Exit fullscreen mode

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" }
    ]


Enter fullscreen mode Exit fullscreen mode

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');


Enter fullscreen mode Exit fullscreen mode

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'));
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode




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 })
    }
  }
};
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode




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.

the custom route give 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.

setting the api permissions

Now, the custom API is ready for the public:

the reduced response

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .