DEV Community

Cover image for Build Your First Todo App on ICP: A Friendly Guide
Mahmud eyitoba bello
Mahmud eyitoba bello

Posted on

Build Your First Todo App on ICP: A Friendly Guide

Ready to build something cool on the Internet Computer? Let's create a todo app together. Don't worry if you're new to this - I'll walk you through everything step by step, no fancy jargon required.

Why ICP Is Pretty Cool

Think of Internet Computer like a giant, shared computer that's always online. Instead of running your app on servers owned by big companies, it runs on a network that belongs to everyone. Pretty neat, right? Here's why it's awesome:

  • Your app never goes down (unless the internet itself breaks!)
  • You don't need to pay for expensive servers
  • Users don't pay gas fees like on other blockchains
  • It's fast - like, really fast

Getting Started

First, let's get your computer ready. Open your terminal and type:

sh -ci "$(curl -fsSL https://sdk.dfinity.org/install.sh)" 
Enter fullscreen mode Exit fullscreen mode

This installs dfx - your new best friend for building ICP apps. Now create your project:

dfx new todo_app cd todo_app 
Enter fullscreen mode Exit fullscreen mode

Building the Brain (Backend)

Let's create the backend first. find a file called src/todo_app_backend/main.mo and drop in this code:

import Array "mo:base/Array"; actor { // This is what each todo looks like type Todo = { id: Nat; text: Text; completed: Bool; }; // Keep all todos in one place private var todos: [Todo] = []; private var nextId: Nat = 0; // Add a new todo to the list public func addTodo(text: Text) : async Nat { let todo: Todo = { id = nextId; text = text; completed = false; }; todos := Array.append(todos, [todo]); nextId += 1; return todo.id; }; // Get all your todos public query func getTodos() : async [Todo] { return todos; }; // Mark a todo as done (or not done) public func toggleTodo(id: Nat) : async Bool { todos := Array.map(todos, func (todo: Todo) : Todo { if (todo.id == id) { return { id = todo.id; text = todo.text; completed = not todo.completed; }; }; todo; }); return true; }; // Remove a todo public func deleteTodo(id: Nat) : async Bool { todos := Array.filter(todos, func(todo: Todo) : Bool { todo.id != id }); return true; }; } 
Enter fullscreen mode Exit fullscreen mode

Making It Pretty (Frontend)

Now for the part you'll actually see! Create src/todo_app_frontend/src/main.jsx:

import React, { useState, useEffect } from 'react'; import { todo_app_backend } from 'declarations/todo_app_backend'; function App() { const [todos, setTodos] = useState([]); const [newTodo, setNewTodo] = useState(''); // Get all todos when the app starts const loadTodos = async () => { const result = await todo_app_backend.getTodos(); setTodos(result); }; // Add a new todo const handleAdd = async (e) => { e.preventDefault(); if (!newTodo.trim()) return; await todo_app_backend.addTodo(newTodo); setNewTodo(''); loadTodos(); }; // Mark a todo as done/not done const handleToggle = async (id) => { await todo_app_backend.toggleTodo(id); loadTodos(); }; // Delete a todo const handleDelete = async (id) => { await todo_app_backend.deleteTodo(id); loadTodos(); }; useEffect(() => { loadTodos(); }, []); return ( <div className="container mx-auto p-4"> <h1 className="text-2xl font-bold mb-4">Todo List</h1> <form onSubmit={handleAdd} className="mb-4"> <input type="text" value={newTodo} onChange={(e) => setNewTodo(e.target.value)} className="border p-2 mr-2" placeholder="Add new todo" /> <button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded"> Add </button> </form> <ul> {todos.map((todo) => ( <li key={todo.id} className="flex items-center mb-2"> <input type="checkbox" checked={todo.completed} onChange={() => handleToggle(todo.id)} className="mr-2" /> <span className={todo.completed ? 'line-through' : ''}> {todo.text} </span> <button onClick={() => handleDelete(todo.id)} className="ml-auto text-red-500" > Delete </button> </li> ))} </ul> </div> ); } export default App; 
Enter fullscreen mode Exit fullscreen mode

Starting It Up

Time to see your app in action! In your terminal:

dfx start --clean --background dfx deploy 
Enter fullscreen mode Exit fullscreen mode

Your app will pop up at http://localhost:4943. Pretty cool, right?

What's Actually Happening Here?

Let's break down what we just built:

  1. The Motoko code (backend) is like a super-secure notebook:

    • It remembers all your todos
    • Gives each todo a unique ID
    • Keeps track of what's done and what isn't
    • Can add, check off, or delete todos
  2. The React code (frontend) is like a friendly interface:

    • Shows you all your todos
    • Lets you type in new ones
    • Has checkboxes to mark things as done
    • Includes delete buttons to remove todos

Cool Things You Can Add

Now that you've got the basics working, why not spice it up? You could:

  • Add due dates
  • Group todos into categories
  • Share todos with friends
  • Add file attachments

When Things Go Wrong

Don't panic! Here's what to check:

  • Is dfx running? (check with dfx status)
  • Did you save all your files?
  • Are there any red error messages?
  • When in doubt, try dfx deploy again

Need Help?

Everyone gets stuck sometimes. Here's where to find help:

  • DFINITY's Discord channel
  • Stack Overflow (tag: 'internet-computer')
  • Internet Computer forums

Remember: every developer started exactly where you are now. Take your time, have fun, and don't be afraid to experiment - that's how we learn!

Top comments (0)