Build a To-Do List with Express and Vanilla JavaScript

Computer Science

Build a To-Do List with Express and Vanilla JavaScript

JavaScriptNode.jsExpress

Build a To-Do List with Express and Vanilla JavaScript

Turn your existing HTML, CSS, and early React experience into a small full-stack to-do app. You will build the page structure first, add a focused Express server and tiny JSON API, then connect the browser with vanilla JavaScript so tasks can be added, completed, and removed.

3 modules10 lessonsComputer ScienceJavaScriptNode.jsExpressHTMLCSSBrowser Fetch API

What you learn by building this

  • Create and run a minimal Express server
  • Serve an existing semantic HTML interface and static browser assets from Node.js
  • Design a small in-memory task API with GET, POST, PATCH, and DELETE routes
  • Use fetch and DOM updates to add, complete, and remove tasks without page reloads
  • Handle empty input, missing tasks, and visible loading or error states

Learning Journey

1

Make the To-Do Page Work Without a Server

3 lessons

Use your existing HTML and CSS foundation to create a complete, testable to-do interface before introducing Express. The result is a useful front end with local browser behavior that can later be replaced by API calls.

2

Put the To-Do Data Behind Express

3 lessons

Cross the bridge from familiar browser code to a small Node.js server: first run one route, then add only the API behavior the project needs. The app becomes a working full-stack foundation without adding a database or framework-heavy architecture.

3

Connect the Browser to the API

4 lessons

Swap local array mutations for fetch requests while keeping the interface the learner already built. Finish with a polished, runnable app that proves the browser, Express routes, and DOM are working together.

Public lesson

Shape the To-Do Interface

Your HTML and CSS work already gives you the right instincts for page structure and targeted selectors. This lesson applies those skills inside your React app: first shape the interface, then make each part easy to change later.

For now, the controls only need to look correct. We will wire up their behavior in a later lesson.

Tasks

Start your React app and open it in the browser.

Then open the component currently rendered by the app—often src/App.jsx or src/App.tsx. Identify:

  • the component that controls the page
  • the stylesheet it uses
  • whether the starter app already has a heading or wrapper to keep

Do not replace useful project setup. You are reshaping the page inside the existing app.

Predict

What will happen?

Before writing JSX, sketch this hierarchy in a comment or on paper:

page
├── header
│   ├── app title
│   └── short description
└── main
    ├── task form section
    │   ├── label
    │   ├── text input
    │   └── add button
    └── task list section
        ├── section heading
        └── either
            ├── list of task items
            └── empty-state message

For one task item, choose elements for:

  • the task text
  • its completion control
  • a delete control

Use elements for their meaning, not just their default appearance. For example, a task collection should be a list, and an action should be a button.

Use four small components:

  • TaskForm
  • TaskList
  • TaskItem
  • App

The components can be in one file for now. The point is to give each visible part a clear home.

Create a small task shape such as:

{
  id: 1,
  title: "Read React notes",
  completed: false
}

Tasks

Then fill in the missing JSX in this scaffold. Adapt the class names and existing imports to your project rather than copying them blindly.

function TaskForm() {
  return (
    <section className="task-section">
      {/* Add a heading, a labelled text input, and a submit button.
          The form does not need working submission yet. */}
    </section>
  );
}

function TaskItem({ task }) {
  return (
    <li className="task-item">
      {/* Add a control for completion, the task title,
          and a delete button. */}
    </li>
  );
}

function TaskList({ tasks }) {
  if (tasks.length === 0) {
    return (
      <p className="empty-state">
        {/* Write a useful message for a person with no tasks. */}
      </p>
    );
  }

  return (
    <section className="task-section">
      {/* Add the list heading and map tasks into TaskItem components. */}
    </section>
  );
}

export default function App() {
  const tasks = [
    // Add two tasks with different completed values.
  ];

  return (
    <div className="app-shell">
      {/* Add the page header and main content.
          Render TaskForm and TaskList here. */}
    </div>
  );
}

Tasks

As you complete it, check these details in the browser and DevTools:

  • the input has a visible <label> connected with htmlFor and id
  • the task collection is an actual <ul>
  • each task is an <li>
  • every button has a clear accessible name
  • the task list receives the tasks array as a prop
  • each mapped task has a stable key, using its id

The checkboxes and buttons do not need to change state yet. They should be present and understandable.

Tasks

Temporarily set the tasks array to an empty array.

You should see the empty-state message instead of an empty <ul>. This is an important structural difference: a person with no tasks should receive guidance, not a blank area.

Tasks

Now restore the two sample tasks. You should see:

  • the list heading
  • two task rows
  • one completed-looking task if you add the appropriate class or attribute
  • completion and delete controls on each row

If the empty state appears even when tasks exist, check the condition around tasks.length.

Reuse the selector habits from your HTML project. Give the page a few meaningful classes rather than styling every nested element through long descendant selectors.

Tasks

Add styles for:

  • the page shell: centered column, readable width, spacing
  • the header: stronger title and muted description
  • the form: input and add button aligned clearly
  • the task section: separated from the header and form
  • each task item: row layout, border or background, spacing
  • task controls: visually distinct but consistent buttons
  • .empty-state: noticeable but quieter than the main heading
  • a completed task: visibly different from an active task

Start with this partial stylesheet and complete the missing values using your existing CSS knowledge.

.app-shell {
  /* Set the page width, outer spacing, and readable text color. */
}

.app-header {
  /* Create space below the header. */
}

.app-header h1 {
  /* Make the application title prominent. */
}

.task-section {
  /* Give each major section comfortable spacing. */
}

.task-form {
  /* Lay out the input and add button. */
}

.task-form input {
  /* Make the input easy to use and fill the available row space. */
}

.task-form button,
.task-controls button {
  /* Share the important button basics. */
}

.task-list {
  /* Remove list indentation and choose a vertical gap. */
}

.task-item {
  /* Lay out the task text and controls as one row. */
}

.task-item.completed .task-title {
  /* Show that this task is complete without hiding its text. */
}

.task-controls {
  /* Keep the completion and delete controls together. */
}

.empty-state {
  /* Style the no-tasks message so it is easy to notice. */
}

Keep the selectors tied to the interface’s meaning. For example, .empty-state is more useful than a selector based on “the third paragraph.”

Tasks

Use the browser at a narrow width and a wider width. Your page should remain readable without controls colliding.

Tasks

Then inspect the rendered HTML and verify:

  • there is one page heading
  • the form has a real label and submit button
  • task titles are inside list items
  • the empty state replaces the list when there are no tasks
  • completed and active tasks can look different
  • delete and completion controls are present for every task
  • the page still renders after switching between sample tasks and []

Your interface is ready for behavior when the structure looks like this:

App
├── TaskForm
└── TaskList
    └── TaskItem × number of tasks

The next step will give those controls real state changes.

Course Outline

3 modules · 10 lessons

Make the To-Do Page Work Without a Server

Put the To-Do Data Behind Express

Connect the Browser to the API

Learn by building your own version.

Remix this public project to open the workspace, follow the guided build, and let the AI mentor teach you through the work instead of doing it for you.