This article is a guest post by Ukpai Ugochi. She wrote this blog post through the Write for the Community program.
As you imagine how intriguing your website should look, I bet you’re also thinking about managing the technical code behind it. You’re probably planning on taking some programming courses to make your website look as elegant as possible.
This is where a CMS (Content Management System) comes in. Just as the name implies, you can create, manage and modify website content without thinking much about the code.
A CMS works by either incorporating a traditional WYSIWYG editor, a decoupled CMS that separates the front end from the back end operation. Or a headless CMS that uses APIs (Application Programming Interface) to transfer data to any front end from your back end.
In this article, we will look into the best practices for Content Modelling in Strapi, but first, we should understand what Strapi is and why it could be the right content management system for you.
We know what a CMS is, so how does this relate to Strapi? And what does Strapi have to offer that other CMSs don’t?
Strapi is an open-source headless content management system for Node.js-based applications. We discussed earlier headless CMS and how they allow you to integrate APIs.
With Strapi, you can create customizable APIs, that can be integrated into your application in the shortest time possible. With this feature, you can get your Node.js application running in a few hours instead of weeks/months.
Have you heard of the modern web development architecture ‘Jamstack’? You can create your Jamstack application with Strapi as CMS for back-end functionalities.
This leads us to the question “Why not Strapi?”. There’s absolutely no reason to hesitate in using Strapi for your Node.js application. Let’s dive into the topic of today by considering the different Strapi Modelling types.
So, this is probably not the first place you’re coming across the word modelling. But, in this context it would only be fair to explain what Modelling has to do with Strapi. Modelling deals with the structure of your content. One can call models schemas—a visual representation of how your content is arranged in the database.
1// Example of NoSQL database model(MongoDB)
2{
3 _id: "1",
4 name: "Joe Bookreader",
5 age: "10 years,
6 class: "High school junior"
7}
In this section, we look at the different Strapi Modelling types briefly.
Content Type Modelling With Content-Type Modelling, the user creates a JSON file that represents how data will be structured in the database and a set of lifecycle hooks in JavaScript file that determines how and when data should be called into their web application.
With the Lifecycle hook in place, content-type data structures can only be used for one API’s model. The expression below shows a simple JavaScript file for Content Modelling containing Lifecycle hooks.
1// Export module so instance can be called in application
2module.exports = {
3// initiate lifecycles
4 lifecycles: {
5 // Called before an entry is created
6 beforeCreate(data) {},
7 // Called after an entry is created
8 afterCreate(result) {},
9 },
10};
The corresponding JSON file for Content Modelling type looks like the expression below.
1{
2 "kind": "collectionType",
3 "connection": "default",
4 "collectionName": "restaurants",
5 "info": {
6 "name": "restaurant",
7 "description": ""
8 },
9 "options": {
10 "increments": true,
11 "timestamps": [
12 "created_at",
13 "updated_at"
14 ],
15 "comment": "",
16 "draftAndPublish": true
17 },
18 "attributes": {
19 "cover": {
20 "collection": "file",
21 "via": "related",
22 "plugin": "upload",
23 "required": false
24 },
25 "name": {
26 "type": "string"
27 },
28 "description": {
29 "type": "text"
30 },
Component Modelling Component Modelling involves creating models with data structures that can be used in many API’s models. With this Modelling Type, you don’t need to create a lifecycle hook, all you need is a JSON file as shown in the expression below:
1{
2 "connection": "default",
3 "collectionName": "",
4 "info": {
5 "name": "restaurant",
6 "description": ""
7 },
8 "options": {
9 "timestamps": true
10 },
11 "attributes": {
12 "name": {
13 "default": "",
14 "type": "string"
15 },
16 "description": {
17 "default": "",
18 "type": "text"
You can generate a content type model with the command below in your CLI:
1// Syntax for generating a model
2strapi generate:model <name> [<attribute:type>]
3options: [--api <name>|--plugin <name>|--draft-and-publish <boolean>]
4
5//Example of generating a model
6strapi generate:model category name:string description:text --api product
Creating a model will generate two files; a JavaScript file and a JSON file. If you want to create a component model, you must use the Content-Types Builder from the Admin Panel. This is because there’s not a CLI generator for creating component models. Or, you can create your component manually by following the file path described previously. If you want to generate a complete API with configurations, controller, model, and service, run the command below in your CLI
1// Syntax for generating a complete API
2strapi generate:api <name> [<attribute:type>]
3options: [--plugin <name>]
4
5// Example of generating a complete API
6strapi generate:api address name:string description:text --plugin content-manager
Next, let’s look at how to implement Content Type Modelling in a Strapi application efficiently and best practices while using the Content Type Modelling.
We have seen in other sections the different Strapi Model Types and how to create a simple model. In this section, we will dive deeper in Content Type Modelling and the best practices to efficiently implement Content Type Modelling in our Strapi application.
Lifecycle Hooks We have seen that you can’t talk about Content Type Modelling without talking about Lifecycle hooks. Lifecycle hooks are the brain of any content model as they get triggered whenever Strapi queries are called. Let’s look at best practices in managing and creating custom content model’s Lifecycle hooks.
To configure Lifecycle hooks, set a Lifecycle key lifecycles: {}
with the available lifecycle in your model’s JavaScript file (
1// List of all available lifecycle hooks
2module.exports = {
3// initiate lifecycles
4 lifecycles: {
5 // Called before an entry is created
6 beforeCreate(data) {},
7 // Called after an entry is created
8 afterCreate(result, data) {},
9 // Called before entries are queried with find() method
10 beforeFind(params, populate) {},
11 // Called after entries are queried with find() method
12 afterFind(results, params, populate) {},
13 // Called before an entry is queried with findOne() method
14 beforeFindOne(params, populate) {},
15 // Called after an entry is queried with findOne() method
16 afterFindOne(result, params, populate) {},
17 // Called before an entry is updated
18 beforeUpdate(params, data) {},
19 // Called after an entry is updated
20 afterUpdate(result, params, data) {},
21 // Called before entries in a collection are counted
22 beforeCount(params) {},
23 // Called after entries in a collection are counted
24 afterCount(result, params) {},
25 // Called before searching strings in an entry
26 beforeSearch(params, populate) {},
27 // Called after searching strings in an entry
28 afterSearch(result, params) {},
29 // Called before entry search results are counted
30 beforeCountSearch(params) {},
31 // Called after entry search results are counted
32 afterCountSearch(result, params) {},
33 // Called before an entry is deleted
34 beforeDelete(params) {},
35 // Called after an entry is deleted
36 afterDelete(result, params) {},
37 },
38};
To change the properties of a Lifecycle hook, you can mutate its parameter and assign a fixed value to it (data.name =
'``fixed value``'``)
. A good practice to consider is to avoid reassigning the same parameter as it would have no effect.
You can as well customize Lifecycle hooks when building custom ORM (Object–relational mapping) specific queries. Lifecycles will not be triggered until the lifecycle function is called directly (manually) with expected parameters.
1module.exports = {
2 async createCustomEntry() {
3 const ORMModel = strapi.query(modelName).model;
4
5 const newCustomEntry = await ORMModel.forge().save();
6
7 // trigger manually
8 ORMModel.lifecycles.afterCreate(newCustomEntry.toJSON());
9 },
10};
Content Type Relations Content Type relations allow developers create one-way, one-to-one, one-to-many, many-to-many and polymorphic links among content types.
While a one-way relationship among Content Type is to link one entry to another entry. Although there’s a relationship between two entries, only one of the models can be queried with its linked item. A simple example is a daycare application that has a model for children in custody. The app registers only one parent that can pick the child up so, in this app the child is linked to a parent. Instead of writing each parent name by hand, one-way relationships help print the parent name wherever there’s a need for that.
Instead of linking an entry to another entry and querying only one model, you can link one entry to another entry and allow both models to be queried with one-to-one relationship. An example of one-to-one relationship usage is a school management app where each student is a member of a class. Each student has a class, and a student can be a member of only one class.
Just like one-way relationship, you can link an entry with multiple entries of another content type model with one-to-many relationship. However, in this context, one entry from a model is linked to multiple entries of another model and the entry of the other model is linked to only one entry. An example is a freelance app that has a user model and job model. With one-way relationship, you can link a user to their job(s) since a user can attend to multiple jobs at a time.
With many-to-many relationship, you can link an entry to multiple model entries. The other model entries can also be linked to multiple entries. A simple example is a movie app with directors, movie names and actors. The app has a directors model an actors model and a list of movie model. Many-to-many relationship allows you to link entries in the movie director, movie name and actor collections and save you from unnecessary repetition.
Content type models can also perform polymorphic relationships. This model relationship type is used for models that can be associated with different model types. With this relationship style, you can link different types of models to a model. Let’s look at the image model, you could want to attach this model to the user, vendor models etc. With polymorphic relationship, a vendor entry can relate to the same image as a user entry since a vendor can still be a user.
Model Structure Let’s look at the structure of a Strapi content type model, what the info key means and the name/value pairs that make up the model information. We would also look at the model option key and how to effectively use it.
The info key gives information about the model. For instance, the model name and description. The name key holds the value of the model’s name, this should be filled appropriately. While the description key could be left empty, it could also be filled with a short description of the model.
1{
2 "info": {
3 "name": "<modelName>",
4 "description": "" /**"description": "<short description about model>"**/
5 }
6}
As a good practice, you need to set some model connection settings appropriately for content models function properly. The kind,connection,collectionName,globalId, and attributes are model settings that can be applied on a Strapi content type model.
1{
2// For connection type of single type
3 "kind": "collectionType",
4
5// Connection name as defined in ./config/database.js
6 "connection": "<collection>",
7
8/*Collection name where data will be stored, can be changed anytime but data maybe lost in no process since there's no automatic migration.*/
9 "collectionName": "<collectionName>",
10
11// This will be adopted as the filename if filename is not configured manually
12 "globalId": "<globalId>",
13
14// Object to define the structure of your model
15 "attributes": {
16 name: {
17 type: String
18 ....
19 }
20 }
21}
The model option in the model JSON file can contain the following key/value pairs, to add more properties to the model that are not part of the connection settings.
1{
2 "options": {
3 // This will add timestamps to each data entry
4 "timestamps": true,
5 // This will allow some attributes to be treated as private
6 "privateAttributes": ["id", "created_at"],
7 /* This key takes a boolean to determine if API response should be populated by creator fields*/
8 "populateCreatorFields": true
9 }
10}
Attribute Validations In Strapi’s content type model, you can perform some attribute validations to limit, compare or manage data types that go into the collection or table. To apply basic validations to SQL databases, you’ll need to use native SQL constraints. The expression below shows some basic attribute validation that can be performed with MongoDB database connection.
1// This is a simple content type model (JSON File) with attribute validations
2{
3 "kind": "collectionType",
4 "connection": "default",
5 "collectionName": "partyhouse",
6 "info": {
7 "name": "partyhouse",
8 "description": ""
9 },
10 "options": {
11 "increments": true,
12 "timestamps": [
13 "created_at",
14 "updated_at"
15 ],
16/*Attributes containing basic validations for model property*/
17 "opening_hours": {
18 "type": "json"
19/*The value of this key is an integer, it validates the field, to make sure it's not below the min number*/
20 "min": 1,
21/*The value of this key is an integer, it validates the field, to make sure it's not above the max number*/
22 "max": 12
23/*This is a boolean, states if an attribute is required or not. If required, the field must be filled*/
24 "required": true,
25/*This is used to hide sensitive data from server response.*/
26 "private": true,
27/*This is used to define a unique index */
28 "unique": true,
29/*If you set this attribute to false, the property cannot be configured by the Content Type Builder plugin*/
30 "configurable": true,
31/*If you set this to false, the property will populate within REST responses*/
32 "autoPopulate": true,
33 },
34}
In this article, we looked at what Strapi is and what it promises. We have explored the different Strapi Model Types, and the best practices to consider while implementing Content Type’s model.
Most people like myself choose the Content Type Model system over the Component Model System since it comes with model options in the JavaScript file. In contrast to component type model system, content type models can be used everywhere in the project, they don’t necessarily need to be in a subfolder. I strongly advise you to use content type builder to generate models, if it’s your first time using Strapi.
It is possible to change Content Type Model default settings, but changing a model’s globalId
is totally a bad idea, come off it. Since content modeling deals with lifecycle hooks, it will be beneficial to understand lifecycle parameters and how they work.
Ukpai is a full-stack JavaScript developer and loves to share knowledge about her transition from marine engineering to software development to encourage people who love software development and don't know where to begin.