Tired of fake data and mock servers? Want to build real projects with real APIs?In this post, I'll show you how to build a simple yet powerful Todo App using Crudify.dev β a platform that provides real-world REST APIs without the backend hassle.
β What We're Building
A clean, responsive Todo List App powered by Crudify.dev's Todo API. It supports:
- β Creating tasks
- π Editing tasks
- β Marking tasks complete/incomplete
- β Deleting tasks
And best of all β no backend code needed.
π Tech Stack
- HTML + CSS: For structure and style
- JavaScript (Vanilla): For interacting with the API
- *Crudify.dev:** For real, production-style REST APIs
π Step-by-Step Breakdown
1. π¨ UI Layout (HTML & CSS)
We started with a simple HTML structure:
<div class="container">
<h1>Todo App</h1>
<form id="todo-form">
<input type="text" id="todo-input" placeholder="Enter a new task" required>
<button type="submit">Add Task</button>
</form>
<ul id="todo-list"></ul>
</div>
And styled it with responsive CSS that feels modern but minimal:
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
.container {
max-width: 500px;
margin: 30px auto;
padding: 20px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h1 {
text-align: center;
color: #333;
}
#todo-form {
display: flex;
margin-bottom: 20px;
}
#todo-input {
flex-grow: 1;
padding: 10px;
font-size: 16px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
}
button {
padding: 10px 15px;
background-color: #4caf50;
color: #fff;
border: none;
cursor: pointer;
font-size: 16px;
border-radius: 0 4px 4px 0;
}
button:hover {
background-color: #45a049;
}
#todo-list {
list-style-type: none;
padding: 0;
}
#todo-list li {
background-color: #f9f9f9;
padding: 10px;
margin-bottom: 10px;
border-radius: 4px;
display: flex;
justify-content: space-between;
align-items: center;
}
.todo-actions button {
margin-left: 5px;
padding: 5px 10px;
font-size: 14px;
border-radius: 4px;
}
.todo-actions .edit {
background-color: #2196f3;
}
.todo-actions .delete {
background-color: #f44336;
}
.todo-actions .complete {
background-color: #ff9800;
}
.completed {
text-decoration: line-through;
opacity: 0.6;
}
2. π§ Using the Crudify.dev Todo API
Crudify gives you a real REST API β no backend required. Once logged in with GitHub, you get your API token, which you'll use like this:
const API_BASE_URL = "https://www.crudify.dev/api/v1/todo-lists";
const ACCESS_TOKEN = "your-api-token-here";
Then use standard fetch calls to interact with your data.
3. π Fetching Todos
async function fetchTodos() {
const response = await fetch(API_BASE_URL, {
headers: { Authorization: `Bearer ${ACCESS_TOKEN}` }
});
const todos = await response.json();
renderTodos(todos);
}
This gives us a list of real tasks β just like a production app.
4. β Adding a Todo
todoForm.addEventListener("submit", async (e) => {
e.preventDefault();
const task = todoInput.value.trim();
await fetch(API_BASE_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ task }),
});
todoInput.value = "";
fetchTodos();
});
5. π Edit, β Complete, β Delete
The app also supports editing, toggling completion, and deleting with just a few lines:
// Toggle completion
await fetch(`${API_BASE_URL}/${id}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ isDone: !isDone }),
});
// Delete a todo
await fetch(`${API_BASE_URL}/${id}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${ACCESS_TOKEN}` },
});
All actions automatically refresh the list, keeping your UI up to date.
π Final Result
You get a fully working Todo App that:
- Connects to a real API (with auth, status codes, errors)
- Mimics real-world production apps
- Requires zero backend setup
- Works great with Crudify's design kits for Figma-to-code practice
π¦ Complete Code
Here's the full JavaScript that powers our app:
const API_BASE_URL = "https://www.crudify.dev/api/v1/todo-lists"
const ACCESS_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // Your token will be different
const todoForm = document.getElementById("todo-form");
const todoInput = document.getElementById("todo-input");
const todoList = document.getElementById("todo-list");
// Fetch all todos
async function fetchTodos() {
try {
const response = await fetch(API_BASE_URL, {
headers: {
Authorization: `Bearer ${ACCESS_TOKEN}`,
},
});
const todos = await response.json();
renderTodos(todos);
} catch (error) {
console.error("Error fetching todos:", error);
todoList.innerHTML = "<p>Failed to load todos.</p>";
}
}
// Render todos
function renderTodos(todos) {
todoList.innerHTML = "";
todos.forEach((todo) => {
const li = document.createElement("li");
li.innerHTML = `
<span class="${todo.isDone ? "completed" : ""}">${todo.task}</span>
<div class="todo-actions">
<button class="edit" data-id="${todo.id}">Edit</button>
<button class="delete" data-id="${todo.id}">Delete</button>
<button class="complete" data-id="${todo.id}" data-done="${todo.isDone}">
${todo.isDone ? "Undo" : "Complete"}
</button>
</div>
`;
todoList.appendChild(li);
});
}
// Add new todo
todoForm.addEventListener("submit", async (e) => {
e.preventDefault();
const task = todoInput.value.trim();
if (!task) return;
const submitButton = todoForm.querySelector("button");
submitButton.textContent = "Adding...";
submitButton.disabled = true;
try {
await fetch(API_BASE_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ task }),
});
todoInput.value = "";
fetchTodos();
} catch (error) {
console.error("Error adding todo:", error);
} finally {
submitButton.textContent = "Add Task";
submitButton.disabled = false;
}
});
// Event delegation for edit, delete, and complete actions
todoList.addEventListener("click", async (e) => {
const button = e.target;
if (button.classList.contains("edit")) {
const id = button.getAttribute("data-id");
const newTask = prompt("Enter new task:");
if (newTask) {
await editTodo(id, newTask, button);
}
} else if (button.classList.contains("delete")) {
const id = button.getAttribute("data-id");
if (confirm("Are you sure you want to delete this task?")) {
await deleteTodo(id, button);
}
} else if (button.classList.contains("complete")) {
const id = button.getAttribute("data-id");
const isDone = button.getAttribute("data-done") === "true";
await toggleTodoCompletion(id, isDone, button);
}
});
// Initial fetch of todos
fetchTodos();
π¦ Code on GitHub
Want the full code?
π¬ Why Use Crudify.dev?
Most mock APIs don't let you:
- Create/update/delete data
- Practice auth headers
- Handle 401/403/404 responses
Crudify.dev does all of that β and gives you real, job-ready experience.
π Try It Yourself
- Visit Crudify.dev
- Log in with GitHub
- Grab your API token
- Clone the app or build your own
π§ Final Thoughts
This tiny app demonstrates how easy it is to work with real APIs using Crudify.dev. Whether you're:
- Practicing frontend dev
- Building a portfolio
- Preparing for interviews
β this tool is a game-changer.
π Big shoutout to Crudify.dev for making real-world development accessible for all frontend devs.
Top comments (0)