Skip to content
Back to writing
2 min read

Designing APIs Developers Actually Enjoy

What makes an API feel right — and how to build one that developers reach for without hesitation.

Developer ExperienceAPI DesignTypeScript

Great APIs don't announce themselves. They simply work the way you expect them to.

Start with the call site

Before writing any implementation, write the code you wish existed:

const env = createEnv({
  server: { DATABASE_URL: z.string().url() },
  client: { NEXT_PUBLIC_APP_URL: z.url() },
});

If the call site feels awkward, the API is wrong — not the user.

Defaults are a feature

Every optional parameter is a decision you're making on behalf of the developer. Good defaults mean the common case requires zero configuration.

  • Sensible timeouts — don't make users guess
  • Clear error messages — tell them exactly what's wrong and how to fix it
  • Type inference — let TypeScript do the heavy lifting

Errors should teach

When something goes wrong, the error message is your best documentation:

Missing required environment variable: DATABASE_URL
Expected: string (URL format)
Received: undefined

Add DATABASE_URL to your .env file.

Compare that to Error: Invalid config. One teaches, one frustrates.

Composition over configuration

Prefer small, composable functions over monolithic configs. Developers should be able to use one piece without importing the whole library.

The invisible details

The best APIs feel invisible because every edge case has been considered:

  • What happens on the second call?
  • What if the input is empty?
  • Does it work in SSR?

These questions, answered well, are what separate good libraries from great ones.