Claude CodeLesson 9 of 25

Building Apps with Claude Code

The best way to learn Claude Code is to build something real. This lesson walks you through building a task manager app from a blank folder to a working app with an API, a React UI, and a passing test — using nothing but Claude Code prompts.

🎯

What We're Building

A simple task manager: add tasks, check them off, delete them. Small enough to finish in one session, real enough to cover the full build flow.

Next.js 15

App Router + TypeScript

🗄️

In-memory store

No database — keep it simple

🧪

Vitest

One test for the API route

localhost:3000

My Tasks

3 of 5 complete

  • Set up project structure
  • Create API routes
  • Build task list UI
  • Add delete functionality
  • Write tests

Built with Claude Code - Next.js + TypeScript

The finished app — built entirely by describing features to Claude Code.

🧭

The Approach

We follow the habits from the previous lesson: one feature at a time, specific prompts, @-mention files, verify before continuing.

1Scaffold the project
2Write CLAUDE.md
3Build the API
4Build the UI
5Add delete
6Write a test
1

Scaffold the Project

Create the project folder and let Claude set up the Next.js scaffold. Open a terminal and start Claude Code.

Terminal
$ mkdir my-tasks && cd my-tasks
$ claude
# Now inside Claude Code session
Y

Scaffold a new Next.js 15 app with TypeScript and Tailwind. Use the App Router. Call it my-tasks. Do not add any example content — just the clean starter files.

I'll scaffold a clean Next.js 15 app with TypeScript and Tailwind using the App Router. Running npx create-next-app@latest with those options now⬦

Claude Code — scaffold output
Created my-tasks/
Installed dependencies (next, react, typescript, tailwindcss)
Removed example content from app/page.tsx
Run: npm run dev to start
Verify: run npm run dev and open localhost:3000. You should see a blank page. Only continue once it loads without errors.
2

Write CLAUDE.md

Before writing any feature code, give Claude the project conventions once. This way every future prompt in this project gets the right context automatically.

Y

Create a CLAUDE.md at the project root. Document: stack is Next.js 15 App Router + TypeScript + Tailwind. API routes go in src/app/api/. Components go in src/components/. Use 'use client' only when needed. No console.log in production code. Tests use Vitest, colocated in src/__tests__/.

I'll create CLAUDE.md at the root with those conventions now.

# CLAUDE.md
Stack: Next.js 15 App Router, TypeScript, Tailwind CSS
API routes: src/app/api/
Components: src/components/
Use 'use client' only when component needs browser APIs or state
Tests: Vitest, files in src/__tests__/
No console.log in production code
CLAUDE.md is read automatically at the start of every session in this folder. You never have to repeat these conventions.
3

Build the API Route

One API route handles all task operations. We use an in-memory array to keep things simple — no database setup.

Y

Create src/app/api/tasks/route.ts. It should export GET (return all tasks), POST (add a task with id and text), PATCH (toggle done by id), and DELETE (remove by id). Use an in-memory array. Each task has: id (number), text (string), done (boolean).

I'll create the API route at src/app/api/tasks/route.ts with GET, POST, PATCH, and DELETE handlers using an in-memory array. No database needed for this.

src/app/api/tasks/route.ts
1 import { NextRequest, NextResponse } from 'next/server';
2
3 type Task = { id: number; text: string; done: boolean };
4 let tasks: Task[] = [];
5 let nextId = 1;
6
7 export function GET() {
8 return NextResponse.json(tasks);
9 }
10
11 export async function POST(req: NextRequest) {
12 const { text } = await req.json();
13 const task = { id: nextId++, text, done: false };
14 tasks.push(task);
15 return NextResponse.json(task, { status: 201 });
16 }
⬦ PATCH and DELETE follow the same pattern
Verify: with dev server running, open localhost:3000/api/tasks in your browser. You should see []. If you do, the API is working.
4

Build the Task UI

Two components: a form to add tasks and a list to display them. We tell Claude exactly what each component should do.

Y

Create src/components/TaskList.tsx — a 'use client' component. It fetches tasks from /api/tasks on load and re-fetches after any change. Each task shows a checkbox (toggles done via PATCH), the task text (strikethrough when done), and a delete button (DELETE). Style with Tailwind — clean, minimal.

I'll create TaskList.tsx as a client component with fetch calls for GET, PATCH, and DELETE. Tailwind classes for a clean minimal style.

Y

Create src/components/AddTaskForm.tsx — a 'use client' component with a text input and an Add button. On submit, POST to /api/tasks with the input value, clear the field, then call a onTaskAdded() prop so TaskList can re-fetch.

I'll create AddTaskForm.tsx with a controlled input, POST on submit, and an onTaskAdded callback prop.

Y

Update @src/app/page.tsx to render AddTaskForm and TaskList together. Centre them on the page with a heading 'My Tasks'.

I'll update page.tsx to import and render both components with a centred layout and the heading.

Verify: Open localhost:3000, add a task, check it off. If both actions update the list instantly, this step is complete.
📁

Where Everything Lives

After the scaffold, CLAUDE.md, API route, and two components, your project structure looks like this.

Explorer — my-tasks
my-tasks/
src/
app/
- api/tasks/route.ts
- page.tsx
- layout.tsx
components/
- TaskList.tsx
- AddTaskForm.tsx
__tests__/
- tasks.test.ts
- CLAUDE.md
- package.json

Blue = Next.js App Router files - Green = React components - Yellow = test file - Purple = CLAUDE.md

5

Write a Test

Ask Claude to write a test for the API route — happy path for POST, then verify it actually passes before finishing.

Y

Write a Vitest test in src/__tests__/tasks.test.ts for the /api/tasks route. Test that POST creates a task and that GET returns it. Install Vitest if not already in package.json.

I'll create the test file and add Vitest to devDependencies. The tests will call the route handlers directly without a running server.

Terminal — running the test
$ npm run test
src/__tests__/tasks.test.ts
POST /api/tasks creates a task (12ms)
GET /api/tasks returns created task (4ms)
2 tests passed
If a test fails, paste the error message back to Claude: “The test failed with this error: [paste]. Fix it.” Claude reads the error and corrects the code or the test.

What Just Happened

🧩

One feature per prompt

Each prompt asked for exactly one thing. Claude never had to guess scope.

📎

@filename kept edits precise

When updating page.tsx we used @src/app/page.tsx — Claude touched only that file.

Verified after each step

We checked localhost before moving on. No broken foundations to debug later.

📄

CLAUDE.md did the heavy lifting

Stack and conventions were stated once. Every prompt inherited them automatically.

6 prompts - 5 features - a working app with a passing test

This is the Claude Code build loop.

What's Next

You've built a real app end-to-end with Claude Code. The next lesson shows how to use Claude as your code reviewer — catching issues before they ship.