I’ve onboarded 200+ developers over the years. The ones who succeed fastest? They’re not necessarily the best coders - they’re the ones who can navigate bad documentation and still get productive.

But here’s the thing: it doesn’t have to be this way. After watching countless talented developers struggle with terrible docs (and writing my fair share of bad ones), I’ve learned what actually works.

Why Most Dev Docs Fail

Let’s be honest - most documentation is written by the person who built the thing, for someone who already understands the thing. It’s the classic curse of knowledge, and it kills productivity.

I see the same patterns everywhere:

The “Just Look at the Code” Problem
“The API is self-documenting” - no, it’s not. Your variable names might make sense to you, but they don’t explain why you chose that particular approach or what happens when things go wrong.

Missing the Mental Model
Here’s a real example from a codebase I inherited:

1
2
3
# Bad documentation
npm run setup
npm start

That’s it. No explanation of what setup does, why you need it, or what start actually starts. Compare that to:

1
2
3
4
5
6
7
# Good documentation
# This downloads dependencies and creates your local database
npm run setup

# Starts the dev server on localhost:3000
# Hot reload is enabled - changes appear automatically
npm start

Same commands, but now I understand what’s happening and what to expect.

The “Happy Path Only” Trap
Most docs assume everything works perfectly. But in reality, developers spend 60% of their time debugging. Yet documentation rarely addresses the “what if” scenarios.

The Three Types of Documentation That Actually Work

After years of trial and error, I’ve found that effective developer documentation needs exactly three things:

1. Getting Started (The First 30 Minutes)

This is your make-or-break moment. A developer should go from zero to “I see something working” in under 30 minutes. Not production-ready - just proof that the system works.

Here’s what I include in every getting started guide:

Before You Start

  • Node.js 18+ (we use features from 18.12)
  • Docker Desktop (for the local database)
  • Git (obviously)

Quick Start

  1. Clone and install dependencies

    1
    2
    3
    
    git clone [repo-url]
    cd [project-name]
    npm install
    
  2. Start the local environment

    1
    2
    3
    4
    5
    
    # This creates a PostgreSQL container and runs migrations
    npm run dev:setup
    
    # Start the development server
    npm run dev
    
  3. Verify it’s working

    • Open http://localhost:3000
    • You should see “Welcome to [Project Name]”
    • Check http://localhost:3000/health - should return 200 OK

What You Just Built

You now have:

  • API server running on port 3000
  • PostgreSQL database with sample data
  • Hot reload enabled for development

Next: Common Development Tasks

Notice what this does:

  • Sets clear expectations (30 minutes, not production-ready)
  • Explains what each command actually does
  • Provides verification steps
  • Connects to what comes next

2. Reference Documentation (Daily Lookups)

This is where most teams go wrong. They either skip reference docs entirely or make them so verbose that finding anything takes forever.

Good reference docs are scannable and searchable. Here’s my format:

Here’s my reference doc format:

POST /api/users

Creates a new user account.

Request:

1
2
3
4
5
6
{
  "email": "user@example.com",
  "password": "string (min 8 chars)",
  "firstName": "string",
  "lastName": "string"
}

Success Response (201):

1
2
3
4
5
6
7
{
  "id": "uuid",
  "email": "user@example.com",
  "firstName": "John",
  "lastName": "Doe",
  "createdAt": "2025-07-31T18:00:00Z"
}

Error Responses:

  • 400 - Invalid email format or password too short
  • 409 - Email already exists
  • 500 - Server error

Notes:

  • Password is hashed with bcrypt before storage
  • Email verification sent automatically
  • User starts with ‘pending’ status until verified

The key is answering the questions developers actually have:

  • What format do I send?
  • What do I get back?
  • What can go wrong?
  • Any gotchas I should know about?

3. Troubleshooting (When Things Break)

This is the documentation that saves time and frustration. I organize it by symptoms, not by system components.

The examples below are for local development, but the same principles apply to production - you just need more elaborate monitoring, structured logging, and proper alerting systems. That’s a topic for another blog post entirely.

Here’s how I organize troubleshooting docs:

“Connection refused” on localhost:3000

Symptoms:

  • curl localhost:3000 returns connection refused
  • Browser shows “This site can’t be reached”

Likely causes:

  1. Dev server not running - Run npm run dev
  2. Port already in use - Kill the process: lsof -ti:3000 | xargs kill
  3. Environment variables missing - Copy .env.example to .env

Still broken? Check the server logs: npm run dev:logs

Database migrations fail

Symptoms:

  • npm run migrate returns error
  • App crashes with “relation does not exist”

Fix:

1
2
3
4
# Reset your local database
npm run db:reset
npm run migrate
npm run seed

If that doesn’t work: The database container might be corrupted. Reset everything:

1
2
docker-compose down -v
npm run dev:setup

Notice the pattern:

  • Start with symptoms (what the developer is experiencing)
  • List likely causes in order of probability
  • Provide specific commands to fix it
  • Include an escalation path

For production issues, you’d document things like “API returning 500 errors” with specific log patterns to look for:

1
2
3
ERROR [2025-07-31 18:23:45] Database connection timeout
  at Connection.connect (/app/db/connection.js:45)
  Pool exhausted. Active: 20, Idle: 0

But the documentation approach stays the same - symptoms first, then fixes. The key difference is that production troubleshooting requires good logging infrastructure and monitoring to surface these symptoms in the first place. Documenting how to read your logs and where to find them is just as important as documenting the fixes themselves.

Writing for the Frustrated Developer

The best documentation acknowledges that developers are usually frustrated when they’re reading it. They’re stuck, they’re under pressure, and they just want to get unstuck.

Use the Second Person
Write “you” not “the developer” or “one should.” It’s more direct and feels like you’re talking to them.

Lead with the Fix
Don’t bury the solution after three paragraphs of explanation. Give them the fix first, then explain why it works.

Admit When Things Are Weird
I have a section in one of my codebases called “Yeah, This Is Weird But…” where I explain architectural decisions that don’t make obvious sense. Developers appreciate the honesty.

For example, I have a section like this:

Yeah, This Is Weird But…

Why we have two different User models

You’ll notice UserProfile in the API and User in the database layer. This seems redundant, but here’s why:

The API model excludes sensitive fields (password hash, internal flags) and includes computed fields (full name, avatar URL). The database model is just raw data.

Yes, it means more code. But it prevents accidentally exposing sensitive data and makes the API cleaner.

Simple Tools and Workflows

You don’t need fancy documentation platforms. Some of my best documentation is just markdown files in the repo.

Documentation-Driven Development
Before I build a feature, I write the documentation for it. Not full docs - just the getting started section. It forces me to think about the developer experience early.

Test Your Docs Like Code
I have a simple script that runs through the getting started guide in a clean Docker container. When new developers join the team, I watch them go through the setup process - if they get stuck, I update the docs immediately.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/bash
# docs-test.sh
# Tests that getting started guide actually works

set -e

echo "Testing getting started guide..."

# Follow exact steps from docs
git clone $REPO_URL temp-test
cd temp-test
npm install
npm run dev:setup
npm run dev &

# Give server time to start
sleep 10

# Test the verification steps
curl -f http://localhost:3000/health || exit 1
curl -f http://localhost:3000 | grep -q "Welcome" || exit 1

echo "Getting started guide works!"

# Cleanup
kill %1
cd ..
rm -rf temp-test

Get Feedback from Actual Users
The only way to know if your documentation works is to watch someone use it. I do this during code reviews - when someone asks a question that should be answered in the docs, I fix the docs instead of just answering the question.

The Documentation That Gets Read

Good documentation isn’t about being comprehensive - it’s about being useful at the exact moment someone needs it.

The best compliment I ever got on documentation was from a junior developer who said: “I never had to ask anyone for help setting up the project.” That’s the goal.

Your documentation should make the person reading it feel smart, not stupid. It should acknowledge their frustration and give them a clear path forward. And it should assume they’re capable but missing context, not incapable of understanding.

Most importantly, it should be written by someone who remembers what it’s like to be confused.


What’s the worst documentation you’ve ever encountered? I’m always collecting examples of what not to do. Share your horror stories - misery loves company.