Strapi provides a set of powerful APIs to let the developers create custom API end-points. In this article, we will be learning about Strapi Customization by creating a Strapi custom API endpoint from scratch.
Before you can jump into this content, you need to have the following:
The following yarn
command will install a new Strapi instance.
yarn create strapi
When you press enter
, the terminal will ask for a project name. For this instance name it, strapi5-custom-api-endpoint
.
If you prefer npm
instead, in your terminal, run the command;
npx create-strapi@latest strapi5-custom-api-endpoint
The command will create a latest Strapi project instance.
You can check out the official Strapi page for learning about the different ways to install Strapi.
The terminal will prompt you to either log in
or sign up
for Strapi Cloud (to begin your free 14-day trial projects), or skip this step. Use the arrow keys to navigate and press Enter to select your option.
For this demo, skip this step, but in a live production app this just means you will have to host the project yourself.
The terminal will ask you a bunch more questions. And for each of these questions, press Enter for Yes
:
Once the installation is complete, the browser automatically opens a new tab. If not, cd
into the dir
, /strapi5-custom-api-endpoint
, run the command yarn run develop
or npm run develop
to spin up the Strapi dev
server.
Awesome news! Strapi 5 made the switch from Webpack to Vite as the default bundler, and as a cherry on top, it now has "enhanced TypeScript support" for both performance and type safety.
This dev server runs on port 1337
, navigate to http://localhost:1337/admin/
in your browser.
If the port is already in use by another process, check the alternate port mentioned in the CLI output. Most of the time, it will be 8000
, 8080
, or 1337
.
Complete the form to create the first administrative user of this Strapi application.
During the Strapi installation process, the terminal prompted us with a question: ? Start with an example structure & data?
. We chose to select Yes
to this option. By doing so, Strapi automatically generated pre-built components, collection types, and a single type, providing a basic structure and example data right out of the box.
This setup helps you get started quickly with pre-defined content models that you can later modify and expand based on your needs.
In the screenshot of the Strapi Content Manager admin panel below, you can see some of the pre-built Collection Types:
When you view the Article collection type in Strapi, you'll see that it contains 5 blog entries.
The schema for the Article collection type is defined in the file located at src/api/article/content-types/article
. This schema might seem a bit complex at first since it contains a variety of data structures that Strapi offers to model your content:
1{
2 "kind": "collectionType",
3 "collectionName": "articles",
4 "info": {
5 "singularName": "article",
6 "pluralName": "articles",
7 "displayName": "Article",
8 "description": "Create your blog content"
9 },
10 "options": {
11 "draftAndPublish": true
12 },
13 "pluginOptions": {},
14 "attributes": {
15 "title": {
16 "type": "string"
17 },
18 "description": {
19 "type": "text",
20 "maxLength": 80
21 },
22 "slug": {
23 "type": "uid",
24 "targetField": "title"
25 },
26 "cover": {
27 "type": "media",
28 "multiple": false,
29 "required": false,
30 "allowedTypes": ["images", "files", "videos"]
31 },
32 "author": {
33 "type": "relation",
34 "relation": "manyToOne",
35 "target": "api::author.author",
36 "inversedBy": "articles"
37 },
38 "category": {
39 "type": "relation",
40 "relation": "manyToOne",
41 "target": "api::category.category",
42 "inversedBy": "articles"
43 },
44 "blocks": {
45 "type": "dynamiczone",
46 "components": ["shared.media", "shared.quote", "shared.rich-text", "shared.slider"]
47 }
48 }
49}
To keep things straightforward, let's use the existing data models in Strapi to demonstrate how to create a custom API endpoint.
We’ll set up a simplified endpoint, GET /api/content-summary
, which provides a quick summary of the number of articles, authors, and categories already in your Strapi application.
This way, we can easily give an overview of the content in the system without creating any additional data models.
The response from this endpoint could look like this:
1[
2 {
3 "totalArticles": 15,
4 "totalAuthors": 5,
5 "totalCategories": 4
6 }
7]
As seen above, this kind of endpoint is simple but useful because it gives you a snapshot of your content, which can be helpful for analytics or dashboard displays.
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 content-summary
.
Navigate to your strapi5-custom-api-endpoint
folder and run
npx strapi generate
The command above creates a custom Strapi API endpoint.
Make sure the api
option is selected and click enter.
1? Strapi Generators api - Generate a basic API
2? API name // name your api
I am going to name it content-summary
and select N
for "Is this API for a plugin?
"
1? Strapi Generators api - Generate a basic API
2? API name content-summary
3? Is this API for a plugin? (Y/n)
This will generate these files below:
We can find these in the /src/api/content-summary
folder. This command creates a directory called content-summary
within src/api
directory. The content-summary
contains three directories called controllers
, routes
, and services
.
Requests sent to the Strapi API URL or Strapi custom endpoint 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/content-summary/routes/content-summary.ts
with the following lines of code.
1export default {
2 routes: [
3 {
4 method: 'GET',
5 path: '/content-summary',
6 handler: 'content-summary.getSummary',
7 config: {
8 policies: [],
9 middlewares: [],
10 },
11 },
12 ],
13};
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.
Controllers in Strapi are functions that have direct access to Koa’s ctx
(context) object. The ctx
object represents the state of the HTTP request and response. Controllers use this object to handle incoming requests, interact with services or database queries, and send responses back to the client.
In this example, we replaced the code in /src/api/content-summary/controllers/content-summary.ts
with the following snippet:
1export default {
2 async getSummary(ctx) {
3 try {
4 const totalArticles = await strapi.query('api::article.article').count();
5 const totalAuthors = await strapi.query('api::author.author').count();
6 const totalCategories = await strapi.query('api::category.category').count();
7
8 ctx.body = {
9 totalArticles,
10 totalAuthors,
11 totalCategories,
12 };
13 } catch (err) {
14 ctx.body = {
15 error: 'An error occurred while fetching the summary data',
16 details: err instanceof Error ? err.message : 'Unknown error',
17 };
18 ctx.status = 500; // Set the HTTP status code to 500 to indicate a server error
19 }
20 },
21};
The code in /src/api/content-summary/controllers/content-summary.ts
fetches counts for articles, authors, and categories, then returns this data in the response. If an error occurs, it catches the error, sets a meaningful error message, and returns a 500
status code.
Strapi 5 automatically stops and restarts the server when necessary.
However, if you need to manually restart the server, you can do so by stopping it with ctrl + c
in the terminal and then starting it again using the command yarn develop
. Ensure that you are in the strapi5-custom-api-endpoint
directory when you run this command.
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/content-summary/services/content-summary.ts
with the following lines of code that use Strapi’s Document Service API to fetch and summarize article data:
1import { factories } from '@strapi/strapi';
2
3export default factories.createCoreService('api::article.article', ({ strapi }) => ({
4 // Custom service method to get a summary of published articles
5 async getPublishedArticlesSummary() {
6 try {
7 // Count the number of published articles using parameters
8 const publishedArticlesCount = await strapi.documents('api::article.article').count({ status: 'published' });
9
10 return publishedArticlesCount;
11 } catch (error) {
12 throw new Error(`Error fetching published articles count: ${error instanceof Error ? error.message : 'Unknown error'}`);
13 }
14 }
15}));
In this code, we are using Strapi 5 Document Service API to interact with and manage content more effectively. The API allows us to count the number of articles with a specific status
, such as published
or draft
, directly from the database, which simplifies the data-fetching process.
To learn more about the various ways of managing data using Strapi’s capabilities, refer to the Document Service API documentation.
Once a service is created, it's accessible from controllers or from other services:
1// access an API service
2strapi.service('api::apiName.serviceName');
3// access a plugin service
4strapi.service('plugin::pluginName.serviceName');
We modified our API controller to use the Content Summary Service, making our code more modular and organized. The updated controller use this service to fetch specific data efficiently. Here's the code snippet showing the changes:
1export default {
2 async getSummary(ctx) {
3 try {
4 // Fetch the total count of articles, authors, and categories
5 const totalArticles = await strapi.query('api::article.article').count();
6 const totalAuthors = await strapi.query('api::author.author').count();
7 const totalCategories = await strapi.query('api::category.category').count();
8
9 // Call the content summary service to get the count of published articles
10 const totalPublishedArticles = await strapi.service('api::content-summary.content-summary').getPublishedArticlesSummary();
11
12 // Return the data as the response
13 ctx.body = {
14 totalArticles,
15 totalAuthors,
16 totalCategories,
17 totalPublishedArticles, // Use the service method result here
18 };
19 } catch (err) {
20 // Handle any errors and respond with an appropriate error message
21 ctx.body = {
22 error: 'An error occurred while fetching the summary data',
23 details: err instanceof Error ? err.message : 'Unknown error',
24 };
25 ctx.status = 500; // Set the HTTP status code to 500 to indicate a server error
26 }
27 },
28};
That’s all the code required for the use case. Send a get request to /api/content-summary
. You will most probably get a 403 error.
In order to make the end-point accessible only to the authenticated users, go to the Settings > Roles > Authenticated. To be able to publicly access the end-point, please go to the Setting > Roles > Public in the Strapi dashboard.
Please, check the getSummary
for the CONTENT-SUMMARY
route and hit the save button.
Now, the custom API is ready for the public:
You can either just go here directly in your browser http://localhost:1337/api/content-summary
or make a GET request using a service like postman or insomnia.
You should now see your data coming from your custom controller.
In this article, we have learned about Strapi Customization by creating a Strapi custom API endpoint from scratch.
We used Strapi’s Document Service API to pull content metrics directly from our backend and create a simple, yet effective, summary of our data.
The Document Service API is Strapi's new Content API, designed to replace the good ol' Entity Service API from Strapi v4 (well, it's not that old, really). Beyond all the Content APIs that Strapi offers for accessing your data, the Document Service API is now the go-to option for interacting with your app's database within the backend server. It’s the layer that handles not only documents but also Strapi’s more complex data structures like components and dynamic zones.
The Document Service API was the obvious choice for our use case. Most importantly so because of its powerful querying techniques, we can easily make our example as flexible as we need, pulling metrics like counts directly from our backend database.
You can go to this Git Repository and check the readme file for complete guide. Let me know if you have any suggestions and what you will be building with the knowledge.
When I’m not working, I love to write, learn something new & read non-fiction.