Creating a Simple CRUD Todo List with Express.js
Introduction
Recently learning backend development, Express.js is the closest framework to frontend, ideal for full-stack engineers to quickly engage with both fields.
This implementation does not involve any database-related parts, focusing on using Express.js to create a simple in-memory backend server to familiarize with CRUDd APIs.
Building a Minimal Working Express.js
JavaScript backend runs on Node.js, and pick your favorite code editor, create a new folder, initialize an NPM project, and install Express.js.
# npm initialization (-y uses default values without further configuration)$ npm init -y# Install Express$ npm install express
Next, just create a new app.js
, write some Express.js related code, and run it with Node.js (node app.js
). Below is the minimal working Express.js.
- req is the conventional abbreviation for request
- res is the conventional abbreviation for response
import express from 'express';
const app = express();const port = 3000;
app.get('/', (req, res) => { res.send('Hello World!');});
app.listen(port, () => { console.log(`Example app listening on port ${port}`);});
We expose Express on port 3000
, so just open a browser to the local address http://localhost:3000/
to see the Hello World example. Express has many methods to call; refer to the official documentation to learn more, such as: app.get(), res.send().
Building CRUD APIs
1. Planning
The API of Express.js is very intuitive. Corresponding to HTTP request methods, you can find methods with the same name in the official documentation. To build a todo list, we will need:
todos
data initialized when Express starts- APIs for external interaction with the data
- Logic for data CRUD operations
let todos = [ // { // id: 1, (unique identifier) // title: 'Init Todo', // isCompleted: false, // },];
// Get Todosapp.get();
// Add Todosapp.post();
// Modify Todosapp.put();
// Delete Todosapp.delete();
One thing to note is that Express does not parse res.body
as JSON by default. We can use Middleware to handle all res.body
using express.json.
app.use(express.json());
2. Implementation
The remaining task is to write the internal logic of the server and interact with the data. I prefer to calculate a new todos
independently and then reassign the content back, while paying attention to some exceptions, such as returning an error to the client when editing a non-existent ID
.
// Get All Todosapp.get("/api/todos", (req, res) => { res.json(todos)})
// Create Todoapp.post("/api/todos", (req, res) => { const { title } = req.body; if (!title || title.trim() === "") { return res.status(400).json({ error: "Title is required and cannot be empty." }); }
const newTodo = { id: Date.now().toString(), title, isCompleted: false }
todos = [...todos, newTodo] res.status(201).json(newTodo)})
// Edit {id} Todoapp.put("/api/todos/:id", (req, res) => { const targetId = req.params.id const targetIndex = todos.findIndex(todo => todo.id === targetId); const isTargetTodoExist = targetIndex !== -1
if (!isTargetTodoExist) { return res.status(404).json({ error: "Todo not found" }); }
const updatedTodo = { ...todos[targetIndex], title: req.body.title ?? todos[targetIndex].title, isCompleted: req.body.isCompleted ?? todos[targetIndex].isCompleted }
const newTodos = [ ...todos.slice(0, targetIndex), updatedTodo, ...todos.slice(targetIndex + 1), ];
todos = newTodos res.json(updatedTodo)})
// Delete {id} Todoapp.delete("/api/todos/:id", (req, res) => { const targetId = req.params.id const targetIndex = todos.findIndex(todo => todo.id === targetId); const isTargetTodoExist = targetIndex !== -1
if (!isTargetTodoExist) { return res.status(404).json({ error: "Todo not found" }); }
const newTodos = [ ...todos.slice(0, targetIndex), ...todos.slice(targetIndex + 1), ]
todos = newTodos res.status(204).send();})
3. Check
Popular API testing tools include: Postman, Hoppscotch, and Insomnia. You can choose one that you find convenient for testing. I prefer Hoppscotch and have previously written a related recommendation article: Learn Hoppscotch. You can create a group to save all the APIs related to this project for easier management.

Side note: CORS Settings
Access to fetch at `http://localhost:3000/api/todos' from origin 'http://127.0.0.1:5500' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
If you encounter the above CORS error message in your frontend project, you can refer to another article I wrote: CORS for Web Developer.
Literally, you can allow websites from different ports to use the API by setting the server response to include the allowed Access-Control-Allow-Origin
. Here, I rely on setting up Express.js middleware: cors to solve this.
$ npm install cors
After installation, add the middleware and set the open origin:
app.use(cors({ origin: 'http://127.0.0.1:5500' }));
Side note: Using Nodemon to Restart the Server in Real-Time
If you find it troublesome to repeatedly restart the server after making changes during development, you can try Nodemon.
Nodemon is used to automatically monitor file changes and restart the server while developing Node.js applications, avoiding the hassle of manually stopping and restarting the server. By adding it as an NPM Script, you can run npm run dev
to start the live-reloading development server.
"scripts": { "dev": "nodemon app.js" }
Summary
The project implemented this time: in-memory-todo files are available on GitHub, and I have additionally implemented the frontend part. If you are interested in the frontend, you can refer to another article of mine: Creating a Todo List in Five Steps with JavaScript, which is basically built on this foundation by connecting to the backend data and avoiding some XSS vulnerabilities.
Further Reading
- Hello world example - Express
- Create a Todo application with Express.js - postman
- RESTful APIs in 100 Seconds // Build an API from Scratch with Node.js Express - Fireship
- Learn CORS In 6 Minutes