Your API Can Work Perfectly and Still Deliver a Bad Developer Experience
I thought I was testing an API. I was actually testing everything around it.
An API can work perfectly and still frustrate developers.
The server starts.
The endpoint responds.
The tests pass.
And somehow, someone still gets stuck.
I discovered this while building a small Express API for a Developer Experience experiment.
Instead of stopping when the API worked, I decided to use it like a developer discovering the project for the first time.
I didn't rely on what I knew about the project.
I followed what a new developer would actually see.
I read the README.
I ran the commands.
I copied the examples.
I tested successful requests.
I sent invalid requests.
I read the errors.
And I compared the documentation against the actual application.
That's when I started finding friction.
The API Was Simple
The API only had two endpoints:
```text id="a7u1m2"
GET /hello
POST /hello
A request to `GET /hello` returns:
```json id="h6p9r3"
{
"message": "Hello, Developer!"
}
The POST endpoint accepts:
```json id="v2k8q4"
{
"name": "Virginia"
}
and returns:
```json id="j5s1d8"
{
"message": "Hello, Virginia!"
}
For an invalid request, it returns:
```json id="n3x7b5"
{
"error": "Name is required"
}
It's a deliberately small API.
But that made it easier to focus on something I was becoming more interested in:
What does it actually feel like to use this API?
---
First Problem: The README Told Me to Run a Command That Didn't Exist
The README instructed developers to run:
```bash id="q4w6e2"
npm run dev
Except there was no dev script.
That meant the documented onboarding path failed immediately.
This was interesting because the documentation itself looked reasonable.
The problem only became obvious when I actually followed it.
I fixed it by adding:
```json id="r8t2k6"
{
"scripts": {
"dev": "tsx watch src/server.ts",
"test": "vitest"
}
}
Now the command in the README matched the actual project.
That gave me my first real lesson:
Documentation has to be tested, not just written.**
---
Second Problem: The API Worked, But the Example Didn't
The original request example used Unix-style `curl` syntax.
I was testing the project in Windows PowerShell.
The API wasn't broken.
The example was.
That's an easy problem to overlook when you're the person who built the API.
You already know how everything works.
A new developer doesn't.
So I replaced the example with a PowerShell-friendly request:
```powershell id="p1m4x7"
Invoke-RestMethod -Uri "http://localhost:3000/hello" -Method Post -ContentType "application/json" -Body '{"name":"Virginia"}'
This was a small change, but it made me think more carefully about developer environments.
A command isn't useful simply because it's technically correct.
The developer has to be able to copy it, run it, and understand what happens next.
Third Problem: The Happy Path Isn't Enough
Documentation often focuses on the successful request.
For example:
```json id="c8v3n1"
{
"name": "Virginia"
}
But what happens when the developer sends:
```json id="k2f7q9"
{}
The API returns:
```json id="d6r1s5"
{
"error": "Name is required"
}
That error is part of the developer experience.
It's not just an implementation detail.
It tells the developer:
* what went wrong
* which input matters
* what the API expects
This made me think about API documentation differently.
Sometimes the best documentation isn't another paragraph.
Sometimes it's a good error message.
---
Tests Became Documentation Insurance
After manually testing the API, I wanted a repeatable way to verify its behavior.
So I added automated tests using **Vitest** and **Supertest**.
The final suite covered:
* successful GET request
* successful POST request
* invalid `name`
* missing `name`
Result:
```text id="w5n8c2"
Test Files 1 passed
Tests 4 passed
This gave me confidence that the behavior I was documenting was behavior the application actually supported.
It also made me think about documentation drift.
An API can change.
The README might not.
An error message might change.
The documentation might not.
A response format might change.
The example might not.
Tests can help catch some of that gap.
So I started thinking of tests as a kind of:
documentation insurance
Not a replacement for documentation.
A safeguard against documentation silently becoming outdated.
Then I Added OpenAPI
Once the API behavior was stable, I documented it using OpenAPI.
The specification describes:
GET /helloPOST /hello- request body requirements
- successful responses
- validation errors
- operation IDs
- the local development server
I also validated the specification with Redocly.
This gave the project another useful layer:
machine-readable API documentation.
Instead of having the implementation and prose documentation as the only sources of information, the API also had a structured specification.
That matters when you're building developer-facing products.
Developers don't all consume documentation the same way.
Some want a QuickStart.
Some want endpoint references.
Some want examples.
Some want machine-readable API definitions.
Good developer documentation needs to support the journey, not just describe the technology.
Docs-as-Code Started Making More Sense
The project eventually included:
- QuickStart documentation
- API reference information
- troubleshooting guidance
- a DevEx audit
- automated tests
- OpenAPI documentation
- Redocly validation
- GitHub Actions CI
- a case study documenting the investigation
I also used Git branches and pull requests to make documentation changes.
That changed how I think about docs-as-code.
It isn't simply:
"Put Markdown in Git."
It's treating developer-facing information as part of the product development process.
Write it.
Review it.
Test it.
Validate it.
Maintain it.
Verify it.
The Developer Experience Loop
The biggest thing I took away from this project wasn't a particular tool.
It was a workflow.
I started thinking about Developer Experience like this:
```text id="s3j7p1"
Build
↓
Document
↓
Use
↓
Find friction
↓
Improve
↓
Test
↓
Verify
↓
Repeat
That changes the question.
Instead of asking:
"Did I write the documentation?"
I want to ask:
"Can a developer actually make progress with it?"
That is a much more useful question.
---
My New Definition of "Done"
Before calling API documentation finished, I'd now check:
Can a developer get started?
The setup instructions should work from a fresh environment.
Can they run the examples?
A developer shouldn't have to rewrite your commands before they work.
Do the examples match reality?
The documentation should reflect the actual API.
Are errors understandable?
Developers should have enough information to know what to fix.
Is there a path when something goes wrong?
Troubleshooting shouldn't begin and end with "check your setup."
Is important behavior tested?
Automated tests can help protect documented behavior.
Does the API specification match the implementation?
OpenAPI shouldn't become a second, outdated version of the API.
Has someone actually followed the documentation?
This might be the most important one.
Because documentation can look excellent until someone actually tries to use it.
---
The Shift
This project changed the question I ask about documentation.
Before:
"Is this documentation accurate?"**
Now:
"Does this documentation help the developer succeed?"**
Those aren't the same thing.
A page can be technically accurate and still be difficult to use.
A command can be correct and still fail in someone's shell.
An API can return the right status code and still provide an unhelpful error.
A README can contain everything a developer needs and still leave them thinking:
"Okay... what do I do now?"
That gap is where Developer Experience lives.
---
The Best Way to Find Developer Friction?
Become the developer.
Don't just review your README.
Follow it.
Don't just look at your example.
Run it.
Don't just document your errors.
Break the API.
Don't just say the tests pass.
Use them to verify the behavior you promise developers.
That shift in perspective uncovered more issues for me than simply reading through the code.
---
What This Small Project Taught Me
I started this project thinking I was building an API.
I ended up learning how much of the developer experience exists outside the API itself.
The code has to work.
The documentation has to work.
The examples have to work.
The errors have to help.
The tests have to verify.
And the journey has to make sense.
That's the standard I want to carry into the next things I build and document, especially around APIs, SDKs, AI integrations, and developer tools.
Because developers don't experience your codebase directly.
They experience the path you create around it.
And that path is part of the product.
Build it. Use it. Break it. Document it. Improve it. Verify it.
---
One question for the community
What's a small piece of developer friction you've encountered that had a much bigger impact than you expected?
I'd love to hear what you've seen.
Top comments (0)