Build
Claude Code Tutorial: Build a Real App
Checked 2026-10-31
Loading poll...
By the end of this tutorial you will have a small command-line tool, built with Claude Code from an empty folder: it reads a CSV of expenses and prints totals by category, with tests that pass and a branch ready for a pull request. The shortest path is a loop of five moves: interview, spec, plan, build with a test, review. Allow about an hour. Facts checked 31 October 2026.
What you will have at the end
- A Node.js command-line tool called
spend, with a test file - A
SPEC.mdthat says what "done" means - A short
CLAUDE.mdthat holds the commands Claude needs - A branch with small commits, reviewed by a fresh subagent
- A repeatable habit you can reuse on any other project
This is the second of three Claude Code guides on this site. The first-run guide covers install and basics. The best-practices guide covers permissions, hooks and subagents for teams. Here you build something.
Prerequisites
- Claude Code installed and signed in (see how to use Claude Code)
- Node.js 20 or newer, and
npm git- Optional: the GitHub CLI
gh, signed in, if you want Claude to open the pull request
Why this project? It is small enough to finish in an hour and real enough to need decisions: parsing, edge cases, error messages, a command-line interface. You will not paste a finished listing from us. Output differs between runs, so you will judge Claude's work with a test you can run.
Step 1: Create the project and start a session
mkdir spend && cd spend
git init
npm init -ySet the project to ES modules and wire up the test command. Node has a built-in test runner, so there is nothing to install.
npm pkg set type=module
npm pkg set scripts.test="node --test"
git add -A && git commit -m "chore: empty project"Start Claude Code in plan mode, which reads and proposes but writes nothing until you approve.
claude --permission-mode planStep 2: Let Claude interview you
You know roughly what you want, but not every detail. The best-practices page suggests asking Claude to interview you instead of guessing.
I want a small command-line tool called spend. It reads a CSV of expenses and prints totals by category. I have not thought through the details. Interview me until you could write the spec, then write it to SPEC.md.Claude comes back with multiple-choice questions. Answer like a product owner. These are the choices we would make, and you can pick different ones:
- Columns:
date,category,amount,note - Amounts: plain decimals, in one currency
- Output: a table sorted by total, highest first, plus a grand total
- Bad rows: skip them, print a warning with the line number, and exit with code 1 at the end
- No dependencies beyond Node itself
When the interview ends, Claude writes SPEC.md. Open it and read it. Fix anything that is wrong now, because every later step builds on it. Add a "Done means" list at the bottom, with the examples you will test:
## Done means
- `node src/cli.js sample.csv` prints one line per category and a total
- A row with a non-numeric amount is skipped with a warning naming the line
- An empty file prints "No expenses found" and exits 0
- `npm test` passesThen exit and clear the context so the build starts clean. The docs recommend a fresh session built from the spec, with a written definition of done.
Step 3: Write a CLAUDE.md that holds only what Claude cannot see
Start a normal session and run /init.
claude/initClaude reads the (nearly empty) project and writes a first CLAUDE.md. The memory docs say to keep it short, so trim it down to the facts Claude cannot find by reading code:
# Commands
- Run all tests: npm test
- Run the tool: node src/cli.js <file.csv>
# Conventions
- ES modules only, no dependencies
- Parsing lives in src/parse.js, totals in src/totals.js, the CLI in src/cli.js
- Money is handled in integer cents internally, never floats
# Gotchas
- SPEC.md is the source of truth for behaviourThat last convention is a decision of ours, and it matters. Adding floats to money is a classic bug, so we say it once. Run /context and confirm the file loaded under Memory files.
Step 4: Plan before you build
Switch to plan mode with Shift+Tab until the status bar shows plan mode, then ask:
Read SPEC.md and CLAUDE.md. Propose a build order in small steps, each with a test written first. Do not write code yet.Read the plan. Look for three things: tests before code, one small step at a time, and nothing the spec did not ask for. If the plan adds a config file, a logging library or a plugin system, say no. Approve the plan only when it matches the spec. The plan-mode docs describe an approval flow where you can also edit the plan in your text editor first. For a first run, leave Manual or accept-edits mode on so you still see each change.
Step 5: Build the first slice with a test
Give the first step a pass or fail check. This is the most useful habit in the whole tutorial.
Step 1 only: write tests in test/parse.test.js for a function parseLine in src/parse.js. Cover a good row, a row with a non-numeric amount, and a row with too few columns. Then implement parseLine until npm test passes. Show me the test output.Watch what it does. It should create the test file, run npm test, see failures, write the function and run again. If it writes the code first and the tests after, that is fine for a tiny function, but note it for later. Press Esc the moment it drifts toward something you did not ask for. After two failed corrections the docs advise /clear and one better prompt, not a third correction.
When it finishes, ask for evidence:
Show me the command you ran and its output.Do not accept "all tests pass" as an answer. The command and its output are the proof.
Step 6: Build the rest in slices
Repeat the pattern for each part of the plan. Here are the prompts we would use, in order. Keep each one small.
Step 2: totals. In src/totals.js, write sumByCategory taking parsed rows and returning totals in integer cents, sorted highest first. Test it with at least three categories and one tie.Step 3: the CLI. In src/cli.js read the file given as the first argument, use parseLine and sumByCategory, print a table and a grand total. Skipped rows print a warning with the line number to stderr, and the exit code is 1 if any were skipped.Step 4: add sample.csv with eight rows including one bad row, and a test that runs the CLI as a child process and checks stdout and the exit code.After each step, run the tests yourself in a second terminal, so you are not relying on the report:
npm testCommit after every green step. You can ask Claude, or do it yourself:
git add -A
git commit -m "feat: parse lines and sum by category"If you do not like where a step went, use /rewind (or Esc twice on an empty prompt) to roll the code and the conversation back to an earlier prompt. It restores files Claude edited with its own tools, not files a shell command changed, so your commits are the safety net.
Step 7: Add one feature the spec did not have
Real projects change. Add a month filter and watch how Claude handles a change to existing code.
Add a --month YYYY-MM option to the CLI that only counts rows from that month. Update SPEC.md first, then the tests, then the code. Rows with an invalid date should be skipped with a warning.Notice the order: spec, tests, code. A change that starts in the spec is easier to review than one that starts in the code. Check that Claude updated SPEC.md and that the old tests still pass.
Step 8: Review with a fresh pair of eyes
The agent that wrote the code is a poor judge of it. The docs describe two ways to get an outside view. In the terminal, /code-review reviews the diff in a fresh subagent that never saw your conversation. Or you can ask for a review against your own spec:
use a subagent to review the diff against SPEC.md, check every requirement is implemented and the listed edge cases have tests, report gaps that affect correctness and skip styleKeep "skip style". The docs warn that a reviewer asked to find problems will always find something, and fixing every finding leaves you with extra layers and tests for cases that cannot happen. Fix the real gaps and ignore the rest.
You can also make the check enforced instead of requested. A Stop hook runs your script whenever Claude tries to finish and sends it back to work if the script exits with code 2. Put this in .claude/settings.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{ "type": "command", "command": "npm test 1>&2 || exit 2" }
]
}
]
}
}Now Claude cannot end a turn while npm test fails. The docs note that Claude Code overrides a Stop hook after eight blocks in a row with no tool call in between, so a hook that can never pass will not trap you forever. Hooks are covered properly in the best-practices guide.
Step 9: Open the pull request
Create a branch, push it, and open the PR. If gh is installed, Claude can do all of it:
create a branch called feature/spend-cli, push it, and open a pull request. Describe what changed and how to run it. Include the test command.Read the PR body before you merge. It is written by a model from the diff, so check that every claim matches the code. If your team forbids certain commit trailers or attribution lines, state that rule in CLAUDE.md, because Claude Code adds its own git instructions by default and the docs list includeGitInstructions and attribution settings to change them.
Step 10: Look at what the session cost
/usageOn Pro, Max, Ultra, Team and Enterprise plans, /usage shows your plan usage and a breakdown by skill, subagent, plugin and MCP server. On API billing it also shows a session cost figure computed from token counts at list price. If a project like this felt expensive, the usual fixes are /clear between unrelated tasks, a smaller model for routine steps via /model, and lower effort via /effort.
What can go wrong
Problem | Why it happens | What to do |
|---|---|---|
Claude builds more than you asked | It reads requests conservatively or expands scope | Point at SPEC.md, say "no new files or libraries", and |
Tests pass but the tool is wrong | The test is too weak | Add the failing example from the spec as a test first |
A float rounding bug in money | Floats used for amounts | State the integer-cents rule in CLAUDE.md and test a case like 0.1 + 0.2 |
It ignores a CLAUDE.md line | The file is too long or the line is vague | Shorten the file. If it must never happen, make it a hook or a deny rule |
Session gets long and sluggish | The history is re-sent every turn |
|
Rewind did not restore a file | The file was changed by a shell command | Restore from git |
Stop hook never lets it finish | The check cannot pass | Fix the test, or stop the loop with |
A reference to compare against
Claude's code will differ from ours, and that is fine. Still, a small reference helps you judge whether your version is sound. This is the parsing and totals core we would expect, in plain JavaScript with no dependencies. We syntax-checked these blocks with node --check. Treat them as a yardstick, not as the answer to paste.
// src/parse.js
export function parseLine(line, lineNumber) {
const parts = line.split(",");
if (parts.length < 3) {
return { error: `line ${lineNumber}: expected at least 3 columns` };
}
const [date, category, amount] = parts.map((p) => p.trim());
if (!/^\d+(\.\d{1,2})?$/.test(amount)) {
return { error: `line ${lineNumber}: amount "${amount}" is not a number` };
}
const [whole, frac = ""] = amount.split(".");
const cents = Number(whole) * 100 + Number(frac.padEnd(2, "0"));
return { row: { date, category, cents } };
}// src/totals.js
export function sumByCategory(rows) {
const totals = new Map();
for (const { category, cents } of rows) {
totals.set(category, (totals.get(category) ?? 0) + cents);
}
return [...totals.entries()]
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
.map(([category, cents]) => ({ category, cents }));
}Read these against your own result with four questions.
- Does money stay in integer cents? The amount is split on the decimal point, so
0.1plus0.2can never turn into0.30000000000000004. If Claude usedparseFloatand multiplied at the end, ask for a test with those two values. - Is the sort stable for ties? The second comparison breaks a tie by name, so output does not change between runs. Without it, two equal totals can swap places, and a snapshot test will fail now and then for no visible reason.
- Where do errors go? Both functions return an error value instead of throwing, so one bad row cannot stop the run. That matches the spec line about skipping bad rows with a warning.
- What did it not handle? Quoted fields that contain commas break a plain
split(","). The spec never promised them, so a good build leaves them out and says so. If you need them, change the spec first, then the tests, then the code, which is the order from step 7.
If your version has a parsing library, a config file or a class hierarchy, it did more than the spec asked. Ask why, and remove what you cannot justify.
Pick the work back up the next day
A tutorial rarely fits in one sitting. Claude Code keeps session transcripts on your machine. To continue the last conversation in the folder, run claude --continue. To choose from earlier sessions, run claude --resume. Resuming brings the history back, but not your memory of it, so start the new session by asking Claude to read SPEC.md and git log. The spec and the commits are the source of truth, not the conversation. If the history is long, a fresh session built from the spec is usually cheaper and cleaner than a resumed one, and it avoids the stale assumptions that pile up in a long thread.
Take the loop to another stack
The tutorial uses Node because its test runner needs nothing installed. The steps do not depend on it.
Stack | Test command to put in CLAUDE.md | What to change |
|---|---|---|
Python |
| Name the virtual environment, and say which one to use |
Go |
| Nothing special, the toolchain ships a runner |
PHP |
| Say which PHP version the project targets |
Web page | A browser check or a Playwright test | Ask Claude to run and screenshot it, and view the result yourself |
Existing repo | The project's own command | Read the existing tests first and match their style |
Two adjustments matter in an existing repo. First, spend the interview on what already exists: ask Claude to read the code that will change and to name the files it expects to touch before it plans. Second, keep slices smaller. A change that crosses five modules is five reviews, so ask for one module per step, with the old tests green after each.
Habits to keep when the tutorial is over
- Write the check before the code. A failing test, a screenshot target or a script that must exit 0. Anything that can fail.
- Keep the spec alive. When behaviour changes, edit
SPEC.mdfirst. A spec that drifts is worse than none. - Commit small and often. Rewind is a convenience, but commits are the real undo.
- Distrust summaries. Ask for the command and its output, then run it yourself.
- Interrupt early. A wrong direction costs more every minute it continues, and
Escis free. - Review in a new context. The author of the code is the worst reviewer of it, whether a person or a model.
- Keep CLAUDE.md short. Add a line when Claude makes the same mistake twice, and remove lines that no longer earn their place.
Cost
You can finish this tutorial on a subscription, and it counts toward your usage window. The consumer plans on the pricing page, checked 31 October 2026, are Pro at $17 a month billed annually ($20 monthly), Max at $100 and Ultra at $200. The Free chat plan does not list Claude Code. On API billing you pay per token, and the costs page gives a planning average for enterprise deployments of about $13 per developer per active day. We have not measured what this project costs, and it will vary with the model and your prompts. Compare plans on Claude pricing.
Privacy
Every file Claude reads and every command output it sees is sent to the model. The tool and the transcript live on your machine, but the model does not. For this tutorial that is harmless: the data is a sample CSV. When you repeat the loop on a real repo, check your account's data-use setting first. On Free, Pro, Max and Ultra accounts, training on your data depends on a setting you choose. On Team, Enterprise and API use, Anthropic says it does not train on code or prompts sent to Claude Code unless the customer opts in. Keep real expense files with personal data out of the folder you point Claude at.
Checklist
- SPEC.md written from an interview and edited by you
- CLAUDE.md under 200 lines with commands and gotchas only
- Every slice started from a test that fails first
- You ran
npm testyourself, not just read Claude's report - One commit per green slice
- A spec change made before the code change
- A fresh-context review against the spec
- PR body checked against the diff
/usagelooked at once
Where to go next
Take this loop to a real task in a repo you know. Then read Claude Code best practices to turn the habits into team rules. If you want Claude to reach outside the repo, see how to install an MCP server in Claude Code, and compare options on best MCP servers. For a longer walk through packaged skills, see how to write a Claude Code skill.
What this guide uses
Item | Type | Page |
|---|---|---|
Claude Code | Agent | |
Anthropic | Provider | |
Claude models | Models | |
Subagents (review step) | Add-on | |
Code reviewer subagent | Add-on | |
GitHub MCP (optional PR flow) | Add-on | |
Claude pricing | Pricing |
Questions
What do I need before this tutorial?
Claude Code installed and signed in, Node.js 20 or newer, and git. If Claude Code is not installed yet, do the first-run guide first.
Do I have to build the expense tool?
No. The tool is a vehicle. The steps (interview, spec, plan, build, test, review, commit) work for any small project in any language.
Will Claude Code write exactly the same code for me?
No. Output varies between runs and between model versions. That is why the tutorial relies on a test file you can run, not on matching a code listing.
How much does the tutorial cost to run?
It depends on your plan and model. On a subscription it counts toward your usage limits. On API billing, run /usage to see the session figure. We have not measured the cost of this project.
Loading poll...