In this guide, we will use Strapi and ReactJS to build a To-Do List application. This application is simple enough to give a basic understanding of how we create backend APIs using Strapi. We start our project by building the backend first, then we build the frontend, and we finally merge them as a single project.
This guide does not cover every single detail of the Strapi or the React framework. This article doesn’t cover the following:
You need to have knowledge of the following to fully understand this guide:
You don’t need an advanced knowledge of Strapi to follow this guide. You must have a NodeJS version greater than 12 installed, along with either yarn or npm package manager.
Strapi is an open-source headless CMS (short for Content Management Systems) that allows you to quickly create and maintain RESTful JavaScript APIs. Strapi helps in creating both simple and complex backends, either as an individual or an organization. Strapi is built on NodeJS, which provides high performance in processing large amounts of requests simultaneously.
We start our project by creating a Strapi application. This application provides an admin page that allows us to handle the operations within our backend. We create a Strapi app using any of the following commands depending on your package manager:
npx create-strapi-app todo-list --quickstart
yarn install global create-strapi-app
yarn create-strapi-app todo-list --quickstart
yarn dlx create-strapi-app todo-list --quickstart
If the command creates our Strapi app successfully, we run our application in development mode. We use the development mode to create data collections and API endpoint to those collections. The development mode creates a local server, which allows us to to the following:
To run our application development mode, we navigate to the todo-list folder, and run any of the following commands:
npm run develop
yarn run develop
If we open the http://localhost:1337/admin site, our application should look like the below.
This page allows us to create an admin account. Having an admin account signifies that you own the application. Only the owner of the application has exclusive access to the backend operations, which ensures the security of your application.
When we create an admin account, the application takes us to the dashboard. This dashboard displays all the possible operations that we can perform on the left panel.
In this section, we'll build the backend for our to-do list. The backend gives us a basic idea of how data will flow between the ends of our application. We build it using the following steps:
A collection is a group of data that have similar skeletal structure. Strapi creates separate API endpoints for each collection. We go through the following steps to create our "Todo" collection:
After the collection is created, we add test entries. We create test entries to see if the collection handles data as expected. We add entries to our collection through the following steps:
Navigate to “Content Manager”. We use this page to manipulate the entries of our collection.
Click on “Create new entry” in the “Todo” collection type.
Write any text into the “item” box.
We create API endpoints for our frontend using the Todo collection. These endpoints allows a frontend to interact with our collection. We go through the following steps to create the endpoints:
After performing the steps above, the following endpoints will be created for each of the permission created:
1 {
2 "data": [
3 {
4 "id": 1,
5 "attributes": {
6 "item": "item",
7 "createdAt": "2022-04-19T10:33:44.577Z",
8 "updatedAt": "2022-04-19T10:33:45.723Z",
9 "publishedAt": "2022-04-19T10:33:45.718Z"
10 }
11 },
12 {
13 "id": 2,
14 "attributes": {
15 "item": "item 2",
16 "createdAt": "2022-04-19T10:33:56.381Z",
17 "updatedAt": "2022-04-19T10:33:58.147Z",
18 "publishedAt": "2022-04-19T10:33:58.144Z"
19 }
20 }
21 ],
22 "meta": {
23 "pagination": {
24 "page": 1,
25 "pageSize": 25,
26 "pageCount": 1,
27 "total": 2
28 }
29 }
30 }
1 {
2 "data": {
3 "item": "item 3"
4 }
5 }
1 {
2 "data": {
3 "id": 3,
4 "attributes": {
5 "item": "item 3",
6 "createdAt": "2022-04-19T13:17:36.082Z",
7 "updatedAt": "2022-04-19T13:17:36.082Z",
8 "publishedAt": "2022-04-19T13:17:36.079Z"
9 }
10 },
11 "meta": {}
12 }
1 {
2 "data": {
3 "id": 2,
4 "attributes": {
5 "item": "item 2",
6 "createdAt": "2022-04-19T13:15:10.869Z",
7 "updatedAt": "2022-04-19T13:15:11.839Z",
8 "publishedAt": "2022-04-19T13:15:11.836Z"
9 }
10 },
11 "meta": {}
12 }
1 {
2 "data": {
3 "item": "2nd item"
4 }
5 }
1 {
2 "data": {
3 "id": 2,
4 "attributes": {
5 "item": "2nd item",
6 "createdAt": "2022-04-19T13:17:36.082Z",
7 "updatedAt": "2022-04-19T13:51:06.266Z",
8 "publishedAt": "2022-04-19T13:14:59.823Z"
9 }
10 },
11 "meta": {}
12 }
1 {
2 "data": {
3 "id": 2,
4 "attributes": {
5 "item": "item 2",
6 "createdAt": "2022-04-19T13:17:36.082Z",
7 "updatedAt": "2022-04-19T13:15:11.839Z",
8 "publishedAt": "2022-04-19T13:15:11.836Z"
9 }
10 },
11 "meta": {}
12 }
ReactJS is a JavaScript framework for building web applications. This framework is popular and beginner-friendly, which is why we will be using it in this guide. We create a React application with any of the following commands:
yarn install global create-react-app
yarn create-react-app todo-frontend
yarn dlx create-react-app todo-frontend
npx create-react-app todo-frontend
After we create the react app, we create two files for the environment variables, and write the following into it:
1REACT_APP_BACKEND=http://localhost:1337/
1REACT_APP_BACKEND=/
.env.development contains the environment variables for development, and .env.production contains the environment variables for development.
Now that we have our React project setup, we copy the following into the App.js file:
1 import { useState, useEffect } from 'react';
2 import TodoItem from './TodoItem';
3 import './App.css';
4
5 function App() {
6 const [todos, setTodos] = useState([]);
7 const [newTodo, setNewTodo] = useState("");
8
9 useEffect(() => {
10 // update update the list of todos
11 // when the component is rendered for the first time
12 update();
13 }, []);
14
15 // This function updates the component with the
16 // current todo data stored in the server
17 function update() {
18 fetch(`${process.env.REACT_APP_BACKEND}api/todos`)
19 .then(res => res.json())
20 .then(todo => {
21 setTodos(todo.data);
22 })
23 }
24
25 // This function sends a new todo to the server
26 // and then call the update method to update the
27 // component
28 function addTodo(e) {
29 e.preventDefault();
30 let item = newTodo;
31 let body = {
32 data: {
33 item
34 }
35 };
36
37 fetch(`${process.env.REACT_APP_BACKEND}api/todos`, {
38 method: "POST",
39 headers: {
40 'Content-type': 'application/json'
41 },
42 body: JSON.stringify(body)
43 })
44 .then(() => {
45 setNewTodo("");
46 update();
47 })
48 }
49
50 return (
51 <div className="app">
52 <main>
53 {/* we centered the "main" tag in our style sheet*/}
54
55 {/* This form collects the item we want to add to our todo, and sends it to the server */}
56 <form className="form" onSubmit={addTodo}>
57 <input type="text" className="todo_input" placeholder="Enter new todo" value={newTodo} onChange={e => setNewTodo(e.currentTarget.value) }/>
58 <button type="submit" className="todo_button">Add todo</button>
59 </form>
60
61 {/* This is a list view of all the todos in the "todo" state variable */}
62 <div>
63 {
64 todos.map((todo, i) => {
65 return <TodoItem todo={todo} key={i} update={update} />
66 })
67 }
68 </div>
69
70 </main>
71 </div>
72 )
73 }
74 export default App;
After copying the above into our App.js file, we create the TodoItem.jsx component file in the same directory. This component renders each item of our to-do list. We copy the following into our TodoItem.jsx file:
1 import { useState } from "react";
2 import './App.css';
3
4 function TodoItem({ todo, update }) {
5
6 // Our component uses the "edit" state
7 // variable to switch between editing
8 // and viewing the todo item
9 const [edit, setEdit] = useState(false);
10 const [newTodo, setNewTodo] = useState("");
11
12 // This function changes the to-do that
13 // is rendered in this component.
14 // This function is called when the
15 // form to change a todo is submitted
16 function changeTodo(e) {
17 e.preventDefault();
18 let item = newTodo;
19 let pos = todo.id;
20 let body = {
21 data: {
22 item
23 }
24 };
25
26 fetch(`${process.env.REACT_APP_BACKEND}api/todos/${pos}`, {
27 method: "PUT",
28 headers: {
29 'Content-type': 'application/json'
30 },
31 body: JSON.stringify(body)
32 })
33 .then(() => {
34 setEdit(false);
35 update();
36 })
37 }
38
39 // This function deletes the to-do that
40 // is rendered in this component.
41 // This function is called when the
42 // form to delete a todo is submitted
43 function deleteTodo(e) {
44 e.preventDefault();
45 let pos = todo.id;
46
47 fetch(`${process.env.REACT_APP_BACKEND}api/todos/${pos}`, {
48 method: "DELETE"
49 })
50 .then(() => {
51 update();
52 })
53 }
54
55 return <div className="todo">
56 {/*
57 The below toggles between two components
58 depending on the current value of the "edit"
59 state variable
60 */}
61 { !edit
62 ? <div className="name">{todo.attributes.item}</div>
63 : <form onSubmit={changeTodo}>
64 <input className="todo_input" type="text" placeholder="Enter new todo" value={newTodo} onChange={e => setNewTodo(e.currentTarget.value)} />
65 <button className="todo_button" type="submit">Change todo</button>
66 </form>
67 }
68 <div>
69 <button className="delete" onClick={deleteTodo}>delete</button>
70 <button className="edit" onClick={() => {
71 // this button toggles the "edit" state variable
72 setEdit(!edit)
73
74 // we add this snippet below to make sure that our "input"
75 // for editing is the same as the one for the component when
76 // it is toggled. This allows anyone using it to see the current
77 // value in the element, so they don't have to write it again
78 setNewTodo(todo.attributes.item)
79 }}>edit</button>
80 </div>
81 </div>
82 }
83
84 export default TodoItem;
After creating this component, we add CSS styling to our web page by copying the following into our App.css file.
1 .app {
2 display: flex;
3 justify-content: center;
4 text-align: center;
5 }
6
7 .todo_input {
8 height: 16px;
9 padding: 10px;
10 border-top-left-radius: 8px;
11 border-bottom-left-radius: 8px;
12 border: 2px solid blueviolet;
13 }
14
15 .todo_button {
16 border: 2px solid blueviolet;
17 background-color: transparent;
18 height: 40px;
19 border-top-right-radius: 8px;
20 border-bottom-right-radius: 8px;
21 }
22
23 .todo {
24 display: flex;
25 justify-content: space-between;
26 margin-top: 5px;
27 font-weight: 700;
28 margin-bottom: 5px;
29 min-width: 340px;
30 }
31
32 .edit {
33 width: 66px;
34 font-weight: 700;
35 background: blueviolet;
36 border: none;
37 border-top-right-radius: 5px;
38 height: 33px;
39 border-bottom-right-radius: 5px;
40 color: white;
41 font-size: medium;
42 }
43
44 .delete {
45 width: 66px;
46 font-weight: 700;
47 background: white;
48 border: 2px solid blueviolet;
49 border-top-left-radius: 5px;
50 height: 33px;
51 color: blueviolet;
52 border-bottom-left-radius: 5px;
53 font-size: medium;
54 }
55
56 .form {
57 padding-top: 27px;
58 padding-bottom: 27px;
59 }
60
61 .name {
62 max-width: 190.34px;
63 text-align: left;
64 }
When we run this application, our website will look like the image below.
We can merge our React frontend and our Strapi backend into one full-stack project. Merging the two ends allows us to deploy our project on a single server. We follow the below steps to merge them together.
The build command is either of the following:
yarn run build
npm run build
If we do the following, our application should come out right:
In this article, we covered the following:
The to-do list application is a very simple application, which involves manipulating entries and RESTful APIs with the collections. You can find the project for this article in the GitHub repo.
Chigozie is a technical writer. He started coding since he was young, and entered technical writing recently.