Pairing Strapi with a separate backend
In this article, we discover what Strapi is briefly and then explore an architecture where we have a CMS as well as a separate backend. This article specifically talks about a NestJS backend, but it can be applied to any type of backend.
What is Strapi?
One way to sum up Strapi is that it provides the tools to create different content types and create content using the said content types.
Examples for content type can be an article schema (which contains fields like article, author, content…etc), author schema (which contains fields like name, articles…etc). And content created using these types could be a specific article under a named author.
Strapi architecture
Content types can only be created or edited when Strapi is running in development mode. The interesting part about content types is that these are not stored in the database. Instead they are stored as JSON files in the src
folder.
This means that content types are managed using version control (since they are part of the repo). To sync new content types to your production deployment, you need to pull from the version control.
Setting up Strapi
One way to get started is to create a repo using a template from Strapi . However, if you want to setup a Strapi with TypeScript support:
yarn create strapi-app my-project --ts
yarn
to install packages- Copy the provided
.env.example
and rename it to a new.env
- Add database credentials to
.env
file
# DB
DATABASE_CLIENT=postgres # If using a Postgres DB
DATABASE_HOST=test.postgres.database.azure.com
DATABASE_USERNAME=sampleuser
DATABASE_PASSWORD=pwd
DATABASE_SSL=true
Gotcha: if the database is a Postgres one, you might have to manually install pg
package by running yarn add pg
to install the driver.
Next, run yarn develop
to start Strapi in auto reload mode. Access the admin portal using http://localhost:1337/admin During the first time login, you will be prompted to create an admin user.
Collection types and adding content
A new collection type can be created by clicking on Content-Type Builder and then Create new collection type. When you are creating a new collection type, you are given the option to add API IDs.
Once you press on Continue, you are shown the screen below where you can certain fields of different types.
And remember, creating a new content type will add new files in our code. Don’t forget to commit these!
After saving this, you can go to the Content Manager to add content from the type we just created (i.e. we can create new articles). Select the Collection Type as Article. You will be shown a list of existing articles (if any) and a Create new entry button.
Strapi endpoints
Now that we have our content schema and content in place, how do we retrieve these from our ReactJS or React Native app?
Strapi has a REST API and a GraphQL API. In this article we will be looking at the REST API. How the REST API nomenclature is laid out can be accessed at https://docs.strapi.io/dev-docs/api/rest#endpoints.
By default, API needs to have an authentication of some sort. An API token can be created by going to Settings and then API Tokens. When creating API tokens, you are given the ability to control exactly what permissions and content is accessible by the token.
On each endpoint, the token should be sent on the Authorization
header as a Bearer token (ex: Bearer <api-token>
)
Authentication and Users
The issue with the API token method of authorisation is that we cannot differentiate between individual users. This situation arises in scenarios where the CMS is only part of the application.
Let’s say that you have a separate NestJS backend where there are certain user tiers. Tier A users can only access Silver Tier articles while Tier B users can access both Silver and Gold Tier articles.
Then we have the React app which consumes the content from our Strapi application but authenticates with the NestJS backend. How do we do permission management on Strapi end of things?
One option (the option that we will be looking at in this article) is to have a user on the Strapi server for every user on our NestJS backend. When a user authenticates with the NestJS backend, we can get a JWT token for the Strapi user as well.
This means that on the React app, there will be two JWTs:
- A JWT to send with requests to our NestJS backend
- Another JWT to send with requests to our Strapi CMS
The entire login flow and requesting flow can be summarised as the following diagram:
Custom authentication endpoint
To create a custom Strapi endpoint, use yarn strapi generate
with the API named server-auth. A new folder will be generated and in this folder, change routes/server-auth.ts and change the route path to /auth/server.
Change the newly created router, controller, and service as follows
/* routes/server-auth.ts */
export default {
routes: [
{
method: 'POST',
path: '/auth/server',
handler: 'server-auth.auth',
config: {
policies: [],
middlewares: [],
},
},
],
};
/* controllers/server-auth.ts */
export default {
auth: async (ctx, next) => {
try {
// INFO: parsing
const email: string = ctx.request.body.email;
// INFO: validation
if (!email) {
ctx.throw(400, 'Email is required');
}
await strapi.service('api::server-auth.server-auth').auth(ctx, email);
} catch (err) {
ctx.throw(400, 'Could not authenticate', err);
}
},
};
/* services/server-auth.ts */
export default {
auth: async (ctx, email: string): Promise<void> => {
const userList = await strapi.entityService.findMany('plugin::users-permissions.user', {
filters: {
email,
},
});
if (userList.length === 0) {
ctx.throw(400, 'User not found');
return;
}
const user = userList[0];
const token = await strapi.plugins['users-permissions'].services.jwt.issue({ id: user.id });
ctx.body = {
user: {
id: user.id,
email: user.email,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
blocked: user.blocked,
},
access_token: token,
};
},
};
By default, this API route is created as a private route. This means that the API token which we use to call this endpoint (possibly from our NestJS server) needs to have permission to access this endpoint. To grant this permission, create (or modify an existing token) to have the permission to access this endpoint (which would have appeared now):
Make sure you do not expose this API token! Because it has the power to authenticate any user by just providing the email address! However, in our use case, we will only be using this particular endpoint from the server.
P.S. I wrote this article while setting it up for a project so there might be gaps in the instructions. Do reach out to me with questions or feedback and I will make sure to answer them!
Special thanks to Thashwini Wattuhewa for a great intro session into Strapi :)