Strapi
Published in

Strapi

How to Customize the Strapi Back-end Using TypeScript

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.

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:

Introduction to Strapi

Strapi is the leading open-source, customizable, headless CMS based on JavaScript that allows developers to choose their favorite tools and frameworks while also allowing editors to manage and distribute their content easily.

Getting started with Strapi using TypeScript

To install Strapi, head over to the Strapi documentation. We’ll be using the SQLite database for this project.

npx create-strapi-app my-app --ts
yarn develop # using yarn
npm run develop # using npm

Building our Application Basics (Modelling Data and Content Types)

As part of the first steps, follow the instructions below:

  1. On the side menu bar, select Content-Type Builder.
  2. Select create new collection type.
  3. Give it a display name, article.
  4. 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

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. Select content-type.
  2. Name the content-type category.
  3. Under “Choose the model type,” select Collection Types.
  4. Do not add a draft and publish system.
  5. Add name as text attribbute.
  6. Proceed and select add model to new API.
  7. Name the API category.
  8. Select yes to Bootstrap API related files.
  1. Add a new Relation field as follows (see image below).
  2. Click save.
relationship between category and article

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. Under users & permissions plugins, select roles.
  2. Click on public.
  3. 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.
  4. Click save.

Generate Typings

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

yarn strapi ts:generate-types //using yarn
or
npm run strapi ts:generate-types //using npm

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.

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:
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
},
}))
  • strapi.entityService.create() is being called to write data to the database; we’ll learn more about entity services in the next sub-section.
yarn add slugify randomstring //using yarn
or
npm install slugify randomstring //using npm
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,
})
}
}));
API request to create an article
Article collection type

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 whenever possible as it is an abstraction around the Queries API, which is more low-level. The Strapi documentation gives accurate information on when to use one or the other, but I’ll go over it a bit.

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. Replace its content with the following lines of code:
/**
* 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)
}
}));

Strapi Routes

Routes handle all requests that are sent to Strapi. Strapi automatically creates routes for all content-types by default. Routes can be specified and added.

  1. Creating Custom Routers: Allows to develop completely new routes.

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:
import { factories } from '@strapi/strapi'; 

export default factories.createCoreRouter('api::article.article', {
only: ['find'],
config: {
find: {
auth: false,
policies: [],
middlewares: [],
}
}
});

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:

export default {
routes: [
{
// Path defined with an URL parameter
method: 'GET',
path: '/articles/single/:slug',
handler: 'article.getSlugs',
config: {
auth: false
}
},
]
}
  1. 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.
  2. 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. Open your src/api/article/controllers/article.ts file. Add the following lines of code to it - just below the find method.
//... 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
//... 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

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.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store