How to Customize the Strapi Back-end Using TypeScript
Tags: typescript, strapi, customization, backend
Published: 2022-11-29 11:06:34

**Author: Alex Godwin**

Every back-end service in existence has some form of system in place that enables them to receive requests and act on them appropriately (providing some response). Services, routes, and controllers are all represented in such servers. Strapi also has this system in place and offers ways for you to customize them to your preferences, such as adding capabilities or developing new ones. 

In this article, using TypeScript, we'll examine the various systems (services, controllers, and routes) that Strapi has in place to receive and respond to requests. You'll learn how to customize these systems since, as it turns out, most times, the default settings are frequently insufficient for achieving your goals; therefore, knowing how to do so is highly useful.

## What You'll Build
In this tutorial, you'll learn how to create an API for articles, which can be queried by either an API client or an application’s front-end. To better understand the internals of Strapi, you’ll have to build this from scratch and add the features.

## Prerequisites
Before continuing in this article, ensure you have the following:
- Knowledge of JavaScript,
- [Node.js](https://nodejs.org/) (v14 recommended for Strapi), and
- Access to an API client (e.g [Postman](https://www.postman.com) or [Insomnia REST](https://insomnia.rest))

## Introduction to Strapi
Strapi [is the leading open-source, customizable, headless CMS](https://strapi.io/) based on JavaScript that allows developers to choose their favorite tools and frameworks while also allowing editors to manage and distribute their content easily.

Strapi enables the world's largest companies to accelerate content delivery while building beautiful digital experiences by making the admin panel and API extensible through a plugin system.

## Getting started with Strapi using TypeScript
To install Strapi, [head over to the Strapi documentation](https://docs.strapi.io/developer-docs/latest/getting-started/introduction.html). We’ll be using the SQLite database for this project.

To install Strapi with TypeScript support, run the following commands:
```bash
        npx create-strapi-app my-app --ts
```
Replace `my-app` with the name you wish to call your application directory. Your package manager will create a directory with the specified name and install Strapi. If you have followed the instructions correctly, you should have Strapi installed on your machine. 

Run the following commands to start the Strapi development server:

```bash
        yarn develop # using yarn
        npm run develop # using npm
```
The development server starts the app on http://localhost:1337/admin.

## Building our Application Basics (Modelling Data and Content Types)
As part of the first steps, follow the instructions below:
1. [Open up the Strapi admin](http://localhost:1337/admin) dashboard in your preferred browser.
2. On the side menu bar, select `Content-Type Builder`.
3. Select `create new collection type`.
4. Give it a display name, `article`.
5. Create the following fields: 
    a. `title` as short text
    b. `slug` as short text
    c. `content` as rich text
    d. `description` as long text
    e. Create a relationship between articles and user (users_permissions_user).
    
![relationship between articles and users](https://paper-attachments.dropbox.com/s_191B973310CD764DE72A28FD14CCCE837BC0825E832C976BE648D86B00F00961_1663294126369_Screenshot+from+2022-09-16+03-07-05.png)

### Using the "Strapi generate" Command
You just created an `article`  content type; you can also create content types using the `strapi generate` command. Follow the instructions below to generate a category content-type using the `strapi generate` command.

1. In your terminal, run `yarn strapi generate` or `npm run generate`.
2. Select `content-type`.
3. Name the content-type `category`.
4. Under "Choose the model type," select `Collection Types`.
5. Do not add a draft and publish system.
6. Add name as `text` attribbute.
7. Proceed and select `add model to new API`.
8. Name the API category.
9. Select `yes` to Bootstrap API related files.

To verify, [open up the Strapi admin dashboard](http://localhost:1337/admin) in your preferred browser. You should see that a category content type has been created.

Next, it's time to create a relationship between the category and the article content-types:

1. In the Strapi admin dashboard, navigate to the article content-type.
2. Add a new `Relation field` as follows (see image below).
3. Click save.

![relationship between category and article](https://paper-attachments.dropbox.com/s_191B973310CD764DE72A28FD14CCCE837BC0825E832C976BE648D86B00F00961_1663298061929_Screenshot+from+2022-09-16+04-12-59.png)

You now have a base API alongside all the necessary content types and relationships.

## Permissions
Since this article focuses on services, routes, controllers, and queries, the next phase involves opening up access to the user, article, and category content type to public requests. 

1. On the side menu, select `settings`.
2. Under `users & permissions plugins`, select `roles`.
3. Click on `public`.
4. Under `permissions`,
    a. Click `Articles`, and check "select all".
    b. Click `Category` and check "select all".
    c. Click `users-permission`, scroll to "users" and check the "select all" box.
5. Click save.

Now, all content-type activities are available to public requests; this would allow us to make requests without having to get JWTs. Finally, create a user with a public role and create some categories.

## Generate Typings
To allow us to use the correct types in our projects, you must generate TypeScript typings for the project schemas.

1. Run the following command in your terminal:
```bash
    yarn strapi ts:generate-types //using yarn
    or
    npm run strapi ts:generate-types //using npm
```
In the project's root folder, you should notice that a `schema.d.ts` file has been created. You may note when you browse the file that it contains type definitions for each of the project's content-types.

2. We also need the general types; [copy the code from this link GitHub](https://gist.github.com/Convly/6cf1e6d143bb0a90c8de2242fdedda8e)  into a `general-schemas.d.ts` file that you’ll create in the project’s root folder.

## Introduction to Strapi Services
Services are reusable functions that typically carry out specialized activities; however, a collection of services may work together to carry out more extensive tasks.

Services should house the core functionality of an application, such as API calls, database queries, and other operations. They help to break down the logic in controllers.

## Customizing Strapi Services
Let's look at how to modify Strapi's services. To begin, open the Strapi backend in your preferred code editor.

### Modifying the "Article" Services
1. Navigate to the `src/api/article/services/article.ts` file.
2. Replace its content with the following lines of code:
```ts
    import { factories } from '@strapi/strapi';
    import schemas from '../../../../schemas'
    import content_schemas from '../../../../general-schemas';
    
    export default factories.createCoreService('api::article.article', ({ strapi }): {} => ({
    
        async create(params: { data: content_schemas.GetAttributesValues<'api::article.article'>, files: content_schemas.GetAttributesValues<'plugin::upload.file'> }): Promise<schemas.ApiArticleArticle> {
            params.data.publishedAt = Date.now().toString()
            const results = await strapi.entityService.create('api::article.article', {
                data: params.data,
            })
            return results
        },
    }))
```
The block of code above modifies the default Strapi `create()` service.
 
-  `params.data.publishedAt` is set to the current time(i.e `Date.now()`). Since we are using the `DraftAndPublish` system, we want whatever is being created through the API to be published immediately.

- `strapi.entityService.create()` is being called to write data to the database; we’ll learn more about `entity services` in the next sub-section.

Let’s add a service that’ll help us with slug creation (if you look at the article content type, you’ll notice that we have a slug field). It’d be nice to have slugs auto-generated based on the title of an article. 

1. You'll need to install two npm packages: [slugify](https://npmjs.com/slugify) and [randomstring](https://npmjs.com/randomstring). In your terminal, run the following commands:

```bash
    yarn add slugify randomstring //using yarn
    or
    npm install slugify randomstring //using npm
```

2. Update the content of `src/api/article/services/article.ts` with the following code:

```ts
    import { factories } from '@strapi/strapi';
    import slugify from 'slugify';
    import schemas from '../../../../schemas';
    import content_schemas from '../../../../general-schemas';
    import randomstring from 'randomstring';
    
    export default factories.createCoreService('api::article.article', ({ strapi }): {} => ({
    
        async create(params: { data: content_schemas.GetAttributesValues<'api::article.article'>, files: content_schemas.GetAttributesValues<'plugin::upload.file'> }): Promise<schemas.ApiArticleArticle> {
            params.data.publishedAt = Date.now().toString()
            params.data.slug = await this.slug(params.data.title)
            const results = await strapi.entityService.create('api::article.article', {
                data: params.data,
            })
            return results
        },
    
        async slug(title: string): Promise<string> {
            const entry: Promise<schemas.ApiArticleArticle> = await strapi.db.query('api::article.article').findOne({
                select: ['title'],
                where: { title },
            });        
            let random = entry == null ? '' : randomstring.generate({
                length: 6,
                charset: 'alphanumeric'
            })
            return slugify(`${title} ${random}`, {
                lower: true,
            }) 
        }
    }));
```

The code above is to add a `slugify` function to our services. Pay close attention to the code, and you’ll notice the use of `strapi.db.query().findOne()`. This is a concept called `Queries Engine API`. Alongside `entity services`, `Queries` are a means to interact with the database.

Let’s test our services to see that they work fine. Open up your API client and make a `POST` request to the following route `http://localhost:1337/api/articles`.


![API request to create an article](https://paper-attachments.dropbox.com/s_191B973310CD764DE72A28FD14CCCE837BC0825E832C976BE648D86B00F00961_1663483757904_Screenshot+from+2022-09-18+07-43-46.png)

Open the admin dashboard and view the article you just created.

![Article collection type](https://paper-attachments.dropbox.com/s_191B973310CD764DE72A28FD14CCCE837BC0825E832C976BE648D86B00F00961_1663485000156_Screenshot+from+2022-09-18+08-06-28.png)

If you try to create entries with the same title, you’ll notice that the duplicate entries have different slugs.

## Entity Services and Query API
Entity services and queries both allow us to interact with the database. However, Strapi recommends using the [Entity service API](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service-api) whenever possible as it is an abstraction around the [Queries API](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine-api.html), which is more low-level. The [Strapi documentation](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine-api.html) gives accurate information on when to use one or the other, but I’ll go over it a bit.

The Entity service API provides a couple of methods for CRUD operations, i.e. (`findOne()`, `create()`, `findMany()`, `update()`, and `delete()`). However, it could fall short more frequently than not as it lacks the flexibility of the Query API. For instance, using a `where` clause with Entity services is not possible, whereas doing so with the Query API is. The distinction between the `findOne()` methods used by the Query APIs and the Entity service provides another striking illustration. The Query API allows us to specify the condition using a where clause, whereas the Entity service only allows us to use `findOne()` with an `id`.

## Introduction to Strapi Controllers
Controllers are JavaScript files with a list of actions the client can access based on the specified route. The C in the model-view-controller (MVC) pattern is represented by the controller. Services are invoked by controllers to carry out the logic necessary to implement the functionality of the routes that a client requests.

## Customizing Strapi Controllers
Let's examine how to alter Strapi controllers. Start by opening the Strapi backend in your favorite code editor.

1. Navigate to the `src/api/article/controllers/article.ts` file
2. Replace its content with the following lines of code:

```ts
    /**
     * article controller
     */
    import { factories } from '@strapi/strapi'
    import schemas from '../../../../schemas'
    import content_schemas from '../../../../general-schemas';
    
    export default factories.createCoreController('api::article.article', ({ strapi }): {} => ({
    
        async find(ctx: any): Promise<content_schemas.ResponseCollection<'api::article.article'>> {
            return await super.find(ctx)
        }
    }));
```

We are modifying the default `find()` controller, although it still has the same functionality because the `super.find()` method is the default action for the find controller. The `ctx` object contains data about the request from the client, e.g. `ctx.request`, `ctx.query`, `ctx.params`. We’ll see more about controllers soon enough.

## Strapi Routes

[Routes handle all requests that are sent to Strapi](https://docs.strapi.io/developer-docs/latest/development/backend-customization/routes.html#implementation). Strapi automatically creates routes for all content-types by default. Routes can be specified and added.

Strapi allows two (2) different router file structures:

1. Configuring Core Routers: Enables the extension of the default Strapi routers functionality.
2. Creating Custom Routers: Allows to develop completely new routes.

[Strapi provides different params to go with the different router file structures](https://docs.strapi.io/developer-docs/latest/development/backend-customization/routes.html#configuring-core-routers). Let’s see how to work with both types of routers.

## Customizing Strapi Routes
1. Configuring Core Routers: A core router file is a Javascript file exporting the result of a call to `createCoreRouter` with some parameters. Open up your `src/api/article/routes/article.ts` file and update it’s contents with the following:
```ts    
    import { factories } from '@strapi/strapi'; 
    
    export default factories.createCoreRouter('api::article.article', {
      only: ['find'],
      config: {
        find: {
          auth: false,
          policies: [],
          middlewares: [],
        }
      }
    });
```
The `only` array signifies what routes to load; anything not in this array is ignored. No authentication is required when auth is set to false, making such a route accessible to everyone.

2. Creating Custom Routers: To use a custom router, we must create a file that exports an array of objects, each of which is a route with a set of parameters. 

### Naming Convention
Routes files are loaded in alphabetical order. To load custom routes before core routes, make sure to name custom routes appropriately (e.g. `01-custom-routes.js` and `02-core-routes.js`). Create a file `src/api/article/routes/01-custom-article.ts`, then fill it up with the following lines of code:

```ts
    export default {
        routes: [
            { 
            // Path defined with an URL parameter
                method: 'GET',
                path: '/articles/single/:slug',
                handler: 'article.getSlugs',
                config: {
                    auth: false
                }
            },
        ]
    }
```

**Below are some things to note from the above snippet of code:**

1. The `handler` parameter allows us to specify which controller we would like to use in handling requests sent to the `path`. it takes the syntax of `<controllerName>.<actionName>`.
2. Hence, the code above will cause Strapi to throw an error and exit because we do not have a `getSlugs` method in our `article controller`. We will create the `getSlugs` method soon.
3. We have auth set to false, which means that this route is available for public requests.


## Case Study: Building a Slug Route
In this case study, a route, together with its controllers and services, will be built. The route enables us to retrieve an article using its slug.

1. Return to the `01-custom-article.ts` file; it’s already set, what you’ll have to do now is build the `getSlugs` action for the `article controller`.
2. Open your `src/api/article/controllers/article.ts` file. Add the following lines of code to it - just below the `find` method.

```js
    //... other actions
    async getSlugs(ctx: any): Promise<schemas.ApiArticleArticle['attributes']> {
            const data = {
                params: ctx.params,
                query: ctx.query
            }
            let response = await strapi.service('api::article.article').getSlugs(data)
            delete response.users_permissions_user.password
            delete response.users_permissions_user.resetPasswordToken
            delete response.users_permissions_user.confirmationToken
            return response
        }
    //... other actions
```

In the code snippet above, `ctx.params` contain the dynamic parameters from the route and `ctx.query` contains all additional query params.  We pass an object made up of both `ctx.params and ctx.query` to the `getSlugs()` service.

3. Open your `src/api/article/services/article.ts` file and copy the lines of code below into it - just below the slugs method.
```js
    //... other services
      async getSlugs(params: { params: any, query: any }): Promise<schemas.ApiArticleArticle> {
            
            if(params.query.populate == '*') {
                params.query.populate =  [ 'category', 'users_permissions_user' ] 
            } else if(params.query.populate != undefined) {
                params.query.populate = [ params.query.populate ]
            }
            
            const data: Promise<schemas.ApiArticleArticle['attributes']> = await strapi.db.query('api::article.article').findOne({
                where: { slug: params.params.slug },
                populate: params.query.populate || []
            })
    
            delete data.users_permissions_user.password
            return data
        }
    //... other services
```

Finally, it's time to create the `getSlugs` service. Using the `Query API`, search for an article that has the same slugs as that given to the params. We'll also populate the data depending on the query params.

Open up your API client and make a GET request to the following URL `http://localhost:1337/api/articles/single/${slug}?populate=*` here `${slug}` represents a valid slug from your database.

![](https://paper-attachments.dropbox.com/s_191B973310CD764DE72A28FD14CCCE837BC0825E832C976BE648D86B00F00961_1663745511709_Screenshot+from+2022-09-21+08-30-04.png)


## Conclusion
You've broken out the services, controllers, and routes of the Strapi in this article. You have written TypeScript code that demonstrates how to edit and build these internal processes from the ground up. You learned how to generate appropriate types. Now that you know more about what's happening in your Strapi backend, hopefully, you can approach it with more confidence going forward.