DEV Community

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

Posted on

3 1 1 1

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!

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more