Next.js CRUD Frontend Integration(Using Express API)
Assume your backend is running at:
const BASE_URL = "http://localhost:5000/todos";
// or
// const BASE_URL = "https://todo-backend-rwmd.onrender.com/todos";This will be used to call your API.
1. Fetch All Todos (GET)
This loads all todos when the page opens.
"use client";
import { useEffect, useState } from "react";
export default function page() {
const [todos, setTodos] = useState([]);
// Fetch data from backend
const fetchTodos = async () => {
const res = await fetch(BASE_URL);
const data = await res.json();
setTodos(data);
};
// Run once on page load
useEffect(() => {
fetchTodos();
}, []);
return (
<div>
<h1>Todos</h1>
{/* Display todos */}
{todos.map((todo) => (
<p key={todo._id}>{todo.title}</p>
))}
</div>
);
}Explanation:
useStatestores todosuseEffectruns when component loadsfetch()calls your backend API.map()displays each todo
2. Create Todo (POST)
This adds a new todo to the database.
const addTodo = async () => {
await fetch("http://localhost:5000/todos", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title: title}),
});
fetchTodos(); // refresh list after adding
};Add this state:
const [title, setTitle] = useState("");Usage:
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Enter todo"
/>
<button onClick={addTodo}>Add</button>Explanation:
- Sends data using
POST JSON.stringifyconverts object to JSON- Calls
fetchTodos()to update UI
3. Update Todo (PUT)
This updates an existing todo.
const updateTodo = async (id) => {
if (!title) return;
await fetch(`${BASE_URL}/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: title,
completed: true,
}),
})
setEditingId(null);
setTitle("");
fetchTodos();
};Get Shoomankhatri’s stories in your inbox
Join Medium for free to get updates from this writer.
Update EditingId on map function:
const [editingId, setEditingId] = useState(null);
<button
className="bg-yellow-400 py-4 px-6"
onClick={() => {
setEditingId(todo._id);
setTitle(todo.title);
}}
>
edit
</button>Toggle Button
<div>
{editingId ? (
<button onClick={updateTodo}>Update</button>
) : (
<button onClick={addTodo}>Add</button>
)}
</div>Usage:
<button onClick={() => updateTodo(todo._id)}>Update</button>Explanation:
- Uses
PUTmethod - Sends updated fields
- Uses
idto target specific todo
4. Delete Todo (DELETE)
This removes a todo.
const deleteTodo = async (id) => {
await fetch(`${BASE_URL}/${id}`, {
method: "DELETE",
});
fetchTodos();
};Usage:
<button onClick={() => deleteTodo(todo._id)}>Delete</button>Explanation:
- Uses
DELETErequest - Removes item by ID
- Refreshes list after deletion
5. Combined Simple UI
A minimal working CRUD interface.
"use client";
import React, { useEffect, useState } from "react";
import { BASE_URL } from "../api/api";
const page = () => {
const [todos, setTodos] = useState([]);
const [title, setTitle] = useState("");
const [editingId, setEditingId] = useState(null);
// Fetch data from backend
const fetchTodos = async () => {
const res = await fetch(BASE_URL);
const data = await res.json();
setTodos(data);
};
const addTodo = async () => {
await fetch(BASE_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title }),
});
setTitle("");
fetchTodos(); // refresh list after adding
};
const updateTodo = async () => {
if (!title) return;
await fetch(`${BASE_URL}/${editingId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title }),
});
setTitle(""); // clear input
setEditingId(null); // exit edit mode
await fetchTodos(); // refresh list
};
const deleteTodo = async (id) => {
await fetch(`${BASE_URL}/${id}`, {
method: "DELETE",
});
fetchTodos();
};
// Run once on page load
useEffect(() => {
fetchTodos();
}, []);
return (
<>
<div>todo list</div>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Enter todo"
/>
<div>
{editingId ? (
<button onClick={updateTodo}>Update</button>
) : (
<button onClick={addTodo}>Add</button>
)}
</div>
{todos.map((todo) => (
<p key={todo._id}>
{todo.title}
<button
className="bg-yellow-400 py-4 px-6"
onClick={() => {
setEditingId(todo._id);
setTitle(todo.title);
}}
>
edit
</button>
<button
className="bg-red-600 py-3s px-6"
onClick={() => deleteTodo(todo._id)}
>
Delete
</button>
</p>
))}
<div></div>
</>
);
};
export default page;Explanation:
- Input field stores new todo text
- Add button sends POST request
- Todos are listed dynamically
- Delete button removes items









