Code Impact Training Program

From Vibe Coder
to Professional Developer

A hands-on curriculum that takes you from zero to deploying real apps using the exact tools, workflow, and standards we use at Code Impact every day.

~41
Total hours
7
Modules
1
Live app deployed
Coffee required

What you'll be able to do when you finish

⌨️

Navigate like a pro

Use the Terminal and VS Code fluently — no more hunting in Finder.

💿

Use Git without fear

Create branches, write commits, open PRs — and understand what you're doing.

🤖

Work with Claude Code

Run the Code Impact slash command workflow from start to deploy, the way the team does it.

🚀

Ship a real app

Build and deploy a full-stack TypeScript app on Railway using our stack — React + Node + Postgres.


The 7 Modules

Module 1

Terminal Basics

  • Mac filesystem & navigation
  • Files, folders, permissions
  • Running programs & scripts
  • Environment variables & .env files
  • Installing tools with Homebrew & npm
🕐 1 hour
Module 2

VS Code Setup

  • Installation & essential extensions
  • Keyboard shortcuts that matter
  • Integrated terminal
  • Explorer, search, source control panel
🕐 1 hour
Module 3

Git & GitHub

  • How version control works
  • Clone, commit, push, pull
  • Branches & merge strategy
  • Opening PRs & code reviews
  • Conventional commits
🕐 1 hour
Module 4

Claude Code & The Code Impact Workflow

  • CLAUDE.md — the project brain
  • Plan Mode before every build
  • Worktrees: the parallel dev system
  • Two VS Code windows rule
  • All 18 slash commands
  • Hard developer rules & daily rituals
  • CI/CD, /release-checklist, /client-handoff
  • Incident response (P1/P2/P3)
  • Impact OS & Cowork setup
🕐 2 hours
Module 5

The Code Impact Stack

  • TypeScript fundamentals
  • React components & hooks
  • Node.js + Express APIs
  • PostgreSQL + Prisma ORM
  • Docker: Dockerfiles, Compose, multi-service stacks
  • Dev vs production image patterns
🕐 2 hours
Module 6

Pipelines & Railway

  • GitHub Actions CI — writing your first pipeline YAML
  • Dev, staging, and production environments
  • Railway: connect repo, provision Postgres, deploy services
  • Disabling auto-deploy — deliberate releases only
  • Prisma in production: migrate deploy vs migrate dev
  • Reading logs and debugging failed deploys
🕐 4 hours
Module 7

Capstone: Build Orbit

  • Personal OS — Dashboard, Calendar, Tasks, Reminders, Profile
  • Product brief with AC — you design the schema, architecture, and issues
  • Claude API integration for AI daily briefing
  • GitHub Milestones + worktree workflow throughout
  • Deploy to Dev, verify AC, then deploy to Production
🕐 ~30 hours

💡
How to use this guide. Work through modules in order — each one builds on the last. Every lesson has a hands-on exercise. Mark checkboxes as you go; your progress is saved in this browser. When you finish all 7 modules you're ready for your first real project.
Module 1

Terminal Basics

The terminal is where professional developers live. It's faster, more powerful, and essential for running servers, managing Git, and using Claude Code. You'll use it every single day at Code Impact.

🕐 1 hour · 5 lessons
1

What Is the Terminal?

45 min

The Terminal lets you control your Mac by typing commands instead of clicking. Open it with Cmd + Space — type "Terminal". You'll see a prompt like this:

tsm@mac ~ %  # This is your prompt. ~ means your home directory.

Windows Terminal lets you control your PC by typing commands instead of clicking. Install it from the Microsoft Store (search "Windows Terminal") or it may already be installed. Open it, then launch PowerShell. You'll see a prompt like this:

PS C:\Users\you>  # PS = PowerShell. C:\Users\you is your home directory.

The most important commands for navigating:

CommandWhat it doesExample
pwdPrint working directory (where am I?)pwd/Users/youpwdC:\Users\you
lsList files in current folderls or ls -la
cdChange directorycd Documents
mkdirMake a new foldermkdir my-project
touchniCreate an empty filetouch index.htmlni index.html
rmRemove file (careful!)rm old-file.txt
cpCopy filecp a.txt b.txt
mvMove or renamemv a.txt new-name.txt
catPrint file contentscat README.md
open .Open current folder in Finderopen .
start .Open current folder in Explorerstart .
Tab autocomplete: Type the first few letters of a file or folder name, then press Tab. If there's only one match, it completes instantly. If there are multiple matches, press Tab twice to see all options. This works for file names, directory names, command names, and even branch names with git checkout. ↑ arrow: Pressing cycles through your recent commands. Press twice to go two commands back. Ctrl+R lets you search your history by typing part of a previous command.
Exercise 1.1

Navigate your machine

In Terminal, complete these steps:

2

Installing Developer Tools

90 min
📚
What are developer tools? Just like a carpenter needs a hammer and a saw, a developer needs specific programs installed before writing any code. This lesson installs the tools you'll use every day at Code Impact. Think of this as setting up your workbench — you only do it once.

Run these in order. Don't skip ahead — each step may depend on the previous one.

Step 1 — Xcode Command Line Tools

xcode-select --install

Step 2 — Homebrew (Mac package manager)

📚
What is Homebrew? Homebrew is like an app store for developers — but you install programs by typing a command instead of clicking. On a Mac there's no built-in way to install developer tools like Git or Node.js, so Homebrew fills that gap. Once Homebrew is installed, adding any tool is just brew install [name].
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Step 3 — Node.js (via nvm)

📚
What is Node.js? Node.js lets you run JavaScript on your computer — not just in a browser. At Code Impact, the entire backend is written in JavaScript/TypeScript and runs on Node.js. npm (Node Package Manager) is how you install JavaScript libraries. nvm (Node Version Manager) lets you switch between Node.js versions for different projects.
brew install nvm
# Add nvm to your shell (follow the output instructions)
nvm install 20
nvm use 20
nvm alias default 20
node --version  # should print v20.x.x
npm --version

Step 4 — Git

📚
What is Git? Git tracks every change you make to your code, lets you go back in time to any previous version, and lets multiple people work on the same codebase without overwriting each other's work. Think of it like "Track Changes" in Word — but for code, across an entire project, across a whole team.
brew install git
git --version
# Configure your identity
git config --global user.name "Your Name"
git config --global user.email "you@codeimpact.ai"

Step 5 — Claude Code

npm install -g @anthropic-ai/claude-code
claude --version

Step 6 — Docker Desktop

📚
What is Docker? Docker packages your entire app — code, dependencies, database, everything — into sealed containers that run identically on every machine. Without Docker, you'd often hear "it works on my computer but not yours." With Docker, that problem goes away. At Code Impact, every production app runs in Docker containers deployed to Railway.

Download Docker Desktop from docker.com and install it. Then verify:

docker --version
docker compose version

Step 1 — Windows Terminal

📚
What is Windows Terminal? Windows Terminal is Microsoft's modern terminal app — it runs PowerShell, Command Prompt, and WSL tabs in one window. It's the standard terminal for developers on Windows. Get it from the Microsoft Store (search "Windows Terminal") if it's not already installed. Always use PowerShell inside it.

Install from the Microsoft Store — search "Windows Terminal" and click Install. Then open it and select PowerShell as the default profile.

Step 2 — winget (Windows package manager)

📚
What is winget? winget is Windows' built-in package manager — the Windows equivalent of Homebrew on Mac. It's included in Windows 11. If you're on Windows 10, update the App Installer from the Microsoft Store to get it. Once available, winget install [name] installs any developer tool.
# Verify winget is available:
winget --version
# If not found, update "App Installer" in the Microsoft Store

Step 3 — Node.js (via nvm-windows)

📚
What is Node.js? Node.js lets you run JavaScript on your computer — not just in a browser. At Code Impact, the entire backend is written in JavaScript/TypeScript and runs on Node.js. npm is how you install JavaScript libraries. nvm-windows is the Windows version of nvm — it lets you switch Node.js versions between projects.
# Install nvm-windows via winget:
winget install CoreyButler.NVMforWindows
# Close and reopen Windows Terminal, then:
nvm install 20
nvm use 20
node --version  # should print v20.x.x
npm --version

Step 4 — Git

📚
What is Git? Git tracks every change you make to your code, lets you go back in time to any previous version, and lets multiple people work on the same codebase without overwriting each other's work. Think of it like "Track Changes" in Word — but for code, across an entire project, across a whole team.
winget install Git.Git
# Close and reopen Windows Terminal, then:
git --version
# Configure your identity
git config --global user.name "Your Name"
git config --global user.email "you@codeimpact.ai"

Step 5 — Claude Code

npm install -g @anthropic-ai/claude-code
claude --version

Step 6 — Docker Desktop

📚
What is Docker? Docker packages your entire app — code, dependencies, database, everything — into sealed containers that run identically on every machine. Without Docker, you'd often hear "it works on my computer but not yours." With Docker, that problem goes away. At Code Impact, every production app runs in Docker containers deployed to Railway. On Windows, Docker Desktop requires WSL 2 (Windows Subsystem for Linux) — the installer will prompt you to enable it if needed.

Download Docker Desktop from docker.com and install it. Then verify:

docker --version
docker compose version
⚠️
If any of these fail, stop and fix it before moving on. Give Claude the exact error message.
Exercise 1.2

Verify your setup

3

Environment Variables & .env Files

60 min
📚
What is an environment variable? An environment variable is a named value that your app reads from its surrounding environment at runtime — not from your code. This solves a critical problem: you need your app to connect to a database, but the database password is a secret that shouldn't be written in code (because code gets committed to GitHub and shared). The solution is to store secrets in a .env file on your local machine, and have the app read them from there. Every developer and every server has their own .env with their own values — the code never changes, only the environment does.

A Code Impact project has a frontend and a backend — and each has its own set of variables. Keep them in one .env file at the repo root, divided by commented sections so it's clear which vars belong where:

# ── BACKEND ──────────────────────────────
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=any-local-string
PORT=3000
NODE_ENV=development

# ── FRONTEND ─────────────────────────────
VITE_API_URL=http://localhost:3000
ℹ️
Frontend variables (Vite projects) must be prefixed with VITE_ — Vite only exposes vars with that prefix to the browser. Backend vars have no prefix requirement. Keeping them in clearly labelled sections avoids confusion about which service reads what.
🔐
.env files are NEVER committed to Git. They're listed in .gitignore. If you commit a secret, you must rotate it immediately. This is one of the most common beginner mistakes.

Every project has a .env.example that lists all required variables — both frontend and backend sections — without real values. When you clone a project, copy it to .env and fill in real values:

cp .env.example .env
# Now edit .env with your actual secrets
Exercise 1.3

Create and read a .env file

4

Running Programs & npm Scripts

75 min
📚
What is npm and what is package.json? npm (Node Package Manager) does two things: it installs libraries your project depends on, and it runs scripts defined for your project. The file package.json is like a recipe card at the root of every JavaScript project — it lists every library the project needs (dependencies) and every command you can run (scripts). When you clone a project and run npm install, npm reads package.json and downloads all the listed libraries into a folder called node_modules. The scripts section defines shortcuts like npm run dev so you don't have to remember the full command.

Every Code Impact project uses npm scripts to run common tasks. You'll see these patterns in every repo:

# Install all dependencies
npm install

# Start the development server
npm run dev

# Build for production
npm run build

# Run tests
npm run test

# Lint the code
npm run lint

# TypeScript type check
npm run typecheck

These are defined in package.json — always check that file when you join a new project.

ℹ️
At Code Impact, before marking any task done, you always run npm run lint && npm run typecheck && npm run test. The && means "run the next command only if the previous one passed." So if lint fails, typecheck and tests won't even run — this is intentional. Fix failures left to right. If lint fails, fix it before worrying about types. If types fail, fix those before running tests. In Module 4, Claude Code's /verify command runs all three automatically.
5

Terminal Power Habits

60 min
Shortcut / TrickWhat it does
Ctrl + CStop a running process (use this constantly)
Ctrl + LClear the terminal screen
↑ / ↓Navigate command history
TabAutocomplete file/folder names
Cmd + TOpen a new terminal tab
Ctrl + Shift + TOpen a new terminal tab (Windows Terminal)
cmd1 && cmd2Run cmd2 only if cmd1 succeeds
cmd1 | cmd2Pipe output of cmd1 into cmd2
grep "text" fileSearch for text in a file
historySee all past commands
which nodeFind where a program is installed
Exercise 1.5 — Module 1 Checkpoint

Terminal fluency check

Module 2

VS Code Setup

VS Code is the editor every Code Impact developer uses. Getting it set up right from day one makes Claude Code work better and prevents a whole class of bugs before they happen.

🕐 1 hour · 4 lessons
1

Installation & Essential Extensions

45 min
📚
What is VS Code? VS Code (Visual Studio Code) is a code editor — like Microsoft Word, but for writing code. It shows your files in a sidebar, highlights your code with color so it's easier to read, underlines mistakes in real-time, and has a built-in terminal so you don't need to switch windows. It's free, made by Microsoft, and used by the vast majority of professional JavaScript/TypeScript developers. At Code Impact, it's the only editor we use. What are extensions? Extensions are add-ons that teach VS Code about specific languages, tools, or frameworks. For example, the TypeScript extension gives VS Code real-time type error detection; the Tailwind extension autocompletes CSS class names.

Download VS Code at code.visualstudio.com. Once installed, open it and install these extensions (Cmd+Shift+X to open Extensions panel):

ExtensionWhy you need it
Claude CodeThe Code Impact AI workflow lives here — chat, slash commands, and Plan Mode all run inside VS Code via this extension
ESLintShows code errors as you type — required at Code Impact
Prettier — Code formatterAuto-formats your code on save
TypeScript + JavaScript (built in)Type checking and autocompletion
PrismaSyntax highlighting for .prisma schema files
GitLensShows git blame, history inline
DotENVSyntax highlighting for .env files
Error LensShows errors inline on the line that has the problem
Tailwind CSS IntelliSenseAutocomplete for Tailwind class names

Then add this to your VS Code settings.json (Cmd+Shift+P → "Open User Settings JSON"):

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.tabSize": 2,
  "editor.fontSize": 14,
  "js/ts.preferences.importModuleSpecifier": "relative",
  "files.trimTrailingWhitespace": true
}
2

The Layout — What Everything Is

30 min
Panel / ShortcutWhat it does
Explorer (Cmd+Shift+E)File tree — browse and open files
Search (Cmd+Shift+F)Search across all files in the project
Source Control (Cmd+Shift+G)Git — see changed files, stage, commit
Terminal (Ctrl+`)Integrated terminal — runs inside VS Code
Command Palette (Cmd+Shift+P)Run any command by typing its name
Quick Open (Cmd+P)Open any file by typing part of its name
Problems panel (Cmd+Shift+M)See all TypeScript / lint errors at once
💡
Always open projects with code . — not by double-clicking files in Finder. In Terminal, navigate to the project folder (cd ~/Documents/code-impact-training) then run code .. This opens the entire project so VS Code's file search (Cmd+Shift+F), the integrated terminal, and Claude Code all know which project they're working in. Opening individual files with File → Open gives you disconnected files with no project context — you lose autocomplete, go-to-definition, and project-wide search.
3

Keyboard Shortcuts That Matter

45 min
Shortcut (Mac)Action
Cmd+PQuick open file by name (use this instead of clicking the file tree)
Cmd+Shift+PCommand palette — run any action
Cmd+/Comment / uncomment selected lines
Alt+ClickAdd multiple cursors — edit many lines at once
Cmd+DSelect next occurrence of selected text
Cmd+Shift+LSelect ALL occurrences of selected text
Option+Up/DownMove a line up or down
Cmd+BToggle sidebar
Cmd+Shift+FSearch across entire project
F12Go to definition of a function/type
Shift+F12Find all references to a function/type
Cmd+Z / Cmd+Shift+ZUndo / Redo
Shortcut (Windows)Action
Ctrl+PQuick open file by name (use this instead of clicking the file tree)
Ctrl+Shift+PCommand palette — run any action
Ctrl+/Comment / uncomment selected lines
Alt+ClickAdd multiple cursors — edit many lines at once
Ctrl+DSelect next occurrence of selected text
Ctrl+Shift+LSelect ALL occurrences of selected text
Alt+Up/DownMove a line up or down
Ctrl+BToggle sidebar
Ctrl+Shift+FSearch across entire project
F12Go to definition of a function/type
Shift+F12Find all references to a function/type
Ctrl+Z / Ctrl+YUndo / Redo
Exercise 2.3

Open your project in VS Code

4

Understanding the File Tree & Project Structure

60 min

Every Code Impact project follows a consistent folder structure. Here's what you'll see:

my-project/
├── frontend/          # React app (Vite)
│   └── src/
│       ├── pages/     # Full-page components
│       ├── components/ # Reusable UI pieces
│       ├── hooks/     # Custom React hooks
│       ├── lib/       # Utility functions
│       └── types/     # TypeScript definitions
├── backend/           # Node.js/Express API
│   └── src/
│       ├── controllers/ # Route handlers (thin)
│       ├── services/  # Business logic (fat)
│       ├── routes/    # Express route definitions
│       ├── middleware/ # Auth, error handling
│       └── utils/     # Shared helpers
├── backend/prisma/    # Database schema + migrations
├── docs/              # Reference documentation
├── CLAUDE.md          # ★ Claude reads this every session
├── .env               # ★ NEVER committed to Git
├── .env.example       # ★ Always committed (no real values)
└── package.json       # Scripts and dependencies
ℹ️
CLAUDE.md at the root is the most important file in any Code Impact project. It tells Claude Code everything about the project — stack, commands, standards, architecture. Always read it when you join a project.
Module 3

Git & GitHub

Git is how you save your work, collaborate with teammates, and never lose code. At Code Impact, every feature lives in its own branch and nothing goes to production without a reviewed PR.

🕐 1 hour · 5 lessons
1

How Git Works (The Mental Model)

45 min
📚
What is Git? Imagine you're writing a novel and you want to save a version every time you finish a chapter, so you can always go back if you delete something important. That's Git — a version control system that saves snapshots of your code over time. Without it, developers would either overwrite each other's work or lose track of what changed and when. What is GitHub? GitHub is a website that stores Git repositories in the cloud. It's the backup, the collaboration layer, and the code review platform all in one. When you "push" to GitHub, you upload your local snapshots to the cloud. When a teammate "pulls," they download your changes. Every Code Impact project lives in a GitHub repo.

Think of Git as a timeline of snapshots. Every time you commit, you save a snapshot of your code. You can go back to any snapshot at any time. GitHub hosts your timeline in the cloud.

📁

Working directory

Your files right now — what you see in VS Code.

📋

Staging area

Files you've selected to include in the next commit. Like a cart before checkout.

📸

Commit

A saved snapshot. Every commit has a message describing what changed.

☁️

Remote (GitHub)

The backup in the cloud. Push to share; pull to get others' changes.

2

Core Git Commands

60 min
# --- Starting work ---
git clone https://github.com/org/repo.git  # Download a repo
git status                                  # See what's changed
git pull                                    # Get latest from remote

# --- Saving work ---
git add filename.ts                         # Stage a specific file
git add .                                   # Stage all changes (use carefully)
git commit -m "feat(auth): add login form"  # Commit with a message
git push                                    # Upload to GitHub

# --- Branches ---
git branch                                  # List all branches
git checkout -b feat/42-user-login          # Create + switch to new branch
git checkout main                           # Switch back to main
git merge feat/42-user-login                # Merge a branch in

# --- Inspecting ---
git log --oneline                           # See commit history
git diff                                    # See unstaged changes
git diff --staged                           # See staged changes
Exercise 3.2

Your first Git repo

3

Conventional Commits

30 min

At Code Impact, every commit follows this format. It keeps the history readable and makes automated changelogs possible.

type(scope): short description
feat(auth): add JWT refresh token rotation
fix(api): handle null user in getProfile
chore(deps): upgrade react to 19.1
docs(readme): add Railway deployment steps
test(auth): add coverage for login failure case
refactor(services): extract email to shared util
TypeWhen to use it
featA new feature visible to users or API consumers
fixA bug fix
choreMaintenance (deps, config, tooling)
docsDocumentation only
testAdding or fixing tests
refactorCode change that doesn't add features or fix bugs
perfPerformance improvement
Exercise 3.3

Write your first conventional commits

4

The Branch → PR Workflow

75 min
📚
What is a branch? A branch is a parallel copy of the codebase where you can make changes without affecting the main version. Think of main as the production codebase — the real app that's running and serving users. A branch is like a safe sandbox where you can experiment and build. When your work is ready and reviewed, you merge the branch back into main. Every feature, bug fix, and change at Code Impact lives on its own branch — you never work directly on main. What is a Pull Request (PR)? A PR is a formal request to merge your branch into main. It shows exactly what changed, triggers CI checks (automated tests), and is where your teammates review the code before it's approved. "Draft PR" means it's not ready for review yet — you open it early so CI starts running and teammates can see what you're working on.

This is the workflow for every single feature at Code Impact. Nothing goes directly to main.

Create a branchNamed after the GitHub issue: git checkout -b feat/42-user-login
Build the featureMake your changes. Commit regularly with conventional commit messages.
Test your changesRun unit tests and do live testing — spin up the app and manually verify the feature works end to end. Don't move on until both pass.
Open a PR and ensure all pipelines passPush your branch and open a PR. Github Pipelines will run lint, typecheck, tests, and build automatically. All checks must be green before the PR can move forward.
Mark ready for reviewOnce pipelines are green and your testing is complete, remove draft status and link the PR to the issue.
Get reviewed & squash mergeA teammate reviews the code. Once approved, squash merge into main — keeps the history clean and readable.
💡
Branch naming convention: type(scope): short description feat/[issue-number]-[short-title] for new features or fix/[issue-number]-[short-title] for bug fixes. Keep the title lowercase with hyphens, under 30 characters. Examples: feat/42-user-auth, fix/51-null-task-crash. If there's no GitHub issue yet, create one first — branches without issue numbers are nearly impossible to track weeks later. The issue number is what links the PR back to the original specification.

The commands in full:

# Start a new branch for your issue
git checkout -b feat/42-short-title

# Stage and commit as you work
git add .
git commit -m "feat(scope): description"

# Push to GitHub and open a PR
git push -u origin feat/42-short-title

# After the PR is merged — sync main
git checkout main
git pull
5

Setting Up GitHub & Connecting SSH

60 min
📚
What is SSH? SSH (Secure Shell) is a way to authenticate yourself to a remote server — in this case, GitHub — without typing a username and password every time. It works by generating a pair of keys: a private key that stays on your machine (never share this), and a public key that you give to GitHub. When you push code, GitHub checks that your private key matches the public key on file. It's more secure than passwords and much faster in daily use.

Create a GitHub account if you don't have one. Then set up SSH authentication so you don't have to type your password every time you push:

# Generate an SSH key
ssh-keygen -t ed25519 -C "you@codeimpact.ai"
# Press Enter for all prompts

# Copy the public key to your clipboard
cat ~/.ssh/id_ed25519.pub | pbcopyGet-Content ~/.ssh/id_ed25519.pub | Set-Clipboard
# Then go to GitHub → Settings → SSH and GPG keys → New SSH key
# Paste your key and save

# Test the connection
ssh -T git@github.com
Exercise 3.5 — Module 3 Checkpoint

Full GitHub flow

Module 4

Claude Code & The Code Impact Workflow

This is the module that separates Code Impact developers from everyone else. You'll learn the full feature lifecycle — from morning ritual to merged PR — using Claude Code's 18 slash commands, git worktrees for parallel development, automated CI gates, and the release and delivery process.

🕐 2 hours · 7 lessons
1

What Claude Code Is (and Isn't)

30 min

Claude Code is an agentic coding assistant that runs in your terminal. Unlike copy-pasting from a chat interface, Claude Code can read your files, run commands, edit code, and execute project scripts — all with your oversight. It is not autopilot. You remain the architect.

Claude Code can

Read files, write and edit code, run terminal commands, search the codebase, open PRs, run tests, follow project slash commands.

🤔

You always own

Architecture decisions, plan approval, code review, understanding what is being built and why. Never let Claude make a plan you can't explain.

# Install Claude Code globally (one-time):
npm install -g @anthropic-ai/claude-code

# Start Claude Code inside any project folder:
claude

# Run a one-shot question:
claude "what does this project do?"

# Run a slash command:
/feature-start 42
⚠️
Vibe coding anti-pattern: asking Claude to "just build the whole app." At Code Impact, you always review the plan before Claude executes, and you understand every meaningful change it makes. If you can't explain what Claude just did — stop and ask it to explain before moving on.
2

CLAUDE.md — The Brain of Every Project

60 min

CLAUDE.md sits at the root of every Code Impact project. Claude Code reads it automatically at the start of every session. It is the project's constitution — stack, commands, standards, architecture, known gotchas. Read it before writing a single line on any project.

# CLAUDE.md — code-impact-training (example)

## Project
- What: Training repo — full-stack app built through the Code Impact curriculum
- Stack: Node.js/TypeScript — React (Vite) frontend + Express API
- Database: PostgreSQL via Prisma (Railway)
- Deploy: Railway via Docker, CI/CD via GitHub Actions

## Commands
npm run dev        # Start dev server
npm run test       # Jest tests
npm run lint:fix   # ESLint auto-fix
npm run typecheck  # tsc --noEmit

## Code Standards
- No any types — use unknown and narrow
- Controller → Service → Prisma (never put business logic in controllers)
- Never console.log in production — use the structured logger

## Architecture
- Frontend: React + Vite + TypeScript + Tailwind, port 5173
- Backend: Express + TypeScript, port 3001

## Known Gotchas
- Run migrations before starting the backend: npx prisma migrate dev
- .env is never committed — copy .env.example and fill in real values
💡
CLAUDE.md is a living document — keep it current. Whenever you discover a gotcha that tripped you up, add it. When you change a run command (like switching from npm run dev to docker compose up), update it. When you add a new service or change the architecture, update the architecture section. A stale CLAUDE.md is worse than none — Claude will act on outdated information with confidence. A simple rule: if something surprised you while working on the project, add it to Known Gotchas so the next developer (or future you) doesn't hit the same wall.
💡
You'll write your own CLAUDE.md in the Capstone module when you have a real project to describe. For now, read through the template above and understand every section.
3

Plan Mode — Think Before You Build

45 min

The single most important habit in the Code Impact workflow: never start building until you have an approved plan. Plan Mode lets Claude analyze the codebase and propose an approach — without writing any code yet. You review and approve (or push back) before a single file is touched.

# Tell Claude to use Plan Mode before writing any code:
"Before writing anything, give me a plan for adding user authentication.
Use Plan Mode — list the files you'll touch, the order of operations,
and any risks. I'll approve before you start."

A good plan includes:

ElementExample
Problem being solved"Add JWT-based auth to protect API routes"
Files created or changedauth.service.ts, auth.middleware.ts, routes/auth.ts
Order of operations1. Schema → 2. Service → 3. Routes → 4. Middleware
Risks or dependencies"Token refresh requires Redis — must be running"
When to use Plan Mode: use it for any task with 3 or more steps, or any task that touches more than 2 files. For a simple one-line bug fix, skip it. To trigger it explicitly, say: "Before writing any code, give me a plan. List every file you'll touch, in what order, and why. Wait for my approval." If Claude's plan touches files you didn't expect, or proposes an approach that seems overcomplicated — ask it to explain or propose an alternative. Common red flags in a plan: "I'll refactor the entire auth system," adding a new dependency when one isn't needed, or a 10-step approach to a 2-step problem.
4

Worktrees — The Parallel Development System

75 min

Git worktrees are how Code Impact works on multiple features at the same time without branches stepping on each other. Each feature gets its own directory on disk — a full checkout of the repo, on its own branch, completely isolated.

The Directory Structure

Worktrees live as siblings to the main project directory — never inside it. /feature-start creates them automatically with a consistent naming convention.

~/Documents/
├── code-impact-training/              # ★ Main project (VS Code Window 1)
├── code-impact-training-feat-42/      # ★ Worktree for issue #42 (Window 2)
└── code-impact-training-feat-51/      # ★ Worktree for issue #51 (if needed)
💿
Each worktree is a full copy of the codebase checked out to its own branch. Changes in code-impact-training-feat-42/ are completely isolated from code-impact-training-feat-51/. Git handles the shared object store underneath — no duplication of history.

The Two VS Code Windows Rule

This is a hard rule, not a suggestion. Every worktree gets its own VS Code window.

WindowOpensTerminal runs
Window 1 (always open)Main project: code-impact-training/Git management, /parallel-status, PR reviews
Window 2 (per feature)Worktree: code-impact-training-feat-42/claude — this is where you build the feature
⚠️
Never drag a worktree folder into an existing VS Code window. Always open it as a fresh window (File → Open Folder or code ~/Documents/code-impact-training-feat-42). Mixing worktrees in one window causes Claude to work in the wrong context and Git to get confused.

Terminal State Checklist

Before writing a single line of code in a worktree, run these three checks. All three must pass.

# Check 1 — Are you in the right directory?
pwd
# Expected: /Users/you/Documents/code-impact-training-feat-42
# NOT: /Users/you/Documents/code-impact-training — wrong!

# Check 2 — Are you on the right branch?
git branch
# Expected: * feat/42-add-auth
# NOT: * main — wrong!

# Check 3 — Does .env exist?
ls .env
# Expected: .env
# If missing: cp ../code-impact-training/.env .env
💡
.env auto-copy: /feature-start automatically copies .env from the parent project into the worktree. If you ever create a worktree manually (rare), run cp ../code-impact-training/.env .env yourself. Never commit .env — it's in .gitignore.

/clean-worktree — After the PR Merges

After a PR is merged, the worktree is done. Run /clean-worktree from the main project window to remove it cleanly.

# In Window 1 (main project), after PR is merged:
/clean-worktree 42

# This command:
# 1. Removes the worktree from Git tracking (git worktree remove)
# 2. Deletes the directory on disk
# 3. Prunes the remote branch reference
⚠️
Never rm -rf a worktree directory manually. Git tracks worktrees internally — deleting the folder without telling Git leaves orphaned references that cause errors. Always use /clean-worktree.
⬇️
Install the slash commands before continuing. Download the zip, unzip it, and copy the files to ~/.claude/commands/. That's it — they'll be available in every Claude Code session immediately.
⬇ Download commands.zip cp -r ~/Downloads/commands/. ~/.claude/commands/
Exercise 4.4

Worktree mechanics — add CLAUDE.md

Create a real GitHub issue, work on it in a worktree, and merge it. The task: add a CLAUDE.md to the repo root.

5

The Full 18-Command Workflow

90 min

Every feature at Code Impact follows the same loop. Each step is a Claude Code slash command. These aren't suggestions — they're the workflow. Running them in order is what makes PRs mergeable and deployments predictable.

The Feature Loop (run in this order)

/feature-start [N]
Creates a sibling worktree directory, checks out a new branch feat/N-title, opens a draft PR immediately (so CI starts running), and copies .env. Open the new worktree in VS Code Window 2. All feature work happens here.
/build-feature [N]
Inside the worktree (Window 2): reads the GitHub issue, enters Plan Mode, waits for your plan approval, then builds using TDD. Claude writes tests first, then makes them pass. Calls /verify internally — lint + typecheck + tests run as part of this step, not separately.
/human-test [N]
Starts the full Docker stack (docker compose up) and prints a manual testing checklist derived from the issue's acceptance criteria. Work through every item. This catches what automated tests miss.
/security-review
Run if the feature touches auth, user data, payments, or external integrations. Audits for OWASP-aligned vulnerabilities. Skip for purely UI or config changes.
/performance-review
Run if the feature adds DB queries, list rendering, search, or new packages. Checks for N+1 queries, bundle size, and slow renders. Skip for trivial changes.
/create-pr [N]
Commits any review fixups, pushes the branch, writes the PR body, and marks the PR ready for review. Wait for CI to pass and a reviewer to approve, then squash merge.
/clean-worktree [N]
After the PR is merged: run from Window 1 (main project). Removes the worktree directory and prunes the branch. Keeps your project directory clean.

Each step hands off to the next. /build-feature is the only command that calls another command internally (/verify) because testing is part of the build, not a separate gate. Everything else is human-initiated.

All 18 Commands — Quick Reference

CommandWhen to use
/project-initOnce, on a new repo. Sets up ESLint, Prettier, commitlint, lint-staged, PR templates.
/prd-to-issuesGive it a PRD or brief. Breaks it into structured GitHub issues with acceptance criteria.
/parallel-statusEvery morning. Shows active worktrees and their PR status. Your daily starting point.
/feature-start [N]Starting a new feature. Creates worktree + branch + draft PR.
/build-feature [N]Building the feature. Plan → approve → TDD build.
/verifyCalled internally by /build-feature — not a manual step in the feature loop.
/security-reviewBefore any PR touching auth, payments, or user data.
/performance-reviewBefore any PR adding queries or complex UI.
/human-test [N]Manual QA. Starts Docker stack + checklist from ACs.
/create-pr [N]Finalizing PR. Commit fixups → push branch → write PR body → mark ready for review.
/clean-worktree [N]After PR merged. Removes worktree directory cleanly.
/fix-issue [N]Small bugs. Skips worktree for trivial fixes.
/hotfix [N]Production emergencies. Fast-path fix and deploy.
/update-depsDependency updates. Safe incremental upgrade with test verification.
/release-checklistBefore any production release. 11 checkpoints.
/client-handoffEnd of sprint. 5-step structured delivery to the client.
/incident-responseProduction incidents. P1/P2/P3 severity response workflow.
/update-focusEvery Monday. Updates your weekly context in Impact OS.
Exercise 4.5 — Module 4 Checkpoint

Parallel development — 2 worktrees, 3 windows

This is the real worktree workflow. You'll run two features simultaneously — one to scaffold the backend, one to scaffold the frontend — each in its own worktree and VS Code window, both running at the same time. This is how you'll work at Code Impact every day.

Step 1 — Create both issues first

Step 2 — Start both worktrees from Window 1

Step 3 — Build both features simultaneously

Step 4 — Verify and ship both

6

Developer Hard Rules & Daily Habits

45 min

These are the three rules the team never breaks. They exist because the times they were ignored cost hours of lost work. Memorize them.

The 3 Hard Rules

🚫

Rule 1 — Never work on main

Every piece of work goes on a feature branch created by /feature-start. Direct commits to main are blocked by branch protection. If you find yourself on main — stop and create a branch.

🚫

Rule 2 — Max 2 active worktrees

You can have at most 2 open worktrees at any time. Starting a third means you have too much in flight. Finish and merge something first. More open PRs = slower reviews for everyone.

🚫

Rule 3 — Reviews before new features

If 2 or more PRs are already waiting for review — yours or a teammate's — stop starting new features. Review the PRs first. Unreviewed PRs accumulate merge conflicts and block everyone.

The Morning Ritual — /parallel-status

Before touching any code, every morning starts with one command from the main project window:

# From Window 1 (main project), every morning:
/parallel-status

# Output shows:
# Active worktrees and which issue they're tracking
# PR status for each: draft / in review / approved / merged
# Any PRs from teammates awaiting your review
# Whether you're clear to start new work
⚠️
If /parallel-status shows 2+ PRs awaiting review, do not run /feature-start. Do the reviews first. This keeps the team unblocked and prevents a pile-up of merge conflicts.

Keeping Your Branch Current

# Every morning, in each active worktree (Window 2):
git fetch origin
git merge origin/main -m "Merging main into branch"

# This keeps your feature branch current.
# /create-pr does this automatically — but do it daily
# to catch conflicts while they're still small.

Day One Checklist

When you join Code Impact and get access to your first real project:

📋
Day One Setup — do these in order on your first real project
  1. Clone the project repo: git clone [repo-url]
  2. Install dependencies: npm install
  3. Read CLAUDE.md fully before anything else
  4. Copy .env.example to .env and fill in values the team provides
  5. Start the app locally and confirm it runs: docker compose up
  6. Run /parallel-status to see current work in flight
  7. Ask the team which GitHub issue to pick up first
Module 5

The Code Impact Stack

TypeScript, React, Node.js, PostgreSQL, and Prisma. You don't need to master these before the capstone — you need enough to read code, understand what Claude is building, and know where things live.

🕐 2 hours · 6 lessons
1

TypeScript Fundamentals

75 min
📚
What is TypeScript? JavaScript is the programming language of the web — browsers understand it, and Node.js runs it on the server. But JavaScript has a famous problem: you can pass the wrong kind of data to a function and nothing warns you until the app crashes for a user at 2am. TypeScript is JavaScript with an extra layer — you declare what type of data each variable and function expects, and the compiler checks that you're using things correctly before the code ever runs. At Code Impact, we use TypeScript everywhere because it catches a whole class of bugs during development, not in production. You write .ts files instead of .js files, and the TypeScript compiler (tsc) translates them to JavaScript that Node and browsers can run.

TypeScript is JavaScript with types. The types catch bugs before they happen. At Code Impact: strict mode, no any, use unknown when unsure.

// Types
type User = {
  id: string
  name: string
  email: string
  createdAt: Date
}

// Functions with typed params + return
function greet(user: User): string {
  return `Hello, ${user.name}`
}

// Arrays and optionals
type Task = {
  id: string
  title: string
  completed: boolean
  dueDate?: Date    // ? means optional
  tags: string[]   // array of strings
}

// Enums
type Status = 'todo' | 'in_progress' | 'done'

// Generics — a function that works with any type
function first<T>(arr: T[]): T | undefined {
  return arr[0]
}
🚫
Never use any. If you don't know the type, use unknown and narrow it. any silently disables all type checking — that's the opposite of why we use TypeScript.
Exercise 5.1

Write your first TypeScript types

🌿 Use the branching workflow from Module 3 — git checkout -b feat/[description], commit your changes, push, and open a PR before moving on.

2

React Components & Hooks

75 min
📚
What is React? React is a JavaScript library for building user interfaces. Instead of writing raw HTML that updates manually, you describe what the UI should look like for any given data — and React handles updating the page automatically when the data changes. A React app is made up of components: small, reusable pieces of UI (like a button, a task card, or a form). Each component manages its own state and renders itself. At Code Impact, the entire frontend of every project is a React app built with Vite (a fast build tool) and styled with Tailwind CSS.

React builds UIs from components. At Code Impact: functional components only, named exports, hooks over classes.

// A typed React component
type TaskCardProps = {
  task: Task
  onComplete: (id: string) => void
}

export function TaskCard({ task, onComplete }: TaskCardProps) {
  return (
    <div className="p-4 bg-white rounded-lg shadow">
      <h3>{task.title}</h3>
      <button onClick={() => onComplete(task.id)}>
        Mark done
      </button>
    </div>
  )
}

// useState — local component state
import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

// useEffect — side effects (fetching, subscriptions)
import { useEffect, useState } from 'react'

export function TaskList() {
  const [tasks, setTasks] = useState<Task[]>([])

  useEffect(() => {
    fetch('/api/v1/tasks')
      .then(r => r.json())
      .then(data => setTasks(data.data))
  }, []) // [] = run once on mount

  return <ul>{tasks.map(t => <li key={t.id}>{t.title}</li>)}</ul>
}
Exercise 5.2

Write your first React component

🌿 Use the branching workflow from Module 3 — git checkout -b feat/[description], commit your changes, push, and open a PR before moving on.

3

Node.js + Express APIs

60 min
📚
What is Node.js? Node.js is the runtime that lets JavaScript run on a server (not just in a browser). The Code Impact backend is a Node.js app: it listens for HTTP requests (from the browser), does things like read from the database or send emails, and sends back responses. What is Express? Express is the most popular Node.js web framework. It gives you a clean way to define API routes — you say "when someone sends a GET request to /api/v1/tasks, run this function." Without Express, you'd have to handle raw HTTP requests manually. Express is intentionally minimal, which is why Code Impact layers its own structure on top: the 3-layer Controller → Service → Prisma pattern.

The backend is a Node.js server built with Express. It follows a strict 3-layer pattern: Controller → Service → Prisma (database).

// routes/tasks.ts — just wires up endpoints
import { Router } from 'express'
import { TaskController } from '../controllers/taskController'

const router = Router()
router.get('/', TaskController.getAll)
router.post('/', TaskController.create)
router.patch('/:id', TaskController.update)
export { router }

// controllers/taskController.ts — thin, delegates to service
export const TaskController = {
  getAll: async (req, res) => {
    const tasks = await TaskService.getAll(req.user.id)
    res.json({ success: true, data: tasks })
  }
}

// services/taskService.ts — all the logic lives here
export const TaskService = {
  getAll: async (userId: string) => {
    return prisma.task.findMany({
      where: { userId },
      orderBy: { createdAt: 'desc' }
    })
  }
}
ℹ️
All API responses follow the same shape: {"{ success: true, data: T }"} or {"{ success: false, error: { code, message } }"}. Consistent shapes make the frontend code much simpler.
Exercise 5.3

Build your first API route end-to-end

🌿 Use the branching workflow from Module 3 — git checkout -b feat/[description], commit your changes, push, and open a PR before moving on.

Build a GET /api/v1/tasks route following the 3-layer pattern — Route → Controller → Service. No database yet — the service returns a hardcoded empty array. You'll wire this to Prisma in the capstone.

4

Unit Testing

45 min
📚
What is a unit test? A unit test verifies that a single function produces the correct output for a given input — in isolation, without touching a database, network, or filesystem. You write them in the same codebase as the code they test. Tests run automatically in CI and block a PR from merging if any fail. The goal isn't 100% coverage — it's confidence that your business logic works correctly before you ship it.

Code Impact uses Vitest for unit tests. It has the same API as Jest (describe, it, expect) and works natively with TypeScript. Install it in the backend:

npm install -D vitest

Add a test script to backend/package.json:

{
  "scripts": {
    "test": "vitest run"
  }
}

All tests live in a tests/ directory at the backend root — not scattered through the source tree. Create it once and all test files go there. Here's an example testing a pure helper function:

// src/services/taskService.ts
export function isOverdue(dueDate: Date): boolean {
  return dueDate < new Date()
}

// tests/taskService.test.ts
import { describe, it, expect } from 'vitest'
import { isOverdue } from '../src/services/taskService'

describe('isOverdue', () => {
  it('returns true when due date is in the past', () => {
    const past = new Date(Date.now() - 86400000)
    expect(isOverdue(past)).toBe(true)
  })
  it('returns false when due date is in the future', () => {
    const future = new Date(Date.now() + 86400000)
    expect(isOverdue(future)).toBe(false)
  })
})

Run tests from the backend directory:

npm run test
💡
Write unit tests for functions with logic — conditionals, calculations, data transformations, date handling. Skip tests for trivial wrappers that just call another function. In the capstone, every service-layer function needs a test. In Module 6, ci.yml runs npm run test on every push — a failing test blocks the PR.
Exercise 5.4

Write your first unit tests

🌿 Use the branching workflow from Module 3 — git checkout -b feat/[description], commit your changes, push, and open a PR before moving on.

5

PostgreSQL & Prisma ORM

75 min
📚
What is PostgreSQL? PostgreSQL is a relational database — it stores data in tables with rows and columns, like Excel but designed for apps with millions of rows and complex queries. Every Code Impact production app uses PostgreSQL, hosted on Railway. You don't interact with it directly in day-to-day work — Prisma handles that. What is an ORM? What is Prisma? An ORM (Object-Relational Mapper) is a library that lets you talk to a database using your programming language instead of raw SQL. Instead of writing SELECT * FROM tasks WHERE user_id = $1, you write prisma.task.findMany({ where: { userId } }). Prisma is the ORM Code Impact uses. You define your data model in a schema.prisma file, and Prisma generates fully-typed TypeScript functions for every database operation. If you change the schema, you run a migration — a versioned SQL script that updates the database structure.

Prisma is the database toolkit. You define your data model in schema.prisma, run a migration, and Prisma generates fully-typed database functions.

// prisma/schema.prisma — source of truth for data
model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String
  tasks     Task[]
  createdAt DateTime @default(now())
}

model Task {
  id        String   @id @default(cuid())
  title     String
  status    Status   @default(TODO)
  userId    String
  user      User     @relation(fields: [userId], references: [id])
  dueDate   DateTime?
  createdAt DateTime @default(now())
}

enum Status {
  TODO
  IN_PROGRESS
  DONE
}
# After changing the schema, always run:
npx prisma migrate dev --name "add-task-due-date"
npx prisma generate    # Regenerate TypeScript types
// Querying with Prisma — fully typed
const tasks = await prisma.task.findMany({
  where: { userId, status: 'TODO' },
  orderBy: { createdAt: 'desc' },
  take: 20   // limit results
})

const task = await prisma.task.create({
  data: { title, userId, status: 'TODO' }
})

await prisma.task.update({
  where: { id: taskId },
  data: { status: 'DONE' }
})
Exercise 5.5

Create the Prisma schema

🌿 Use the branching workflow from Module 3 — git checkout -b feat/[description], commit your changes, push, and open a PR before moving on.

6

Docker — The Full Picture

2 hours

Every Code Impact production project runs in Docker. Both the frontend and backend are containerized so they behave identically on your Mac, in CI, and on Railway. This lesson teaches you to write Dockerfiles from scratch and wire them together with Compose.

5a — What Docker Actually Does

Docker packages your app and everything it needs (Node version, system libraries, config) into an image. When you run the image, it becomes a container — an isolated process that behaves the same on every machine. Docker Compose orchestrates multiple containers together as a stack.

📦

Image

A snapshot: your code + the runtime + dependencies. Built once, run anywhere. Defined in a Dockerfile.

🏃

Container

A running image. Isolated — it can't see other containers' files unless you wire them together.

💼

Docker Compose

Orchestrates multiple containers (frontend, backend) as a single local stack. One command to start everything.

5b — Writing a Backend Dockerfile

The backend Dockerfile is a production build — it compiles TypeScript to JavaScript, then starts the compiled output. This is the same image used locally and on Railway. Pin the Node version — never use latest.

# backend/Dockerfile
FROM node:20-alpine

WORKDIR /app

# Copy package files first — Docker caches this layer
# if they haven't changed (faster rebuilds)
COPY package*.json ./
RUN npm ci

COPY . .

# Compile TypeScript → dist/
RUN npm run build

EXPOSE 3000

CMD ["node", "dist/server.js"]
🚫
Never use FROM node:latest — pin the version. Never put ENV MY_SECRET=xyz in a Dockerfile — secrets are injected at runtime via environment variables in Railway or your .env file.

5c — Writing a Frontend Dockerfile

The frontend Dockerfile follows the same pattern — install deps and run the Vite dev server.

# frontend/Dockerfile
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

EXPOSE 5173

CMD ["npm", "run", "dev"]

5d — Docker Compose: The Full Local Stack

Compose wires the frontend, backend, and a local PostgreSQL database together. The db service runs Postgres in a container — no installation required. The backend depends on it starting first.

# docker-compose.yml
services:

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=orbit
    ports:
      - "5432:5432"  # Exposed so you can run prisma migrate dev from your terminal
    volumes:
      - db_data:/var/lib/postgresql/data  # Persist data between restarts

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://postgres:password@db:5432/orbit  # Uses service name, not localhost
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      - db

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
      args:
        - VITE_API_URL=http://localhost:3000
    ports:
      - "5173:5173"
    depends_on:
      - backend

volumes:
  db_data:
🔗
Inside Docker Compose, containers talk to each other using the service name as the hostname — not localhost. That's why the backend's DATABASE_URL uses @db:5432 (the service name), and the frontend reaches the backend at http://backend:3000.
⚠️
Two DATABASE_URL values. The backend container uses @db:5432 (the compose service name). But when you run npx prisma migrate dev from your terminal, you're outside Docker — so your .env must use @localhost:5432:

DATABASE_URL=postgresql://postgres:password@localhost:5432/orbit

The port 5432 is exposed by the db service above, so your terminal can reach it while compose is running.

5e — Essential Docker Commands

CommandWhat it does
docker compose up --buildBuild images and start the full stack
docker compose up -dStart in detached mode (runs in background)
docker compose downStop and remove all containers
docker compose down -vAlso remove volumes (wipes Redis data etc.)
docker compose psSee what's running + health status
docker compose logs -f backendTail logs for a specific service
docker compose exec backend shOpen a shell inside the running backend container
docker compose build --no-cacheForce a clean rebuild (when deps change)
docker imagesList all images on your machine
docker system pruneClean up unused images and containers (frees disk)

5f — The .dockerignore File

Like .gitignore but for Docker builds. Always have one — it stops node_modules and secrets from being accidentally copied into images.

# .dockerignore (put in both frontend/ and backend/)
node_modules
dist
build
.env
.env.*
*.test.ts
*.spec.ts
coverage
.git
.DS_Store
⚠️
Without a .dockerignore, Docker copies your entire node_modules into the build context — this makes builds dramatically slower and can cause platform mismatches (Mac vs Linux binaries). Always include it.
Exercise 5.6 — Docker Checkpoint

Containerize your training repo

🌿 Use the branching workflow from Module 3 — git checkout -b feat/[description], commit your changes, push, and open a PR before moving on.

You already have frontend/ and backend/ directories in code-impact-training. Now add Docker to both and wire them together.

Module 6

Pipelines & Railway

Before you deploy a real app, you need to understand CI pipelines, environment management, and the deployment platform. This module gives you hands-on experience with all three so that Module 7's deploy phase is familiar, not new.

🕐 4 hours · 5 lessons
1

GitHub Actions — Your First CI Pipeline

45 min
📚
What is a GitHub Actions workflow? A workflow is a YAML file that lives in .github/workflows/ in your repo. GitHub reads it automatically. When you push code, GitHub spins up a fresh virtual machine, checks out your code, and runs every step you defined. If any step fails, the workflow fails — and if you've set up branch protection, the PR can't merge. This is how you catch lint errors, type errors, and broken tests before they ever hit main.

Here's a complete CI workflow for a monorepo with frontend and backend:

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: ['**']          # Run on every push to any branch
  pull_request:
    branches: [main]

jobs:
  backend:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: backend/package-lock.json
      - run: npm ci
        working-directory: backend
      - run: npm run lint
        working-directory: backend
      - run: npm run typecheck
        working-directory: backend
      - run: npm run test
        working-directory: backend
      - run: npm run build
        working-directory: backend

  frontend:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: frontend/package-lock.json
      - run: npm ci
        working-directory: frontend
      - run: npm run lint
        working-directory: frontend
      - run: npm run typecheck
        working-directory: frontend
      - run: npm run build
        working-directory: frontend
💡
Protecting main with required status checks: Go to your GitHub repo → Settings → Branches → Add branch protection rule for main. Enable "Require status checks to pass before merging" and select both the backend and frontend jobs. Now the merge button is greyed out until CI passes — the same gate Code Impact uses on every client project.
Exercise 6.1

Add CI to your training repo

🌿 Use the branching workflow from Module 3 — git checkout -b feat/[description], commit your changes, push, and open a PR before moving on.

2

Local, Dev, Staging, and Production Environments

60 min
📚
Why do environments exist? Every application runs in multiple environments — and each one has a different purpose. The mistake beginners make is treating "it works on my machine" as proof that something is ready. It isn't. Your machine has your local database, your test data, your secrets. A real deployment is a completely different machine with different data and different pressures. Environments let you test that difference before real users are affected.
EnvironmentWhere it runsPurposeDatabase
LocalYour laptopBuild and iterate fast. Verbose errors, hot reload, fake data. Nothing here is shared.Local Docker Postgres — wipe freely
DevelopmentRailway (dev project)Shared environment for the team. Test new changes and releases in an active deployed environment before they go to staging.Railway dev DB — can be reset, not real data
StagingRailway (staging project)User Acceptance Testing (UAT). Client reviews work here before it goes live. Mirrors production exactly.Railway staging DB — realistic but not real user data
ProductionRailway (production project)Live. Real users. Real data. Zero tolerance for unvetted changes.Production Railway DB — never wipe this

NODE_ENV — How Apps Change Behaviour

The NODE_ENV environment variable tells your app which mode it's running in. Apps use this to change behaviour automatically:

// In production: hide error details from users
if (process.env.NODE_ENV === 'production') {
  res.json({ error: 'Something went wrong' })
} else {
  res.json({ error: err.message, stack: err.stack })  // local only
}
Env varLocalDevelopmentStagingProduction
NODE_ENVdevelopmentproductionproductionproduction
DATABASE_URLLocal Docker DBRailway dev DBRailway staging DBRailway production DB
JWT_SECRETAny stringRandom secretDifferent random secretDifferent random secret
ANTHROPIC_API_KEYYour personal keyTeam keyTeam keyTeam key
VITE_API_URLhttp://localhost:3000Dev backend URLStaging backend URLProduction backend URL
⚠️
Never share secrets between environments. Each environment must have its own JWT_SECRET and its own database URL. If someone compromises dev or staging, they shouldn't automatically have production access.
Exercise 6.2

Update your training repo's .env.example

🌿 Use the branching workflow from Module 3 — git checkout -b feat/[description], commit your changes, push, and open a PR before moving on.

Your training repo has accumulated several env vars across modules. Make sure .env.example documents all of them properly.

3

Railway — Connect, Configure, Deploy

90 min
📚
What is Railway? Railway is a cloud hosting platform that runs your Docker containers on its servers 24/7. Think of it as the home for every deployed environment. Here's how it's structured:
  • Projects — the top-level container. You create one project per app — e.g. code-impact-training. Inside that project you have multiple environments.
  • Environments — Railway gives you Development, Staging, and Production environments inside every project. Each environment has its own services, its own database, and its own env vars — completely isolated from each other.
  • Services — what lives inside an environment. Your backend is one service, your frontend is another. Each service connects to a GitHub repo and builds from a Dockerfile.
  • Databases — Railway provisions a managed PostgreSQL database inside an environment with one click. It generates a DATABASE_URL automatically and can inject it directly into your services.
  • Variables — each service has its own Variables tab per environment. This is where secrets live at runtime — never in code or Dockerfiles. Vars set in one environment don't bleed into another.
  • Deployments & Logs — every deploy is versioned. Railway keeps build logs, deploy logs, and live runtime logs per service. You can roll back to any previous deploy in two clicks.

How to set up a project from scratch

You create the project once, then configure each environment inside it. Here's the setup flow:

StepWhat to do
1. Create accountGo to railway.app, sign up, connect your GitHub account
2. New projectNew Project → Empty Project. Name it code-impact-training
3. Select environmentRailway creates Development, Staging, and Production environments automatically. Start in Development.
4. Add databases+ New → Database → select the database type your app needs (e.g. PostgreSQL). Each one is provisioned for this environment only and generates its own connection URL
5. Add services+ New → GitHub Repo for each service your app has (e.g. backend, frontend). Each service connects to the same repo but can point to a different Dockerfile
6. Add railway.tomlTell Railway which Dockerfile to use and how to start the app (see below)
7. Disable auto-deployService → Settings → Source → turn off "Deploy on push." Deploys are deliberate, not automatic
8. Set env varsService → Variables → add all required vars. These are scoped to this environment only
9. Link databases to servicesDatabase service → Settings → Connected Services → link each service that needs it. Railway auto-injects the connection URL so you don't have to copy it manually
10. Manual deployService → Deployments → Deploy. Watch build logs. Green health check = live

railway.toml

Commit this to the repo root. It tells Railway which Dockerfile to build and how to start the server:

# railway.toml (at repo root)
[build]
builder = "dockerfile"
dockerfilePath = "backend/Dockerfile"

[deploy]
healthcheckPath = "/health"
healthcheckTimeout = 300
startCommand = "npx prisma migrate deploy && node dist/server.js"

Required environment variables

VariableWhere to get it
DATABASE_URLAuto-injected if you link the Postgres service — otherwise copy from Postgres → Connect tab
JWT_SECRETGenerate locally: openssl rand -hex 32
NODE_ENVproduction
PORT3000
⚠️
Never put secrets in your Dockerfile or commit them to GitHub. Railway's Variables tab is the only place secrets should live in a deployed environment.
Exercise 6.3

Set up your training repo on Railway

🌿 Use the branching workflow from Module 3 — git checkout -b feat/[description], commit your changes, push, and open a PR before moving on.

You're setting up the Development environment for your training repo. Staging and Production follow the exact same steps — you'll do those in Module 7.

4

Prisma in Production & Environment Management

45 min

migrate dev vs migrate deploy

These two commands do different things. Using the wrong one in production can destroy data.

CommandUse inWhat it does
npx prisma migrate devDevelopment onlyCreates migration files, applies them, regenerates the Prisma client. May reset the database in edge cases.
npx prisma migrate deployProduction & stagingApplies only pending migrations that already exist. Never resets. Never generates files. Safe for production.
⚠️
Never run prisma migrate dev against a production database. It can prompt to reset the database if it detects drift — wiping all your data. Always use prisma migrate deploy in any non-development environment.

The Railway deploy command in railway.toml runs migrations automatically before starting the server — so every deploy applies any pending migrations safely:

startCommand = "npx prisma migrate deploy && node dist/server.js"
# Migrations run first, then the server starts.
# If migrations fail, the server never starts — Railway marks the deploy failed.
# The previous deploy stays live. No broken state.
5

Reading Logs & Debugging Failed Deploys

30 min

A failed deploy is not an emergency — it's a normal part of shipping software. Railway keeps your previous successful deploy live while a new one is building. Nothing breaks for users. Your job is to read the logs, find the error, fix it, and redeploy.

Where to Find Logs

Log typeWhere in RailwayWhat it shows
Build logsService → Deployments → click a deploy → Build LogsDocker build output — npm install, compile, etc.
Deploy logsService → Deployments → click a deploy → Deploy LogsContainer startup — migrations, server start
Runtime logsService → Logs (live)Your app's console output while running

Common Failure Patterns

Error in logsCauseFix
Error: Cannot find module '...'Build didn't complete — missing deps or compile errorCheck build logs. Run npm run build locally first.
address already in use or port errorPORT env var not set or wrong valueSet PORT=3000 in Railway Variables
P1001: Can't reach databaseDATABASE_URL missing or wrongCheck Railway Variables. Re-link Postgres service.
Migration error on startupPending migration that failsCheck migration SQL. Run npx prisma migrate status locally with the prod DB URL.
Health check timeoutServer never started, or started on wrong portCheck deploy logs for startup errors.

Rolling Back a Deploy

If a bad deploy makes it through and breaks production, Railway keeps your deploy history. Roll back in two clicks:

Go to Service → DeploymentsFind the last known-good deploy (green health check).
Click the three-dot menu → RedeployRailway re-runs that exact build. Your app is back to the previous version in under a minute.
Fix the issue in a new branchNever push a "quick fix" directly to main under pressure. Create a branch, fix it properly, run CI, then redeploy.
Exercise 6.5 — Module 6 Checkpoint

Add a development deployment pipeline

🌿 Use the branching workflow from Module 3 — git checkout -b feat/[description], commit your changes, push, and open a PR before moving on.

Right now deploying means going into Railway and clicking Deploy. This exercise wires up a GitHub Actions workflow you can trigger manually from GitHub — same deliberate deploy, but from one place alongside your CI rather than jumping between tools.

The workflow uses workflow_dispatch — a manual trigger. It uses the Railway CLI with a RAILWAY_TOKEN secret. You generate the token in Railway and store it in GitHub — it never touches your code.

# .github/workflows/deploy-dev.yml
name: Deploy to Development

on:
  workflow_dispatch:   # Triggered manually from GitHub → Actions

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Railway CLI
        run: npm install -g @railway/cli
      - name: Deploy backend to Development
        run: railway up --environment development --service backend
        env:
          RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
      - name: Deploy frontend to Development
        run: railway up --environment development --service frontend
        env:
          RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
ℹ️
GitHub Secrets are encrypted variables stored in your repo settings (Settings → Secrets and variables → Actions). They're injected into workflows at runtime — never visible in logs or code. RAILWAY_TOKEN is how the workflow authenticates to Railway without hardcoding credentials.
Checkpoint

Pre-Capstone Review

Before starting Module 7, verify your training repo is in the right state. The capstone builds directly on top of what you've built — if anything is missing or broken, go back and fix it now rather than hitting a wall mid-build.

⚠️
Don't skip this. Every issue in the capstone that seems mysterious is usually something that was missing or broken here. Take 15 minutes to verify the full checklist before opening a single capstone issue.

Expected Directory Structure

This is what your repo should look like right now. Every file listed here was created in a specific exercise — if something is missing, the exercise reference tells you where to go back.

code-impact-training/
├── .git/
├── .github/
│   └── workflows/
│       ├── ci.yml                  # Exercise 6.1
│       └── deploy-dev.yml          # Exercise 6.5
├── .gitignore                      # Exercise 1.3
├── .env                            # Never committed — local only
├── .env.example                    # Exercise 6.2 — backend + frontend sections
├── CLAUDE.md                       # Exercise 4.4
├── docker-compose.yml              # Exercise 5.6
├── railway.toml                    # Exercise 6.3
├── README.md                       # Exercise 3.5
│
├── frontend/
│   ├── Dockerfile                  # Exercise 5.6
│   ├── .dockerignore               # Exercise 5.6
│   ├── package.json                # Exercise 4.5 — issue #4
│   ├── tsconfig.json               # Exercise 4.5
│   ├── vite.config.ts              # Exercise 4.5
│   ├── index.html                  # Exercise 4.5
│   └── src/
│       ├── main.tsx                # Exercise 4.5
│       ├── index.css               # Exercise 4.5
│       ├── types.ts                # Exercise 5.2 — Task type
│       └── App.tsx                 # Exercise 5.2
│
└── backend/
    ├── Dockerfile                  # Exercise 5.6
    ├── .dockerignore               # Exercise 5.6
    ├── package.json                # Exercise 4.5 — issue #3
    ├── tsconfig.json               # Exercise 4.5
    ├── .eslintrc                   # Exercise 4.5
    ├── prisma/
    │   ├── schema.prisma           # Exercise 5.5 — User, Task, Status
    │   └── migrations/             # Exercise 5.6 — created by prisma migrate dev
    ├── src/
    │   ├── server.ts               # Exercise 4.5 — GET /health
    │   ├── app.ts                  # Exercise 5.3 — router registration
    │   ├── types.ts                # Exercise 5.1
    │   ├── routes/
    │   │   └── tasks.ts            # Exercise 5.3
    │   ├── controllers/
    │   │   └── taskController.ts   # Exercise 5.3
    │   └── services/
    │       └── taskService.ts      # Exercise 5.3 & 5.4
    └── tests/
        └── taskService.test.ts     # Exercise 5.4

Verification Checklist

Work through each item. For anything that fails or is missing, the module reference tells you exactly where to go back.

Repo & Git

Backend

Frontend

Docker

CI & Railway

All checked? You're ready for the capstone. Everything in Module 7 extends what you just verified — the Prisma schema, the backend, the frontend, the Docker setup, and the Railway deployment. You won't be starting from scratch.
Module 7 — Capstone

Build & Deploy Orbit

You already know the tools. You've used the terminal, set up a GitHub repo, written Dockerfiles, defined a Prisma schema, and run the worktree workflow on real issues. Now you apply all of it to Orbit — a personal operating system with a calendar, tasks, reminders, and an AI-powered daily briefing. This is a real project brief, not a guided exercise. You own the architecture.

🕐 ~30 hours · full project
Your training repo is the starting point. You're not creating a new repo — you're continuing code-impact-training, the repo you've been building throughout the course. It already has:
  • A GitHub repo with SSH configured, git history, and your existing commits
  • frontend/ and backend/ directories with src/ structure
  • frontend/Dockerfile, backend/Dockerfile, docker-compose.yml, and both .dockerignore files
  • backend/prisma/schema.prisma with User and Task models already defined
  • .gitignore and a working .env
This is your starting point. The schema, the architecture, and the issue breakdown are yours to define.
🏗️
What you're building: Orbit
A personal OS with four integrated domains: a week-view calendar, a task manager, a reminders list, and a dashboard with an AI daily briefing powered by the Claude API. Stack: React (Vite + TypeScript + Tailwind) + Node.js/Express + PostgreSQL/Prisma + Railway.

Product Requirements

⚠️
No schema, no issue list, no architecture diagram. The requirements below describe what Orbit must do — not how to build it. You design the data model, decompose the work into GitHub issues, and make every architectural decision. That's the job.
ℹ️
Multi-user and data persistence are baseline requirements across all pages. Every piece of data — events, tasks, reminders, profile — belongs to the authenticated user who created it. Users cannot see or modify each other's data. All data persists across logout/login: signing out and back in returns the user to the exact same state they left.

Dashboard

  • Shows the current date prominently on load
  • Displays all events scheduled for today in chronological order
  • Displays tasks that are overdue or due today
  • Displays reminders due today that have not been marked done
  • A button triggers an AI-generated "your day ahead" summary via the Claude API, incorporating the day's events, tasks, and reminders — not auto-loaded on page visit
  • All sections load fresh on each page visit

Calendar

  • Supports month, week, and day views — user can switch between them
  • Events can be created with at minimum: title, start time, end time
  • Existing events can be viewed, edited, and deleted
  • Tasks that have a due date/time appear on the calendar on their due date
  • Reminders with a scheduled time appear on the calendar on their scheduled date
  • Week and day views handle multiple events in the same time slot without hiding any data

Tasks

  • Displays tasks in two sections: upcoming (incomplete) and completed
  • Tasks can be created with: title, optional notes, optional due date/time
  • Tasks can be marked complete, edited, and deleted
  • A task can be associated with a calendar event to indicate it is a precursor to that event
  • Tasks with no due date appear in a backlog section, separate from dated tasks

Reminders

  • Displays a list of active reminders with their scheduled times
  • Reminders can be created with: title, optional notes, reminder date/time, repeat frequency (none / daily / weekly)
  • Marking a reminder done with no repeat moves it to a done/archived state; with a repeat, the next occurrence is automatically scheduled
  • When a due reminder is detected while the user is active in the app, a browser notification fires — requires the user to have granted notification permission (Web Notifications API)
  • Reminders can be edited and deleted

Profile

  • Displays the user's name and email from signup
  • User can update their display name
  • User can upload and display a profile picture
  • Sign out clears the session and redirects to the login page

Technical Requirements

These are non-negotiable. Every item must be true before the app is considered complete.

RequirementStandard
StackReact + TypeScript + Tailwind (frontend) · Node.js + Express + TypeScript (backend) · PostgreSQL + Prisma (database) · Railway (deployment)
AuthJWT-based · bcrypt password hashing (min 10 salt rounds) · all non-public routes require a valid token · data scoped to the authenticated user
APIGET /health returns 200 { ok: true } · versioned routes (/api/v1/)
DatabasePrisma ORM · prisma migrate deploy in the Railway start command · no raw SQL
TestsUnit tests required for all service-layer functions (business logic, data transformations, repeat scheduling) · tests run as part of CI and must pass before any PR is merged
CIlint + typecheck + tests + build all pass on every push (ci.yml from Exercise 6.1)
SecretsNo secrets in code, Dockerfiles, or version control — environment variables only

Process Requirements

How you build matters as much as what you build. These standards match what Code Impact uses on every client project.

Write a PRD and run /prd-to-issuesWrite a short product requirements paragraph for Orbit, then run /prd-to-issues in Claude Code. Review what Claude generates, write acceptance criteria for every issue, and create them on GitHub. You decide the issue count and how to decompose the work — there is no prescribed list.
Set up GitHub MilestonesBefore creating issues, create milestones in GitHub to group related work. Every issue must belong to a milestone. You decide the milestone structure — it just has to exist and be used consistently.
Update CLAUDE.md for OrbitYour CLAUDE.md exists from Exercise 4.4. Update it before building any issues: add the Orbit stack, run commands, architecture pattern, and known gotchas. Use the branch workflow — branch, commit, PR, merge — same as any other change.
Branch per issue — no direct pushes to mainEvery issue gets its own branch and its own PR. You can use the worktree workflow (/feature-start) or standard Git (git checkout -b feat/N-short-title) — your choice. What's required: commit regularly, push your branch, open a PR, and merge to main via the PR. Get comfortable with git pull, git push, and resolving conflicts on your own.
Deploy to Development after every milestoneAt the end of each milestone, trigger a manual deploy to Development using the deploy-dev.yml workflow from Exercise 6.5. Test the features from that milestone in a real hosted environment — not Docker on your machine. Fix any issues before starting the next milestone. When the final milestone is complete and all AC passes in Development, you're ready for Production.
Deploy to ProductionRun /release-checklist first. Then set up the Production Railway environment and deploy. Share the Production URL with the team — that's your graduation checkpoint.

Two ways to work a branch

# Option A — Standard Git (recommended if you want to practice the basics)
git checkout -b feat/N-short-title   # Create and switch to your branch
# ... build the feature, commit as you go ...
git add .
git commit -m "feat(scope): description"
git push -u origin feat/N-short-title  # Push and open a PR on GitHub
# After merge:
git checkout main
git pull

# Option B — Worktree workflow (Claude Code slash commands)
/feature-start [N]              # Creates worktree + branch + draft PR + copies .env
code ../code-impact-training-feat-[N]
/build-feature [N]              # Plan → approve → build → /verify (called internally)
/human-test [N]                 # Docker stack + manual QA from acceptance criteria
/security-review                # If applicable: auth, user data, payments, integrations
/performance-review             # If applicable: DB queries, lists, search, packages
/create-pr [N]                  # Push → PR body → ready for review → squash merge
# After merge:
/clean-worktree [N]
💡
Either approach is fine. Option A is good practice for core Git habits — pulling, pushing, resolving conflicts. Option B is faster once you're comfortable. The non-negotiable either way: branch per issue, PR before merge, no direct pushes to main.

Extra Credit — Weather Integration

Optional — does not affect your graduation status. Complete it after all 5 pages pass AC in Production.

Add live weather to the dashboard and factor it into the AI briefing. Teaches one new skill: consuming a third-party REST API with an API key.

ℹ️
Sign up for a free OpenWeatherMap key at openweathermap.org/api. The free tier is generous enough for personal use. You'll add WEATHER_API_KEY and WEATHER_LOCATION to your Railway env vars and .env.example.
Create a GitHub issueTitle: "Extra credit: add weather to dashboard". AC: the dashboard displays current temperature and conditions, and the AI briefing mentions the weather.
Add a weather serviceA single function that calls the OpenWeatherMap current weather endpoint with your location and API key. Returns temperature and condition. Wire it into your dashboard data fetch.
Add a weather widget to the dashboardA small component that renders the temperature and condition. Factor the weather data into the Claude API prompt so the AI briefing includes it.
Set env vars in Railway ProductionAdd WEATHER_API_KEY and WEATHER_LOCATION in Railway → Production → backend service → Variables. Trigger a manual deploy.

🎓 Final Graduation Checklist

Your app is complete when all of these are true

Product — All 5 pages pass AC in Production

Deployment

Process

Concepts to Understand


🎉

Training Complete

Congratulations — you built and shipped a full-stack production app from scratch. Send the team your Railway URL and a note on what was hardest and what clicked.