Simply copy and paste the following command line in your terminal to create your first Strapi project.
npx create-strapi-app
my-project
This article is a guest post by Chidume Nnamdi. He wrote this blog post through the Write for the Community program. If you are passionate about everything Jamstack, open-source, or JavasSript and want to share, join the writer's guild!
In this article, we will learn how to use two excellent techs, Next.js and Strapi to build a simple to-do app.
Strapi is an open-source Node.js headless CMS that gives developers the power to easily create self-hosted, customizable, and performant content REST/GraphQL APIs. It is easy to use. First, you create the content structures i.e the tables and models. Then, you can write, edit or manage any content type. Next.js is the frontend part of the to-do app and Strapi will provide the API endpoints to the to-do app.
Strapi ensures that we follow the best practices when creating and consuming web resources over HTTP.
For example, we have a book resource: /books
.
The HTTP verbs denote the action to be performed on the book’s resources. The books become a collection and a new book can be added, a book can be edited and a book can also be deleted.
1
/books
post /books
A new book put books/:id
Edit a book get /books/:id
get a book delete /books/:id
delete a book.
Books become the collection and any of the CRUD actions can be performed on a book as we have seen above. We will create a Strapi API and a Next.js project. The Strapi will serve our to-dos backend while the Next.js will fetch the to-dos from the Strapi backend and render them.
Requirements This project needs some software and tools to be installed before you can proceed.
1
npm i yarn -g
VS Code: The most popular code editor from Microsoft. Download from VS Code
Setting up our Strapi backend Let’s begin by creating a central folder that will hold the Strapi backend and Next.js frontend.
1
➜ mkdir strapi-todo-blog
This creates a strapi-todo-blog
folder. Now, move into the folder:
1
➜ cd strapi-todo-blog
Now, we scaffold our Strapi project:
1
➜ strapi-todo-blog yarn create strapi-app todo-api --quickstart
Here Strapi creates a Strapi project in todo-api
folder, then it installs the dependencies.
Here Strapi builds the admin UI with development configuration.
Next, Strapi runs the Strapi project by running the command: strapi develop
The Strapi server is started at localhost:1337. We can go to localhost:1337/admin to setup our admin privileges.
Open your favorite browser and navigate to localhost:1337/admin
Fill in your details:
Then, click on the “LET’S START” button. Clicking on this button will create your account and the strapi UI for admin will show:
Creating collections
Now, we will create the web resources. Go to Content-Types Builder
and click on the Create new collection
type. A modal will appear, there is the Display name
input box, enter todo
.
Then, click on Continue
.
A modal will appear where we select the field for our collection type.
Click on Text
.
Enter the name of the field selected:
Type in todoText
, then click on "Finish".
Click on “Save”.
Now, on the sidebar we will see “COLLECTION TYPES” under it we will see “Todo”.
Click on the “Todo”
Let’s add new todo. To do that click on the “+ Add New Todos” button.
Now, on the next page,
Enter “go to church” in the “TodoText” input box. Next click on “Save”. The “Publish” button will become enabled.
Click on it to publish the todoText.
Now, the todos will be like this:
The id
, published_at
, created_at
fields are added by Strapi. id
is the primary key and auto-incremented. published_at
is a date timestamp that records the time when the todo is edited and published. created_at
is the timestamp that records the todo was created.
Right now, to access our todos is restricted to only authorized users with certain roles. But let’s make it accessible to the public. To do that go to Settings
- Roles
.
We see two roles here: “Authenticated” and “Public”. Click on “Public”, on the page that appears, scroll down to “Permissions” and click on the “Select all” checkbox to select all checkboxes.
With this, unauthorized users can perform CRUD actions on the todos
collections.
Now, we can test the todos
endpoint using Postman.
Test with Postman
Open Postman, and create a new request.
Let’s fetch all todos, to do that, enter localhost:1337/todos
in the URL input box, and click on "Send".
See, all todos are returned.
Let’s fetch a single todo. Let’s fetch the todo with id 2.
Enter localhost:1337/todos/2
in the URL input box and click on "Send"
The todo with id 2 is returned.
Building the Next.js app We have built and tested our API endpoints, now is the time to create the frontend using Next.js.
Run the below command to create a Next.js project. Make sure you move outside of the current Strapi project.
1
yarn create next-app todo-app
The above command creates a Next.js project todo-app
.
Open the todo-app
project in VS Code.
Our todo app will look like this:
Now, we will divide it into components:
The Header
and TodoItem
will be a presentational component, while AddTodo
and TodoList
will be containers.
Now, let’s create the folders components
and containers
in the todo-app
project.
In the components
folder, add the files header.js
and todoItem.js
.
In the containers
folder, add the files addTodo.js
and todoList.js
.
Let’s flesh out the files:
In Header.js
, paste the below code:
1
2
3
4
5
6
7
8
9
//header.js will hold the header section.
function Header() {
return (
<div className="header">
<h2>ToDo app</h2>
</div>
);
}
export default Header;
In todoItem.js
, paste the below code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// todoItem.js will hold the single todo display.
function TodoItem({ todo, editTodoItem, deleteTodoItem }) {
return (
<>
<div className="todoItem">
<div className="todoItemText">{todo.todoText}</div>
<div className="todoItemControls">
<i className="todoItemControlEdit">
<button className="bg-default" onClick={() => editTodoItem(todo)}>
Edit
</button>
</i>
<i className="todoItemControlDelete">
<button className="bg-danger" onClick={() => deleteTodoItem(todo)}>
Del
</button>
</i>
</div>
</div>
</>
);
}
export default TodoItem;
The TodoItem component has props that expect the todo
to display, then editTodoItem
, and deleteTodoItem
are functions that will be called when a todo is to be edited or deleted.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// addTodo.js is where new todos are added to our backend.
function AddTodo({ addTodo }) {
return (
<>
<div className="addTodoContainer">
<input
className="todoInputText"
type="text"
placeholder="Add new todo here..."
id="todoText"
onKeyDown={(e) => {
if (e.code === "Enter") {
addTodo(todoText.value);
todoText.value = "";
}
}}
/>
<input
className="todoInputButton"
type="button"
value="Add Todo"
onClick={() => {
addTodo(todoText.value);
todoText.value = "";
}}
/>
</div>
</>
);
}
export default AddTodo;
There are an input box and button, whenever the button is clicked, the addTodo
function prop is called with the value of the input box. We added an onKeyDown
event on the input box so we can capture the "Enter" key when it is pressed, and call the addTodo
function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// todoList.js renders the todo list gotten from the Strapi backend.
import TodoItem from "../components/todoItem";
function TodoList({ todos, editTodoItem, deleteTodoItem }) {
return (
<div className="todoListContainer">
<div className="todosText">Todos</div>
{todos
.sort((a, b) => b.created_at.localeCompare(a.created_at))
.map((todo, i) => (
<TodoItem
todo={todo}
key={i}
deleteTodoItem={deleteTodoItem}
editTodoItem={editTodoItem}
/>
))}
</div>
);
}
export default TodoList;
This component gets the todo list in an array. It sorts the array by the time it was created and then renders them. It receives editTodoItem
, deleteTodoItem
in its props and passes it to the TodoItem
component.
We are done fleshing out our components. Now, we have to render the containers.
We are going to need the axios
library, so we install it with
1
yarn add axios
Got to the index.js
file in the pages
folder and paste the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import Head from "next/head";
import { useEffect, useState } from "react";
import Header from "../components/header";
import AddTodo from "../containers/addTodo";
import TodoList from "../containers/todoList";
import axios from "axios";
export default function Home() {
const [todos, setTodos] = useState([]);
useEffect(async () => {
const result = await axios.get("http://localhost:1337/todos");
setTodos(result?.data);
}, []);
const addTodo = async (todoText) => {
if (todoText && todoText.length > 0) {
const result = await axios.post("http://localhost:1337/todos", {
todoText: todoText,
});
setTodos([...todos, result?.data]);
}
};
const deleteTodoItem = async (todo) => {
if (confirm("Do you really want to delete this item?")) {
await axios.delete("http://localhost:1337/todos/" + todo.id);
const newTodos = todos.filter((_todo) => _todo.id !== todo.id);
console.log(newTodos);
setTodos(newTodos);
}
};
const editTodoItem = async (todo) => {
const newTodoText = prompt("Enter new todo text or description:");
if (newTodoText != null) {
const result = await axios.put("http://localhost:1337/todos/" + todo.id, {
todoText: newTodoText,
});
const moddedTodos = todos.map((_todo) => {
if (_todo.id === todo.id) {
return result?.data;
} else {
return _todo;
}
});
setTodos(moddedTodos);
}
};
return (
<div>
<Head>
<title>ToDo app</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Header />
<main className="main">
<AddTodo addTodo={addTodo} />
<TodoList
todos={todos}
deleteTodoItem={deleteTodoItem}
editTodoItem={editTodoItem}
/>
</main>
</div>
);
}
This renders Header
component and the container components, AddTodo
and TodoList
.
We have a todos state, initially set to an empty array. This state holds the current todos array.
Next, we have a useEffect
that we use to fetch the todos from the Strapi endpoint when the component mounts. See that we use the endpoint localhost:1337/todos
to get all todos.
Here also, we defined the functions addTodo
, deleteTodoItem
, editTodoItem
. These functions are passed to the container components where they are called.
The addTodo
function adds a new todo in the endpoint. The function accepts the todo text in the todoText
arg, then uses the HTTP POSt to create the new todo via http://localhost:1337/todos
passing the todo text as payload. Then, the result of the action is gotten, and the newly added todo is added to the todos state thus making our app display the new todo in the UI.
The deleteTodoItem
function accepts the todo object to be deleted in the todo
arg. It confirms first if the user wants to delete the todo, if yes, it proceeds to call the endpoint http://localhost:1337/todos/:todoId
with the todo id and the HTTP DELETE verb. This will cause Strapi to delete the todo with the id in its database. Next, we filter out the deleted todo and set the filtered result as the new todos in the todos state.
The editTodoItem
functions edits a todo. It prompts the user to enter the new todo text. Upon clicking OK in the prompt dialog, it edits the todo via the endpoint via http://localhost:1337/todos/
with the todo id, the HTTP PUT verb is used here, with the property in the todo model to edit is sent in the body. This causes Strapi to seek out the todo and edit it accordingly.
Next, we use Array#map
method to get the todo in the local todos state and change it to was it is currently is in the Strapi backend, then, we set the modified todos in the todos state.
Let’s add CSS to our project. Open styles
folder and paste the following in the globals``.``css
file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-size: xx-large;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
.main {
padding: 10px 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.header {
display: flex;
justify-content: center;
color: rgba(70, 130, 236, 1);
}
.todoInputText {
padding: 10px 7px;
border-radius: 3px;
margin-right: 2px;
margin-left: 2px;
width: 100%;
font-size: large;
}
button {
padding: 10px 10px;
border-radius: 3px;
cursor: pointer;
margin-right: 2px;
margin-left: 2px;
font-size: large;
}
.bg-default {
background-color: rgba(70, 130, 236, 1);
border: 1px solid rgba(28, 28, 49, 1);
color: white;
}
.bg-danger {
background-color: red;
border: 1px solid rgba(28, 28, 49, 1);
color: white;
}
.todoInputButton {
padding: 10px 10px;
border-radius: 3px;
background-color: rgba(70, 130, 236, 1);
color: white;
border: 1px solid rgba(28, 28, 49, 1);
cursor: pointer;
margin-right: 2px;
margin-left: 2px;
font-size: large;
}
.addTodoContainer {
margin-top: 4px;
margin-bottom: 17px;
width: 500px;
display: flex;
justify-content: space-evenly;
}
.todoListContainer {
margin-top: 9px;
width: 500px;
}
.todoItem {
padding: 10px 4px;
color: rgba(70, 130, 236, 1);
border-radius: 3px;
border: 1px solid rgba(28, 28, 49, 1);
margin-top: 9px;
margin-bottom: 2px;
display: flex;
justify-content: space-between;
}
.todosText {
padding-bottom: 2px;
border-bottom: 1px solid;
}
Run the Next.js server Now, we run our Next.js server.
1
yarn dev
Navigate to localhost:3000
, there you will see our frontend being rendered.
See we have a “go to church” todo there. This is from the todo we added via the Strapi admin panel when we were setting up the todos collection.
Now let’s add a new todo. Type “go to market” and click on the “Add todo” button.
See a new todo shows up with “go to market”.
Let’s edit the todo text. Click on the “Edit” button next to the “go to market” todo.
A prompt dialog shows up, type in “go to shopping mall in Jabi” Click on OK.
See the todo text changes to “go to shopping mall in Jabi” Let’s delete the todo. Click on the “Del” button next to “go to shopping mall in Jabi” todo. A confirm dialog shows up.
Click on OK. The todo is deleted.
GitHub Source Code You can find the source code of the project here:
Conclusion You can see Strapi is very powerful and very easy to use. Setting up our backend was very simple and easy. Just create your collections and Strapi will provide you with endpoints following best web practices.
If you have any questions regarding this or any suggestions to add or modify, feel free to comment, email, or DM me. Thanks !!!
Author of "Understanding JavaScript", Chidume is also an awesome writer about JavaScript, Angular, React and other web technologies.
Get all the latest Strapi updates, news and events.