Build a Small To-Do List with Node.js, Express, and Vanilla JavaScript
Computer Science
Build a Small To-Do List with Node.js, Express, and Vanilla JavaScript
Build a Small To-Do List with Node.js, Express, and Vanilla JavaScript
Turn your existing HTML and CSS foundation into a small working full-stack to-do list. You will add a focused Express server, connect a browser interface with vanilla JavaScript, and finish with a project you can run locally, use, and explain.
What you learn by building this
- Create a minimal Express server and serve an existing HTML, CSS, and JavaScript interface
- Design and implement small GET and POST endpoints for a to-do workflow
- Use vanilla JavaScript to submit forms, call server endpoints, render task data, and respond to user actions
- Represent tasks as JavaScript data and validate empty or malformed submissions
- Debug the complete browser-to-server path using the terminal and browser developer tools
Learning Journey
Shape the Interface Before the Server
3 lessonsUse the learner's existing HTML structure and CSS selector skills to create a complete static to-do interface first. The result works as a visible, dependency-free front end before any server or API is introduced.
Add the Express Server and API
4 lessonsIntroduce one new layer at a time: first a Node.js process serving the finished front end, then a tiny in-memory API for reading and creating tasks. The project becomes a real client-server application without adding a database or framework-heavy architecture.
Connect the Browser to the API
3 lessonsReplace sample data with real requests and make the core workflow usable. The learner will keep the interface simple while learning the exact browser-to-server path needed for a credible small full-stack project.
Finish the To-Do Workflow
3 lessonsComplete the project with task completion and deletion, then run a focused polish and verification pass. The final result is small enough to understand but complete enough to demonstrate a real full-stack workflow.
Public lesson
Build the semantic to-do screen
Lesson 3-1: Build the semantic to-do screen
Your HTML work is already solid, so this lesson is about making the React screen’s structure match the interface: a page heading, task form, task list, empty state, and controls.
You’ll keep the work in one component for now. That gives you a clear screen to inspect before extracting smaller React components later.
Tasks
1. Find the screen entry point
Open the React app and locate the component currently rendered for the to-do page. It may be named something like App, TodoPage, or TaskScreen.
Before changing it, open the app in the browser and note:
- Which component renders first?
- Where would a user add a task?
- Where would a user see existing tasks?
- What should appear when there are no tasks?
Do not create a new route or component if the project already has a screen for this. Extend the existing screen so the current app wiring keeps working.
Tasks
2. Add the page outline
Replace the screen’s current placeholder content with an outline using the existing styling conventions from the project.
Use one main landmark, a page header, and separate section elements for the form and task area. Give each section a heading so its purpose is clear to both users and assistive technology.
function TodoPage() {
// Use the task data already present in this project.
const isEmpty = /* derive this from the task collection */;
return (
<main className="todo-page">
<header className="todo-header">
<p className="eyebrow">/* short supporting text */</p>
<h1>/* page title */</h1>
<p>/* one-sentence description */</p>
</header>
<section aria-labelledby="new-task-heading">
<h2 id="new-task-heading">/* form section heading */</h2>
{/* Add the task form here. */}
</section>
<section aria-labelledby="task-list-heading">
<h2 id="task-list-heading">/* list section heading */</h2>
{/* Render either the empty state or the task list here. */}
</section>
</main>
);
}
function TodoPage() {
// Use the task data already present in this project.
const isEmpty = /* derive this from the task collection */;
return (
<main className="todo-page">
<header className="todo-header">
<p className="eyebrow">/* short supporting text */</p>
<h1>/* page title */</h1>
<p>/* one-sentence description */</p>
</header>
<section aria-labelledby="new-task-heading">
<h2 id="new-task-heading">/* form section heading */</h2>
{/* Add the task form here. */}
</section>
<section aria-labelledby="task-list-heading">
<h2 id="task-list-heading">/* list section heading */</h2>
{/* Render either the empty state or the task list here. */}
</section>
</main>
);
}
Tasks
3. Add the task form
Inside the first section, create a form with:
- A visible
label - A text input connected to that label with matching
htmlForandid - A
name required- A submit button with
type="submit"
Connect onSubmit to the project’s existing submit handler if one exists. If this lesson’s project does not have task behavior yet, use the handler placeholder already expected by the project rather than inventing a separate data flow.
<form onSubmit={/* use the existing submit handler */}>
<label htmlFor="task-title">/* label text */</label>
<input
id="task-title"
name="title"
type="text"
placeholder="/* useful example */"
required
/>
<button type="submit">/* action text */</button>
</form>
<form onSubmit={/* use the existing submit handler */}>
<label htmlFor="task-title">/* label text */</label>
<input
id="task-title"
name="title"
type="text"
placeholder="/* useful example */"
required
/>
<button type="submit">/* action text */</button>
</form>
Adapt the class names and text to your project rather than copying the example wording blindly.
Tasks
4. Render a task list and its controls
Use the task collection already used by the app. If the collection does not exist yet, create a small temporary collection in the component so you can inspect the layout. Give each temporary task a stable id, title, and completion value.
The list should use:
ulfor the collectionlifor each task- A checkbox for completion
- A label associated with that checkbox
- A button for removing the task
Fill in the missing expressions and handlers for your project:
{!isEmpty && (
<ul className="task-list">
{tasks.map((task) => (
<li key={/* stable task id */} className="task-item">
<label>
<input
type="checkbox"
checked={/* task completion value */}
onChange={/* existing completion handler */}
/>
<span>{/* task title */}</span>
</label>
<div className="task-controls">
<button
type="button"
onClick={/* existing remove handler */}
aria-label={/* name the task this button removes */}
>
/* remove text */
</button>
</div>
</li>
))}
</ul>
)}
{!isEmpty && (
<ul className="task-list">
{tasks.map((task) => (
<li key={/* stable task id */} className="task-item">
<label>
<input
type="checkbox"
checked={/* task completion value */}
onChange={/* existing completion handler */}
/>
<span>{/* task title */}</span>
</label>
<div className="task-controls">
<button
type="button"
onClick={/* existing remove handler */}
aria-label={/* name the task this button removes */}
>
/* remove text */
</button>
</div>
</li>
))}
</ul>
)}
The button is explicitly type="button" so it cannot accidentally submit the separate task form.
Tasks
5. Add the empty state
The empty state belongs in the same task section, but it should appear instead of the list when there are no tasks.
Add it to the isEmpty branch:
{isEmpty ? (
<div className="empty-state" role="status">
<h3>/* empty-state heading */</h3>
<p>/* tell the user what to do next */</p>
</div>
) : (
/* your task list */
)}
{isEmpty ? (
<div className="empty-state" role="status">
<h3>/* empty-state heading */</h3>
<p>/* tell the user what to do next */</p>
</div>
) : (
/* your task list */
)}
Use a heading level that follows the section’s h2. The empty state should explain the next action rather than only saying “No tasks.”
Tasks
6. Inspect the finished structure
With the page open, use the browser’s Elements panel to verify this outline:
main
├── header
│ └── h1
├── section
│ ├── h2
│ └── form
│ ├── label
│ ├── input
│ └── button
└── section
├── h2
└── ul
└── li
├── label + checkbox
└── button
main
├── header
│ └── h1
├── section
│ ├── h2
│ └── form
│ ├── label
│ ├── input
│ └── button
└── section
├── h2
└── ul
└── li
├── label + checkbox
└── button
Then test the empty branch once more by clearing the task collection.
Course Outline
4 modules · 13 lessons
Shape the Interface Before the Server
Add the Express Server and API
Connect the Browser to the API
Finish the To-Do Workflow
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.