Tangy is building your next step
Shaping the lesson around what you want to make.
Tangy is building your next step
Shaping the lesson around what you want to make.
Computer Science
Turn your existing HTML and CSS foundation into a small full-stack to-do app. You will add an Express server, connect browser interactions to a tiny API, and finish with a runnable app that can create, complete, delete, and filter tasks.
Shape the project and run the server
3 lessonsReuse the learner's existing semantic HTML and CSS strengths while introducing only the Node.js and Express runtime needed to make the project serve itself.
Build the task API
4 lessonsAdd the smallest useful backend: an in-memory collection of tasks and predictable endpoints that the frontend can use.
Connect the browser to the API
3 lessonsUse familiar DOM work and targeted selectors to make the static page behave like a real application, with the browser and server sharing task state.
Make the app useful and shippable
4 lessonsAdd the final user-facing behavior, then verify the complete workflow so the result is something the learner can confidently run and show.
Public lesson
You already know how to make semantic HTML and target it with CSS. This step is about deciding what the app must do and putting each responsibility in a place where you can find it later.
Work in your existing react-fullstack-app project. Keep your current HTML structure and styles; do not rebuild them from scratch.
Tasks
Before creating files, decide what “done” means for the first version.
Create or update README.md with this checklist. Replace the bracketed text with your own wording where needed.
# To-do app
## Version 1 checklist
- [ ] Show the saved tasks when the page loads.
- [ ] Add a new task from the page.
- [ ] Mark a task as complete or incomplete.
- [ ] Delete a task.
- [ ] Save task changes so they remain after a restart.
## Task shape
Each task has:
- `id`: a unique value
- `title`: the text shown to the user
- `completed`: `true` or `false`
## Not in version 1
- [ ] Add your own feature that will wait until a later version.
# To-do app
## Version 1 checklist
- [ ] Show the saved tasks when the page loads.
- [ ] Add a new task from the page.
- [ ] Mark a task as complete or incomplete.
- [ ] Delete a task.
- [ ] Save task changes so they remain after a restart.
## Task shape
Each task has:
- `id`: a unique value
- `title`: the text shown to the user
- `completed`: `true` or `false`
## Not in version 1
- [ ] Add your own feature that will wait until a later version.
Keep the first version small. For example, filtering, accounts, due dates, and categories can wait.
Tasks
Check: Read the checklist as a user story. Every item should describe something you could demonstrate in the browser, except the task shape and “not in version 1” notes.
Tasks
Create this structure inside the project:
react-fullstack-app/
├── data/
│ └── tasks.json
├── public/
│ ├── index.html
│ ├── styles.css
│ └── app.js
├── server/
│ └── index.js
├── package.json
└── README.md
react-fullstack-app/
├── data/
│ └── tasks.json
├── public/
│ ├── index.html
│ ├── styles.css
│ └── app.js
├── server/
│ └── index.js
├── package.json
└── README.md
If your existing files have different names, keep the names that already make sense. The important separation is:
server/ — receives requests and reads or changes task datapublic/ — the files the browser loadsdata/ — saved task informationREADME.md — the feature contractTasks
Move or copy your existing semantic page into public/index.html and your existing CSS into public/styles.css. Keep the meaningful elements you already made, such as headings, labels, forms, lists, and buttons.
Tasks
Do not add JavaScript behavior yet. Leave the existing controls as markup until the app’s data flow is connected.
Tasks
Check: Open public/index.html directly in the browser. You should still see the same page structure and styling you had before. If the styling disappeared, fix the stylesheet path before continuing.
Tasks
Create data/tasks.json with two or three tasks. Use realistic text that will make the later interface easy to recognize.
[
{
"id": "task-1",
"title": "Replace this with a task you really want to track",
"completed": false
},
{
"id": "task-2",
"title": "Add another task for testing",
"completed": true
}
]
[
{
"id": "task-1",
"title": "Replace this with a task you really want to track",
"completed": false
},
{
"id": "task-2",
"title": "Add another task for testing",
"completed": true
}
]
Change the IDs and titles rather than keeping the placeholder wording. Keep completed as a Boolean: it must be either true or false, not a quoted string.
Tasks
Check: Parse the file with your editor’s JSON validation, or run the project’s available validation command. The file should contain one array with no red JSON errors. It should also contain at least one incomplete task and one completed task so both states can eventually be displayed.
Tasks
Create server/index.js. This is only the server’s starting outline for now; leave the request behavior unfinished.
const express = require("express");
const path = require("path");
const app = express();
const port = /* choose the port your project will use */;
app.use(express.json());
app.use(express.static(path.join(__dirname, "..", "public")));
app.get("/api/tasks", (request, response) => {
// Later: read the tasks from data/tasks.json.
});
app.listen(port, () => {
console.log(`To-do app running on port ${port}`);
});
const express = require("express");
const path = require("path");
const app = express();
const port = /* choose the port your project will use */;
app.use(express.json());
app.use(express.static(path.join(__dirname, "..", "public")));
app.get("/api/tasks", (request, response) => {
// Later: read the tasks from data/tasks.json.
});
app.listen(port, () => {
console.log(`To-do app running on port ${port}`);
});
Choose the same port your project’s existing setup expects. Do not try to implement the API route yet; the purpose here is to make the boundary visible:
browser
│
│ requests page or /api/tasks
▼
server/index.js
│
│ later reads and updates
▼
data/tasks.json
browser
│
│ requests page or /api/tasks
▼
server/index.js
│
│ later reads and updates
▼
data/tasks.json
The browser-facing files stay in public, while saved task information stays in data. That separation will keep the upcoming request code easier to reason about.
Tasks
Check: Start the server using the command already defined by your project. You should see the server’s startup message. Visit the local address in a browser and confirm that it serves your existing public/index.html.
Find the bug
Something's wrong — can you spot it?
If starting the server fails, check these in order:
server/index.js?public/index.html?Tasks
Look at each version 1 feature and point to the file that will eventually help deliver it:
| Feature | Main place it will live |
|---|---|
| Show tasks | public/app.js + server/index.js |
| Add a task | public/index.html + public/app.js + server |
| Complete a task | public/app.js + server |
| Delete a task | public/app.js + server |
| Persist changes | data/tasks.json + server |
This is a map, not an instruction to implement all five features now.
Tasks
Final check: Your project should now have:
publicdata/tasks.json/api/tasks boundaryStop here once the structure is working. The next lesson can give the server route a real job without forcing the browser, server, and data storage into one large file.
4 modules · 14 lessons
Shape the project and run the server
Build the task API
Connect the browser to the API
Make the app useful and shippable
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.