API Design Tools: OpenAPI, Swagger, and API-First Development
API Design Tools: OpenAPI, Swagger, and API-First Development
Most APIs are designed by accident. Someone writes a handler, returns some JSON, and the "API" is whatever the code happens to produce. API-first development flips this: you design the API contract first, then implement it. The tooling around OpenAPI makes this practical, not just aspirational.
OpenAPI Specification
OpenAPI (formerly Swagger) is the industry standard for describing REST APIs. It's a YAML or JSON document that defines your endpoints, request/response schemas, authentication, and more.
# openapi.yaml
openapi: 3.1.0
info:
title: User API
version: 1.0.0
description: User management endpoints
servers:
- url: https://api.example.com/v1
paths:
/users:
get:
summary: List users
operationId: listUsers
parameters:
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 100
- name: cursor
in: query
schema:
type: string
responses:
'200':
description: A list of users
content:
application/json:
schema:
type: object
properties:
users:
type: array
items:
$ref: '#/components/schemas/User'
nextCursor:
type: string
nullable: true
post:
summary: Create a user
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
User:
type: object
required: [id, name, email, createdAt]
properties:
id:
type: string
format: uuid
name:
type: string
email:
type: string
format: email
createdAt:
type: string
format: date-time
CreateUserRequest:
type: object
required: [name, email]
properties:
name:
type: string
minLength: 1
maxLength: 100
email:
type: string
format: email
Error:
type: object
required: [code, message]
properties:
code:
type: string
message:
type: string
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []
API Documentation Renderers
Swagger UI
Swagger UI renders an OpenAPI spec as an interactive documentation page where developers can try API calls directly.
# Docker
docker run -p 8080:8080 \
-e SWAGGER_JSON=/spec/openapi.yaml \
-v $(pwd):/spec \
swaggerapi/swagger-ui
# Or embed in your Express app
npm install swagger-ui-express
import swaggerUi from 'swagger-ui-express';
import spec from './openapi.json';
app.use('/docs', swaggerUi.serve, swaggerUi.setup(spec));
Strength: Interactive "Try it out" button lets developers test endpoints without leaving the docs.
Weakness: The default theme looks dated. Limited customization without forking.
Redocly
Redocly produces beautiful, three-panel API documentation (navigation, content, code samples).
npm install -g @redocly/cli
# Preview docs locally
redocly preview-docs openapi.yaml
# Build static HTML
redocly build-docs openapi.yaml -o docs/index.html
# Lint your OpenAPI spec
redocly lint openapi.yaml
Redocly's linting is the hidden gem — it catches common API design mistakes: missing descriptions, inconsistent naming, unused schemas, and security issues.
# redocly.yaml
extends:
- recommended
rules:
operation-description: warn
tag-description: warn
no-unused-components: error
operation-operationId: error
Scalar
Scalar is a modern, open-source API documentation renderer with a clean design and built-in API client.
npx @scalar/cli serve openapi.yaml
It generates documentation that looks modern out of the box — dark mode, collapsible sections, language-specific code examples, and an embedded API testing client. It's quickly becoming the default choice for new projects.
Code Generation
One of the biggest benefits of writing an OpenAPI spec is generating code from it.
Client Generation
# Generate TypeScript client
npx openapi-typescript-codegen \
--input openapi.yaml \
--output src/api-client \
--client fetch
# Or use openapi-typescript for just types
npx openapi-typescript openapi.yaml -o src/api-types.ts
openapi-typescript generates TypeScript types from your spec without any runtime dependencies. This is often the sweet spot — you get type safety without a generated client that feels foreign.
import type { paths } from './api-types';
type ListUsersResponse = paths['/users']['get']['responses']['200']['content']['application/json'];
// { users: User[]; nextCursor: string | null; }
Server Stub Generation
Less commonly, you can generate server stubs from the spec. This ensures your implementation matches the contract. Tools like openapi-generator support this, but in practice most teams find it easier to write the server code and validate against the spec.
API-First Workflow
- Design: Write the OpenAPI spec before any code. Use Redocly or Stoplight to edit visually.
- Review: PR the spec change. Reviewers discuss the API design, not implementation details.
- Mock: Use Prism or Stoplight to run a mock server from the spec. Frontend development starts immediately.
- Implement: Build the actual API. The spec is the contract.
- Validate: In tests or middleware, validate requests and responses against the spec.
- Document: The spec is the documentation — render it with Scalar or Redocly.
Mock Servers
# Prism (from Stoplight)
npm install -g @stoplight/prism-cli
prism mock openapi.yaml
# Now http://localhost:4010/users returns example data
Prism reads your OpenAPI spec and returns realistic example responses. Frontend developers can build against the mock while backend developers implement the real API.
Validation
zod-openapi
If you use Zod for runtime validation (increasingly common in TypeScript), zod-openapi generates OpenAPI schemas from your Zod schemas:
import { z } from 'zod';
import { extendZodWithOpenApi } from 'zod-openapi';
extendZodWithOpenApi(z);
const UserSchema = z.object({
id: z.string().uuid().openapi({ description: 'Unique user ID' }),
name: z.string().min(1).max(100),
email: z.string().email(),
createdAt: z.string().datetime(),
}).openapi('User');
This is "spec-from-code" rather than "code-from-spec," which some teams prefer because the validation logic and API docs are always in sync.
Recommendations
For new APIs: Start with the OpenAPI spec. Use Redocly CLI for linting. Render docs with Scalar. Generate TypeScript types with openapi-typescript.
For existing APIs without docs: Add openapi-typescript codegen to get types, then gradually build out the spec. Redocly's lint-as-you-go approach catches issues incrementally.
For internal APIs: The spec is valuable even if you never publish docs externally. Type-safe clients generated from the spec eliminate an entire category of integration bugs.