Product
Resources
Anumadu Moses
March 17, 2021
This article is a guest post by Anumadu Moses. He wrote this blog post through the Write for the Community program. If you are passionate about everything jamstack, open-source or javascript and want to share, join the writer's guild!
Strapi is a headless content management system (CMS) built with React.js and Node.js. It is easy to learn. It supports PostgreSQL, MongoDB, SQLite, MySQL and MariaDB. You can work with any of the above-mentioned databases to build your next big Strapi project.
A headless content management system (CMS) is simply a CMS consisting of the backend only. It delivers content through an application programming interface (API). Strapi has become the first choice of many developers in the Jamstack ecosystem.
Strapi has a very unique yet powerful design pattern that lets you build almost anything. It has a plugin design pattern. You can think of plugins in Strapi as smaller applications with unique functionalities that are injected into the main project. It would help to think of a single Strapi plugin as a house in a housing estate. The Strapi app is the housing estate. A Strapi application can be expanded beyond the scope of its core features with the power of plugins. Remember when we talked about React.js and Node.js? You will need a background knowledge of those to be able to build upon Strapi. React and Node are the main technologies used in Strapi and have grown so much in popularity recently. You can get started looking at their respective documentations if you are not familiar with them. The scope of this tutorial would not cover the essentials of Node and React. I will assume you already know the basics.
Remember how we described Strapi plugins as individual apps all within the main Strapi application. The part of the plugin that shows up on the Strapi dashboard is built with React.js components. These components are in plugin→admin→src→components. The image below further explains the folder structure of a Strapi plugin.
plugin/
└─── admin/ # Contains the plugin's front-end
| └─── src/ # Source code directory
| └─── index.js # Entry point of the plugin
| └─── pluginId.js # Name of the plugin
| |
| └─── components/ # Contains the list of React components used by the plugin
| └─── containers/
| | └─── App/ # Container used by every other container
| | └─── Initializer/ # This container is required. It is used to executed logic right after the plugin is mounted.
| └─── translations/ # Contains the translations to make the plugin internationalized
| └─── en.json
| └─── index.js # File that exports all the plugins translations.
| └─── fr.json
└─── config/ # Contains the configurations of the plugin
| └─── functions/
| | └─── bootstrap.js # Asynchronous bootstrap function that runs before the app gets started
| └─── policies/ # Folder containing the plugin's policies
| └─── queries/ # Folder containing the plugin's models queries
| └─── routes.json # Contains the plugin's API routes
└─── controllers/ # Contains the plugin's API controllers
└─── middlewares/ # Contains the plugin's middlewares
└─── models/ # Contains the plugin's API models
└─── services/ # Contains the plugin's API services
To master the concept of plugin development, we will have to build one. In this tutorial, we will learn how to build a custom Strapi plugin. This custom plugin will allow a Strapi admin to send emails to a provided email address from the Strapi application dashboard.
It’s time to get our hands dirty writing the code. Let's get started by installing Strapi using the following command:
yarn create strapi-app my-plugin-project --quickstart
Using the quickstart
command instructs Strapi to select all the necessary options and speeds up the installation process.
Once the installation is completed, our project will be launched in the browser prompting us to create the first admin account.
Go ahead and create your admin account by providing the necessary details. Then create a few collections in the Strapi dashboard too. Look up the official documentation if you need help creating collections.
To create our plugin, we will use the following Yarn command.
yarn strapi generate:plugin customPlugin
After generating our plugin, Notice the similarity in the folder structure with what we have in the plugin structure above.
There is an index.js
file at the root of the admin folder all within the plugins folder. The file contains the following:
import pluginPkg from '../../package.json';
import pluginId from './pluginId';
import App from './containers/App';
import Initializer from './containers/Initializer';
import lifecycles from './lifecycles';
import trads from './translations';
export default strapi => {
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
const icon = pluginPkg.strapi.icon;
const name = pluginPkg.strapi.name;
const plugin = {
blockerComponent: null,
blockerComponentProps: {},
description: pluginDescription,
icon,
id: pluginId,
initializer: Initializer,
injectedComponents: [],
isReady: false,
isRequired: pluginPkg.strapi.required || false,
layout: null,
lifecycles,
mainComponent: App,
name,
preventComponentRendering: false,
trads,
menu: {
pluginsSectionLinks: [
{
destination: `/plugins/${pluginId}`,
icon,
label: {
id: `${pluginId}.plugin.name`,
defaultMessage: name,
},
name,
permissions: [
// Uncomment to set the permissions of the plugin here
// {
// action: '', // the action name should be plugins::plugin-name.actionType
// subject: null,
// },
],
},
],
},
};
return strapi.registerPlugin(plugin);
};
Notice the pluginsSectionLinks
method, it displays the icon of our plugin and mainComponent
displays what we have in the App
component in the component page.
The App
component can be found in the containers/HomePage/index.js directory. It holds what is displayed when we click on our component icon, similar to what we can see below.
Let’s make a few changes to the file and earn ourselves a bragging right. Before that, remember to restart the development server with the following command:
yarn develop --watch-admin
Edit the index.js file and add the following lines of code to it:
const HomePage = () => {
return (
<div>
<h1>I changed my title</h1>
<p>Hi! I just earned my self a bragging right for
chaning the title of this plugin</p>
</div>
);
};
Nice! You should have seen the changes by now from your browser. Strapi plugins are really easy to build (We just built one). Let's proceed by modifying our plugin into an email list.
In admin/src create the following components/MyCustomPlugin/index.js. Let us create a React component in our newly created file. Add the following lines of code below.
~~~~
import React, { Component } from 'react';
class MyCustomPlugin extends Component {
render() {
return (
<div className="App">
<h2>Yeah! My component is working fine </h2>
</div>
);
}
}
export default MyCustomPlugin;
We need to import our newly created component into containers/HomePage/index.js for it to show up on our screen. The index.js file above is like the root of our plugin. It holds everything that shows on the screen. Edit it like so:
/*
*
* HomePage
*
*/
import React from 'react';
import pluginId from '../../pluginId';
import MyCustomPlugin from '../../components/MyCustomPlugin'
const HomePage = () => {
return <MyCustomPlugin/>;
};
export default HomePage;
Here, we imported our component using import MyCustomPlugin from '../../components/MyCustomPlugin'
and added our component to the page using the tag <MyCustomPlugin/>
.
Now we can check it out to ensure our component was imported correctly.
I hope your screen looks like mine now:
Good job if your on the same page with me. If your not, keep debugging till you do. Okay, we need to add a form to our newly created component.
In the newly created component, add the following HTML and react code to add and process our form:
import React, { Component } from 'react';
import axios from 'axios'
class App extends Component {
constructor(props){
super(props);
this.state = {emailAddress: '', emailBody: ''};
}
handleChange = (event) => {
axios.post('http://localhost:1337/email/create', {
emailAddress: this.state.emailAddress,
emailBody: this.state.emailBody
}).then((response) => {
console.log(response);
}, (error) => {
console.log(error);
});
event.preventDefault();
}
bindEmailEvent = (event) => {
this.setState({emailAddress: event.target.value});
}
bindBodyEvent = (event) => {
this.setState({emailBody:event.target.value})
}
render() {
return (
<div className="App container pt-5">
<h2>My Email list plugin</h2>
<p>{this.state.value}</p>
<form onSubmit={this.handleChange}>
<div className="form-group">
<label htmlFor="exampleFormControlInput1">Email address</label>
<input type="email" class="form-control" value={this.state.emailAddress} onChange={this.bindEmailEvent} placeholder="name@example.com"/>
</div>
<div className="form-group">
<label htmlFor="exampleFormControlTextarea1">Example textarea</label>
<textarea class="form-control" value={this.state.emailBody} onChange={this.bindBodyEvent} rows="3"></textarea>
</div>
<button type="submit" class="btn btn-primary">Send Email</button>
</form>
</div>
);
}
}
export default App;
We created a basic HTML form using react.
In the input field and the text area of the form, you’ll notice onChange={this.bindBodyEvent}
and onChange={this.bindEmailEvent}
respectively. These methods bind the values of the input fields to values in the component state. This is a basic react. It has nothing to do with Strapi. value={this.state.emailAddress}
and value={this.state.emailBody}
binds the value in the state to themself.
onSubmit={this.handleChange}
as found in the form simply calls the handleChange
method. In this method, an API call is made to the Strapi backend where the logic of sending our email would be done. This is basic and simple. The API call uses Axios, and it calls the following endpoint http://localhost:1337/email/create
. This IP address is used to access Strapi’s back end. Wondering who told me that? Well here is how I found out.
The following route has not been created yet. We would create that in a second. For now, if everything is done properly, your form should look like mine below:
Let us get started creating our Strapi email back end. To send an email from Strapi, we have to use Strapi’s build-in email plugin. To read more on this click here.
Sending Email in Strapi requires the installation of an email provider. For the purpose of this tutorial, we will use Mailtrap as our email provider. Use the command below to install:
yarn add strapi-provider-email-mailtrap
or
npm install strapi-provider-email-mailtrap --save
After installing, We need to do some configurations to get our email provider to work correctly. In the ./config
folder directory, create a new file named plugins.js
and paste the following line of code in it.
module.exports = ({ env }) => ({
// ...
email: {
provider: 'mailtrap',
providerOptions: {
user: env('MAILTRAP_USER', '4a7xxxxxxx92'),
password: env('MAILTRAP_PASSWORD', 'df1xxxxxxfd')
},
settings: {
defaultFrom: env('MAILTRAP_DEFAULT_FROM', 'default@value.com'),
defaultReplyTo: env('MAILTRAP_DEFAULT_REPLY_TO', 'default@value.com'),
},
}
// ...
});
If you do not have a mailtrap.io account already, you would need to create one. If you do, you would need your user name and password to replace mine above. After creating your account, you can find your user name and password when you click on inboxes → selecting an inbox → smtp settings. Click on the drop-down and select nodemailer
option from the drop-down.
Let us proceed to create the routes and controllers to send our email. In the API folder directory, You would find all the collections you created in your Strapi dashboard.
I created an article collection and article-category collection, respectively. The process is outlined below:
Our article schema should look like the image below:
Head back to your codebase. You would notice the changes in the folder structures. This is one of the fantastic features of Strapi. Look at mine below.
I will use my articles collection for the following steps, and you can use any collection you have in your project.
In the newly created article collection, you will find a routes.json
file at ./api/article/config/routes.json
. This file contains code like so:
{
"routes": [
{
"method": "GET",
"path": "/articles",
"handler": "articles.find",
"config": {
"policies": []
}
},
{
"method": "GET",
"path": "/articles/count",
"handler": "articles.count",
"config": {
"policies": []
}
},
{
"method": "GET",
"path": "/articles/:id",
"handler": "articles.findOne",
"config": {
"policies": []
}
},
{
"method": "POST",
"path": "/articles",
"handler": "articles.create",
"config": {
"policies": []
}
},
{
"method": "PUT",
"path": "/articles/:id",
"handler": "articles.update",
"config": {
"policies": []
}
},
{
"method": "DELETE",
"path": "/articles/:id",
"handler": "articles.delete",
"config": {
"policies": []
}
}
]
}
We just need to add one more route for our email controller. Add the following to the above:
{
"method": "POST",
"path": "/email/create",
"handler": "Email.create",
"config": {
"policies": []
}
},
The handler points to the method in the controller. Path points to the route. To continue, create a new file name `Email.js`. in the `./api/article/controllers` directory and add the following to it :
module.exports = {
create: async ctx => {
await ctx.send({
message: "email sent from post request"
})
}
}
This is a controller in Strapi. Strapi controllers are JavaScript modules that contains set of methods called actions. Actions or methods can be reached or called up through an assigned route. Routes point a particular endpoint to an action in a controller. For more on Strapi controller click here . Our method above is called create
because the handle in the route points to Email.create
. To receive data from our HTML form created above and send our email, Let us modify our code like so:
module.exports = {
create: async ctx => {
const email = ctx.request.body.emailAddress;
const body = ctx.request.body.emailBody;
await strapi.plugins['email'].services.email.send({
to: email,
from: 'admin@strapi.io',
subject: 'testing email',
text: body,
});
await ctx.send({
message: "email sent from post request"
})
}
}
We need to grant permission to our newly created route. You can update permissions to our route from your Strapi dashboard in settings → users & permissions plugins → roles → public → email → create. Set this to public, and we will be good to go.
Now we need to test from our dashboard that our form is actually working. On the plugin page, enter a valid email address and body of the email, then send.
If everything is in order, you will receive the email in your Mailtrap dashboard. With that done, we are good to go.
Congrats you’ve made it this far. Here is a brief walk-through of this plugin in use.
If you followed all the steps by now, you should have successfully created your first Strapi plugin that should work like what we have in the video above.
Find a link to the complete project on Github here.
Strapi has a marketplace. You can find a link to it in your admin dashboard. I’ll list a few of some useful Strapi plugins you should consider using in your next Strapi project:
You can make your custom Strapi plugin readily available through NPM. You can make your plugin a Node.js module that can be easily installed using an NPM command. For more on creating NPM packages, check the following link. For a hint on the folder structure of Strapi plugins, here is another helpful link Okay, you can build something great, and don’t forget to share it with the rest of the community. Good job.
Sign up for the Strapi newsletter to keep up with the latest news from the Strapi community!