Content Management Systems (CMS) are used to develop applications while creating their content. Traditionally, CMS build applications as one. This means the backend and the frontend of the application are managed by one base system.
The system can store and manage the content as a backend. The same system will still handle the presentation layer as the frontend. The two sets of systems are closely connected to run as a monolith application.
This is a great set of powering applications. However, the approach creates some challenges; some of them are highlighted below.
Above are a few challenges that the traditional CMS fails to solve. However, with the advances in technology, a headless CMS was born to address these challenges.
A headless CMS allows you to run a decoupled system. This means the content management (backend) and the presentation layer (frontend) run independently.
A headless CMS only deals with content management; it is not responsible for how the content will be represented to the user. This approach gives you a tone of advantages such as:
Check the Guide to Headless CMS and learn what is a headless CMS, its benefits, and usage.
One of the most popular headless CMS that fits your content management demands is Strapi. Strapi is an open-source headless CMS based on Node.js. It helps you build and manage your backend content while producing API endpoints to consume your data.
Once you have created your content with Strapi, you need the right framework to handle the content presentation layer. Remix is a good choice to channel your content to the users. In this guide, we will use Remix to consume Strapi generated content.
To continue in this article, it is essential to have the following:
With these, you're ready to continue.
To set up Strapi, create a remix-strapi-app
project directory and open it using the VS Code editor. Using your editor, open a terminal and run the following one-time command to create Strapi CMS backend project.
npx create-strapi-app strapi-notes-app --quickstart
This command will create a local Strapi project inside the strapi-notes-app
directory.
Strapi will then build an admin UI with development configurations and automatically start the administration panel on http://localhost:1337/admin
.
To start using Strapi, create an admin account using your details, then click let's start
. This will direct you to the Strapi administrator Dashboard.
We are building a Notes app using Strapi; therefore, we need to model the data for a Notes app. To build the Notes app content structure, first head over to the Content-Type Builder section and Create new collection type.
Start modeling the Notes content as follows:
Once done, you should have the following fields:
The next step is to create notes content. Here, we'll add some Notes entries to the Strapi CMS. Head over to the Content Manager section:
Manage the Notes as follows:
Now we need to access this backend and use it with Remix. We need to create an API token for Strapi CMS as follows:
Your API token will be generated successfully.
Copy this token and save it in a safe location. We will use to consume the API using Remix. The next step is to set up the Remix Notes application.
To set up Strapi, navigate to the remix-strapi-app
project directory and open it using your text editor. Open a terminal and run the following one-time command to create a Remix frontend project.
npx create-remix@latest
When creating the Remix app, you will be asked basic questions to set up the Remix App. Answer these onboarding command prompts as shown in the image below:
A remix-notes-app
will be created containing the bootstrapped Remix app. To test this app, open a terminal using your text editor and change the directory to the created remix folder:
cd remix-notes-app
Then start the Remix app development server:
npm run dev
You can now access the server on your browser at http://localhost:3000
.
To start using Strapi with Remix, create a .env
file at the root of the remix-notes-app
folder and add in the following:
http://localhost:1337
.Add these environment variables to the .env
file as follows, ensuring you replace the variables with the correct parameters:
1 STRAPI_URL_BASE=http://your_ip_address:port_where_strapi_is_running
2 STRAPI_API_TOKEN=your_strapi_access_token
To consume these variables, head over to the app folder and create a utils
directory. The directory will host errorHandling.js
for handling errors while communicating to Strapi as follows:
1 // Custom error class for errors from Strapi API
2 class APIResponseError extends Error {
3 constructor(response) {
4 super(`API Error Response: ${response.status} ${response.statusText}`);
5 }
6 }
7
8 // Checking the status
9 export const checkStatus = (response) => {
10 if (response.ok) {
11 // response.status >= 200 && response.status < 300
12 return response;
13 } else {
14 throw new APIResponseError(response);
15 }
16 }
17
18 class MissingEnvironmentVariable extends Error {
19 constructor(name) {
20 super(`Missing Environment Variable: The ${name} environment variable must be defined`);
21 }
22 }
23
24 export const checkEnvVars = () => {
25 const envVars = [
26 'STRAPI_URL_BASE',
27 'STRAPI_API_TOKEN'
28 ];
29
30 for (const envVar of envVars) {
31 if (!process.env[envVar]) {
32 throw new MissingEnvironmentVariable(envVar)
33 }
34 }
35 }
Let's now work on the frontend application. We will make all the changes inside the app/routes/index.jsx
file.
To display the Notes from the CMS, head over to the app/routes/index.jsx
file:
1 import { useLoaderData} from "@remix-run/react";
2 import { checkStatus, checkEnvVars } from "~/utils/errorHandling";
1 export async function loader () {
2 checkEnvVars();
3
4 const res = await fetch(`${process.env.STRAPI_URL_BASE}/api/notes`, {
5 method: "GET",
6 headers: {
7 "Authorization": `Bearer ${process.env.STRAPI_API_TOKEN}`,
8 "Content-Type": "application/json"
9 }
10 });
11
12 // Handle HTTP response code < 200 or >= 300
13 checkStatus(res);
14
15 const data = await res.json();
16
17 // Did Strapi return an error object in its response?
18 if (data.error) {
19 console.log('Error', data.error)
20 throw new Response("Error getting data from Strapi", { status: 500 })
21 }
22
23 return data.data;
24 }
Index
function as follows:1 export default function Index() {
2 const notes = useLoaderData();
3 return (
4 <div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
5 <h1>Notes App</h1>
6 {
7 notes.length > 0 ? (
8 notes.map((note,index) => (
9 <div key={index}>
10 <h3>{note.attributes.title}</h3>
11 <p>{note.attributes.description}</p>
12 <p>{new Date(note.attributes.createdAt).toLocaleDateString()}</p>
13 <button onClick={null}>
14 delete note
15 </button>
16 </div>
17 ))
18 ) : (
19 <div>
20 <h3>Sorry!!, you do not have notes yet!!</h3>
21 </div>
22 )
23 }
24 </div>
25 );
26 }
From above, we are checking whether we have notes. If yes, we are looping through them, displaying each and every one of them, else we are displaying that no notes exist.
Based on the notes added, your page should be similar to the image below.
Now, let's work on adding a note. To do this, follow the instructions below.
1 import { Form,useActionData } from "@remix-run/react";
1 const addNote = async (formData) => {
2 checkEnvVars();
3
4 const response = await fetch(`${ process.env.STRAPI_URL_BASE } /api/notes`, {
5 method: "POST",
6 body: JSON.stringify({
7 "data":{
8 "title" : formData.title,
9 "description" : formData.description
10 }
11 }),
12 headers: {
13 "Authorization": `Bearer ${ process.env.STRAPI_API_TOKEN } `,
14 "Content-Type": "application/json"
15 }
16 });
17
18 // Handle HTTP response code < 200 or >= 300
19 checkStatus(response);
20
21 const data = await response.json();
22
23 // Did Strapi return an error object in its response?
24 if (data.error) {
25 console.log('Error', data.error)
26 throw new Response("Error getting data from Strapi", { status: 500 })
27 }
28
29 return data.data;
30 }
Index
function, initialize the action data hook and log the response:1 const actionData = useActionData();
2 console.log("actionData",actionData);
1 <Form
2 method="post">
3 <div>
4 <input type="text" name="title" placeholder="title of note" />
5 </div>
6 <div>
7 <input type="text" name="description" placeholder="Description of note" />
8 </div>
9 <div>
10 <button type="submit">
11 add note
12 </button>
13 </div>
14 </Form>
Test the functionality from your browser. When you add a note, it will reflect on the notes section below:
To delete a Note, define a function to handle the delete functionality:
1 const deleteNote = async (noteId) => {
2 const response = await fetch(`http://localhost:1337/api/notes/${noteId}`, {
3 method: "DELETE",
4 headers: {
5 "Authorization": `Bearer your_api_token`,
6 "Content-Type": "application/json"
7 }
8 });
9
10 // Handle HTTP response code < 200 or >= 300
11 checkStatus(response);
12
13 const data = await response.json();
14
15 // Did Strapi return an error object in its response?
16 if (data.error) {
17 console.log('Error', data.error)
18 throw new Response("Error getting data from Strapi", { status: 500 })
19 }
20
21 window.location.reload(); // refresh the page
22 }
23- Append the functionality to the `deleteNote` button:
24 <button onClick={() => deleteNote(note.id)}>
25 delete note
26 </button>
Now, once you click the button, the note will be deleted.
Headless CMSes are scalable solutions that can be incorporated into the architecture of practically any application. You can maintain and store content to be consumed by various devices, platforms, and applications. This design may be used to handle items in e-commerce apps, store material for blog apps, or create any CRUD-related application.
In this guide, we have learned to use Strapi as headless CMS to build a minimal Remix CRUD application. I hope you enjoyed this tutorial. Happy coding!
You can find the source code for the project on this GitHub repository.
Joseph is fluent in Android Mobile Application Development and has a lot of passion for Fullstack web development. He loves to write code and document them in blogs to share ideas.