Creating a Searchable Reading List with Strapi CMS Custom API
Tags: strapi, idea, cms
Published: 2024-01-20 18:34:45

---
title: Creating a Searchable Reading List with Strapi CMS Custom API
published: true
date: 2023-11-16 18:51:29 UTC
tags: strapi, idea, cms
canonical_url: https://www.njfamirm.ir/en/blog/strapi-custom-api/
---

Hi everyone!

As programmers, we are constantly learning and reading articles, blog posts, and documentation. Maybe this is the most important part of **programming lifestyle**!  
Unfortunately, we do not keep those resource anywhere, and even if we keep them on our notes because it is not accessible through searching, we can't access them anymore.

## Idea

An interesting solution for our problem is a **`searchable reading list`** that's I got this idea from the [esif.dev](https://esif.dev/) blog.

At the end, you can setup and maintain a **`searchable-reading-list`** of valuable resources that you encounter during your programming journey!

## Roadmap

Since we may read several articles in a day and need to put it in the reading-list, We needed to have a CMS, so that I could put the links there very quickly and easily and not think about anything else.

Also, our reading list page should work with the API and not need to be built statically like other pages with [Eleventy](https://www.11ty.dev/). So I decided to use [Strapi](https://strapi.io/) as a beautiful, dynamic, and easy-to-use CMS.

## Preparing the environment

Before we can start building our reading-list, we need to create a new Strapi project.

```
yarn create strapi-app reading-list --ts
```

## Create Content Type

First, we need to create a content-type that we can put our reading-list in, So to create a content type for our reading-list, we can use the Strapi CLI and run the following command in our project directory. This will generate a new content type with the specified name and fields.

This command will create a new content type called reading-list and generate the necessary files and fields to get started building our custom API.

```
yarn strapi generate

  ? Strapi Generators content-type - Generate a content type for an API
  ? Content type display name Reading List
  ? Content type singular name reading-list
  ? Content type plural name reading-lists
  ? Please choose the model type Collection Type
  ? Use draft and publish? No
  ? Do you want to add attributes? Yes
  ? Name of attribute title
  ? What type of attribute string
  ? Do you want to add another attribute? Yes
  ? Name of attribute url
  ? What type of attribute string
  ? Do you want to add another attribute? Yes
  ? Name of attribute keyword
  ? What type of attribute text
  ? Do you want to add another attribute? Yes
  ? Name of attribute description
  ? What type of attribute richtext
  ? Do you want to add another attribute? Yes
  ? Name of attribute keyword
  ? What type of attribute text
  ? Do you want to add another attribute? No
  ? Where do you want to add this model? Add model to new API
  ? Name of the new API? reading-list
  ? Bootstrap API related files? Yes
  ✔ ++ /api/reading-list/content-types/reading-list/schema.json
  ✔ +- /api/reading-list/content-types/reading-list/schema.json
  ✔ ++ /api/reading-list/controllers/reading-list.ts
  ✔ ++ /api/reading-list/services/reading-list.ts
  ✔ ++ /api/reading-list/routes/reading-list.ts
```

This is a base schema, but we can improve them by adding regex and unique to the url field and also making the title and the url required, this is result of the schema can put under api/reading-list/content-types/reading-list/schema.json: 
```
{
  "kind": "collectionType",
  "collectionName": "reading_lists",
  "info": {
    "singularName": "reading-list",
    "pluralName": "reading-lists",
    "displayName": "Reading List",
    "description": ""
  },
  "options": {
    "draftAndPublish": false
  },
  "attributes": {
    "title": {
      "type": "string",
      "required": true
    },
    "url": {
      "type": "string",
      "regex": "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)",
      "required": true,
      "unique": true
    },
    "description": {
      "type": "richtext"
    },
    "keyword": {
      "type": "text",
      "required": false
    }
  }
}
```

![Screenshot of strapi content type](https://www.njfamirm.ir/img/IzMncCUc-2524.jpeg)

Very good, Now we have simple reading list in strapi, let's make them searchable!

## Search Custom API

In this step we'll use the strapi-generate command again to creates a custom API pure file structure.

```
yarn strapi generate

  ? Strapi Generators api - Generate a basic API
  ? API name reading-list-search
  ? Is this API for a plugin? No
  ✔ ++ /api/reading-list-search/routes/reading-list-search.ts
  ✔ ++ /api/reading-list-search/controllers/reading-list-search.ts
  ✔ ++ /api/reading-list-search/services/reading-list-search.ts
```

Now we need to fill the files with the correct codes.

## Service

Services are a set of reusable functions to simplify controllers' logic. <sup> <a href="https://docs.strapi.io/dev-docs/backend-customization/services" target="_blank">ReadMore</a></sup>

In this step, we use the service to create a function to get the reading list and filter them. The keyword parameter is used to filter the reading-list based on their keywords, and if no keyword is given, it returns all content of reading-list.

`api/reading-list-search/services/reading-list-search.js`:

```
import {errors} from '@strapi/utils';

async function search(keyword?: string) {
  try {
    const entries = await strapi.entityService.findMany('api::reading-list.reading-list', {});

    let filteredEntries = entries;
    if (keyword) {
      filteredEntries = entries.filter((entry) => {
        const keywords = entry.keyword.split('\n').map((keyword) => keyword.trim());
        return keywords.includes(keyword);
      });
    }

    return filteredEntries;
  } catch (err) {
    throw new errors.ApplicationError('Something went wrong');
  }
}

export default {search};
```

this function first gets reading-list with [findMany](https://docs.strapi.io/dev-docs/api/entity-service/crud#findmany) and then filters them using the typescript filter function.

## Controller

Controllers contain a set of methods, called actions, reached by the client according to the requested route. <sup> <a href="https://docs.strapi.io/dev-docs/backend-customization/controllers" target="_blank">ReadMore</a></sup>

To write a controller for the reading-list search function, set the content of the `api/reading-list-search/controllers/reading-list-search.js` file something like this to pass keyword search query into search service function.

```
async function search(ctx) {
  try {
    return await strapi.service('api::reading-list-search.reading-list-search').search(ctx.request.query.keyword);
  } catch (err) {
    ctx.badRequest('Reading list search controller error', {detail: err});
  }
}

export default {search};
```

## Route

Requests sent to Strapi on any URL are handled by routes. <sup> <a href="https://docs.strapi.io/dev-docs/backend-customization/routes" target="_blank">ReadMore</a></sup>

We must add this endpoint and sets the previous controller as the route handler, put this code to `api/reading-list-search/routes/reading-list-search.ts`:

```
export default {
  routes: [
    {
      method: 'GET',
      path: '/reading-list-search',
      handler: 'reading-list-search.search',
      config: {
        policies: [],
      },
    },
  ],
};
```

Now this route is accessible at [/api/reading-list-search](http://localhost:1337/api/reading-list-search), of course if we do the next step 🤓!

## Set Permission

By default, this API can't be accessible publicly so set the correct permission.

![Screenshot of set permission for strapi custom API](https://www.njfamirm.ir/img/5Nszux28-3394.jpeg)

## Result

Congratulations 🎉, we create a custom API for searching in our reading-list using Strapi CMS.

![Screenshot of reading list custom API response](https://www.njfamirm.ir/img/t2XQottQ-2318.jpeg)

I hope this article is useful for you, and it is a step towards a society where we grow together ♥️

* * *

Thanks [@derrickmehaffy](https://github.com/derrickmehaffy) for improve this article.