In this tutorial, we'll learn how to create a small to-do app using two amazing technologies: Next.js and Strapi.
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.
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. By the end of this tutorial, you will be able to 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.
This project needs some software and tools to be installed before you can proceed:
npm I yarn -g
Let’s begin by creating a central folder that will hold the Strapi backend and Next.js frontend. To proceed, launch your terminal and enter the following command to create a project folder.
mkdir strapi-ToDo-blog
This creates a strapi-ToDo-blog folder. Now, move into the folder:
cd strapi-todo-blog
Now, we scaffold our Strapi project:
npx create-strapi-app strapi-ToDo-blog --quickstart
The above command will create a new Strapi project and install the necessary dependencies.
To start your Strapi project navigate to the folder when you installed the Strapi project and run the command below:
npm run develop
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 preferred 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:
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 To-Do.
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 ToDojText, then click on "Finish".
Click on “Save”. On the sidebar, you'll see “COLLECTION TYPES” under it and you will see “Todo”.
Now to add some to-do items to the TO-Do.
Click on the “To-Do”
Let’s add a new to-do. To do that click on the “+ Create New entry” button.
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 todo text.
Now, the to-dos will be like this:
The id, published_at
and 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.
We can see that all todos are returned.
localhost:1337/todos/{ID}
.localhost:1337/todos/2
.The todo with id 2 is returned.
We have built and tested our API endpoints. Now, it is time to create the frontend using Next.js.
yarn create next-app todo-app
The above command creates a Next.js project todo-app.
Now, we will divide it into components:
The Header and TodoItem will be a presentational component, while AddTodo and TodoList will be containers.
To create the folders components and containers in the todo-app project,
1 //header.js will hold the header section.
2 function Header() {
3 return (
4 <div className="header">
5 <h2>ToDo app</h2>
6 </div>
7 );
8 }
9 export default Header;
10
11 In todoItem.js, paste the below code:
12 // todoItem.js will hold the single todo display.
13
14 function TodoItem({ todo, editTodoItem, deleteTodoItem }) {
15 return (
16 <>
17 <div className="todoItem">
18 <div className="todoItemText">{todo.todoText}</div>
19 <div className="todoItemControls">
20 <i className="todoItemControlEdit">
21 <button className="bg-default" onClick={() => editTodoItem(todo)}>
22 Edit
23 </button>
24 </i>
25 <i className="todoItemControlDelete">
26 <button className="bg-danger" onClick={() => deleteTodoItem(todo)}>
27 Del
28 </button>
29 </i>
30 </div>
31 </div>
32 </>
33 );
34 }
35 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 // addTodo.js is where new todos are added to our backend.
2 function AddTodo({ addTodo }) {
3 return (
4 <>
5 <div className="addTodoContainer">
6 <input
7 className="todoInputText"
8 type="text"
9 placeholder="Add new todo here..."
10 id="todoText"
11 onKeyDown={(e) => {
12 if (e.code === "Enter") {
13 addTodo(todoText.value);
14 todoText.value = "";
15 }
16 }}
17 />
18 <input
19 className="todoInputButton"
20 type="button"
21 value="Add Todo"
22 onClick={() => {
23 addTodo(todoText.value);
24 todoText.value = "";
25 }}
26 />
27 </div>
28 </>
29 );
30 }
31 export default AddTodo;
There is an input box and a 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 // todoList.js renders the todo list gotten from the Strapi backend.
2 import TodoItem from "../components/todoItem";
3 function TodoList({ todos, editTodoItem, deleteTodoItem }) {
4 return (
5 <div className="todoListContainer">
6 <div className="todosText">Todos</div>
7 {todos
8 .sort((a, b) => b.created_at.localeCompare(a.created_at))
9 .map((todo, i) => (
10 <TodoItem
11 todo={todo}
12 key={i}
13 deleteTodoItem={deleteTodoItem}
14 editTodoItem={editTodoItem}
15 />
16 ))}
17 </div>
18 );
19 }
20 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
yarn add axios
1 import Head from "next/head";
2 import { useEffect, useState } from "react";
3 import Header from "../components/header";
4 import AddTodo from "../containers/addTodo";
5 import TodoList from "../containers/todoList";
6 import axios from "axios";
7 export default function Home() {
8 const [todos, setTodos] = useState([]);
9 useEffect(async () => {
10 const result = await axios.get("http://localhost:1337/todos");
11 setTodos(result?.data);
12 }, []);
13 const addTodo = async (todoText) => {
14 if (todoText && todoText.length > 0) {
15 const result = await axios.post("http://localhost:1337/todos", {
16 todoText: todoText,
17 });
18 setTodos([...todos, result?.data]);
19 }
20 };
21 const deleteTodoItem = async (todo) => {
22 if (confirm("Do you really want to delete this item?")) {
23 await axios.delete("http://localhost:1337/todos/" + todo.id);
24 const newTodos = todos.filter((_todo) => _todo.id !== todo.id);
25 console.log(newTodos);
26 setTodos(newTodos);
27 }
28 };
29 const editTodoItem = async (todo) => {
30 const newTodoText = prompt("Enter new todo text or description:");
31 if (newTodoText != null) {
32 const result = await axios.put("http://localhost:1337/todos/" + todo.id, {
33 todoText: newTodoText,
34 });
35 const moddedTodos = todos.map((_todo) => {
36 if (_todo.id === todo.id) {
37 return result?.data;
38 } else {
39 return _todo;
40 }
41 });
42 setTodos(moddedTodos);
43 }
44 };
45 return (
46 <div>
47 <Head>
48 <title>ToDo app</title>
49 <link rel="icon" href="/favicon.ico" />
50 </Head>
51 <Header />
52 <main className="main">
53 <AddTodo addTodo={addTodo} />
54 <TodoList
55 todos={todos}
56 deleteTodoItem={deleteTodoItem}
57 editTodoItem={editTodoItem}
58 />
59 </main>
60 </div>
61 );
62 }
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 html,
2 body {
3 padding: 0;
4 margin: 0;
5 font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
6 Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 font-size: xx-large;
8 }
9 a {
10 color: inherit;
11 text-decoration: none;
12 }
13 * {
14 box-sizing: border-box;
15 }
16 .main {
17 padding: 10px 0;
18 flex: 1;
19 display: flex;
20 flex-direction: column;
21 justify-content: center;
22 align-items: center;
23 }
24 .header {
25 display: flex;
26 justify-content: center;
27 color: rgba(70, 130, 236, 1);
28 }
29 .todoInputText {
30 padding: 10px 7px;
31 border-radius: 3px;
32 margin-right: 2px;
33 margin-left: 2px;
34 width: 100%;
35 font-size: large;
36 }
37 button {
38 padding: 10px 10px;
39 border-radius: 3px;
40 cursor: pointer;
41 margin-right: 2px;
42 margin-left: 2px;
43 font-size: large;
44 }
45 .bg-default {
46 background-color: rgba(70, 130, 236, 1);
47 border: 1px solid rgba(28, 28, 49, 1);
48 color: white;
49 }
50 .bg-danger {
51 background-color: red;
52 border: 1px solid rgba(28, 28, 49, 1);
53 color: white;
54 }
55 .todoInputButton {
56 padding: 10px 10px;
57 border-radius: 3px;
58 background-color: rgba(70, 130, 236, 1);
59 color: white;
60 border: 1px solid rgba(28, 28, 49, 1);
61 cursor: pointer;
62 margin-right: 2px;
63 margin-left: 2px;
64 font-size: large;
65 }
66 .addTodoContainer {
67 margin-top: 4px;
68 margin-bottom: 17px;
69 width: 500px;
70 display: flex;
71 justify-content: space-evenly;
72 }
73 .todoListContainer {
74 margin-top: 9px;
75 width: 500px;
76 }
77 .todoItem {
78 padding: 10px 4px;
79 color: rgba(70, 130, 236, 1);
80 border-radius: 3px;
81 border: 1px solid rgba(28, 28, 49, 1);
82 margin-top: 9px;
83 margin-bottom: 2px;
84 display: flex;
85 justify-content: space-between;
86 }
87 .todosText {
88 padding-bottom: 2px;
89 border-bottom: 1px solid;
90 }
yarn dev
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.
See a new todo shows up with “go to market”.
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”
You can find the source code of the project here.
Strapi is both powerful and simple to use, as you can see. It was incredibly quick and straightforward to set up our backend. Simply construct your collections, and Strapi will provide endpoints that adhere to proper web principles.
Data Analyst, Data Visualization Expert.