Back to Blog

Software Documentation: What to Write, How to Structure It, and Tools That Help

Software documentation best practices in 2026 — API docs with OpenAPI, architecture decision records, README standards, docs-as-code with Docusaurus, and what d

Viprasol Tech Team
April 7, 2026
11 min read

Software Documentation: What to Write, How to Structure It, and Tools That Help

Software documentation is one of those investments that feels unproductive until the moment it isn't. Then — when a new engineer joins, when a client integration fails, when an incident happens at 2am — the absence of documentation costs far more than writing it would have.

The problem isn't usually that teams don't know they need documentation. It's that they don't know what to write, where to write it, or how to keep it from going stale. This guide addresses all three.


The Documentation Types That Actually Matter

Not all documentation has equal ROI. In priority order:

1. README (Highest ROI)

Every repository needs a README that answers the five questions any new team member asks in the first hour:

# Service Name

One sentence: what this service does.

## Prerequisites
- Node.js 20+
- PostgreSQL 16+
- Redis 7+

🌐 Looking for a Dev Team That Actually Delivers?

Most agencies sell you a project manager and assign juniors. Viprasol is different — senior engineers only, direct Slack access, and a 5.0★ Upwork record across 100+ projects.

  • React, Next.js, Node.js, TypeScript — production-grade stack
  • Fixed-price contracts — no surprise invoices
  • Full source code ownership from day one
  • 90-day post-launch support included

Quick Start

git clone https://github.com/org/service-name
cd service-name
cp .env.example .env        # Fill in required values
npm install
npm run db:migrate
npm run dev                  # http://localhost:3000

Environment Variables

VariableRequiredDescription
DATABASE_URLYesPostgreSQL connection string
REDIS_URLYesRedis connection string
JWT_SECRETYes≥32 character secret for JWT signing
STRIPE_SECRET_KEYNoRequired only for billing features

🚀 Senior Engineers. No Junior Handoffs. Ever.

You get the senior developer, not a project manager who relays your requirements to someone you never meet. Every Viprasol project has a senior lead from kickoff to launch.

  • MVPs in 4–8 weeks, full platforms in 3–5 months
  • Lighthouse 90+ performance scores standard
  • Works across US, UK, AU timezones
  • Free 30-min architecture review, no commitment

Architecture

Brief overview: what this service does, what it depends on, what depends on it. Link to full architecture doc if it exists.

Development

  • npm run dev — start with hot reload
  • npm run test — run test suite
  • npm run test:watch — watch mode
  • npm run lint — lint + typecheck
  • npm run db:migrate — run pending migrations

Deployment

See deployment guide or CI/CD pipeline runs on merge to main.


A README that answers these questions eliminates 80% of onboarding questions. Write it on day one, update it when things change.

### 2. API Documentation (OpenAPI)

For any HTTP API, OpenAPI (formerly Swagger) is the standard. Generate it from code — don't maintain it separately.

```typescript
// Using Fastify with @fastify/swagger — auto-generates from route schema
import Fastify from 'fastify';
import swagger from '@fastify/swagger';
import swaggerUi from '@fastify/swagger-ui';

const fastify = Fastify();

await fastify.register(swagger, {
  openapi: {
    info: {
      title: 'My API',
      description: 'API documentation',
      version: '1.0.0',
    },
    components: {
      securitySchemes: {
        bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
      },
    },
    security: [{ bearerAuth: [] }],
  },
});

await fastify.register(swaggerUi, {
  routePrefix: '/docs',
  uiConfig: { deepLinking: true },
});

// Route schema auto-populates the OpenAPI spec
fastify.post('/users', {
  schema: {
    summary: 'Create a user',
    description: 'Creates a new user account and sends a welcome email',
    tags: ['Users'],
    body: {
      type: 'object',
      required: ['email', 'password'],
      properties: {
        email: { type: 'string', format: 'email', description: 'User email address' },
        password: { type: 'string', minLength: 8, description: 'Password (min 8 chars)' },
        displayName: { type: 'string', maxLength: 100 },
      },
    },
    response: {
      201: {
        description: 'User created successfully',
        type: 'object',
        properties: {
          id: { type: 'string', format: 'uuid' },
          email: { type: 'string' },
          createdAt: { type: 'string', format: 'date-time' },
        },
      },
      400: { description: 'Validation error', $ref: '#/components/schemas/Error' },
      409: { description: 'Email already exists' },
    },
  },
}, createUserHandler);

The /docs route now serves a live Swagger UI with the complete API reference — always in sync with the actual implementation.

For Python (FastAPI):

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI(title="My API", description="API documentation", version="1.0.0")

class CreateUserRequest(BaseModel):
    email: EmailStr
    password: str
    display_name: str | None = None

class UserResponse(BaseModel):
    id: str
    email: str
    created_at: str

@app.post("/users", response_model=UserResponse, status_code=201,
          summary="Create a user",
          description="Creates a new user account and sends a welcome email")
async def create_user(request: CreateUserRequest) -> UserResponse:
    ...
# FastAPI auto-generates /docs (Swagger UI) and /redoc

3. Architecture Decision Records (ADRs)

ADRs capture why architectural decisions were made — the most valuable and most neglected documentation type.

Format:

# ADR-0023: Use PostgreSQL for all relational data

**Date:** 2025-11-12  
**Status:** Accepted  
**Decision makers:** Rahul (Eng Lead), Priya (Backend)

## Context

We're starting a new service that needs a relational database. 
MySQL, PostgreSQL, and SQLite were considered.

Decision

Use PostgreSQL as our primary relational database.

Rationale

  • JSON support (JSONB) handles semi-structured data without a separate NoSQL DB
  • Row-level security simplifies multi-tenant data isolation
  • TimescaleDB extension available if we need time-series
  • Strong ecosystem: Prisma, Drizzle, Knex, pgvector (for AI embeddings)
  • Team has more PostgreSQL experience than MySQL

Consequences

  • All engineers must know PostgreSQL basics
  • Deployment requires PostgreSQL (not SQLite in dev)
  • Connection pooling (PgBouncer or RDS Proxy) required for serverless

Alternatives Rejected

  • MySQL: weaker JSON support, fewer extensions
  • SQLite: not suitable for multi-process/multi-instance deployments

Store ADRs in `docs/decisions/adr-NNNN-title.md`. Never delete old ADRs — even superseded ones explain why you made choices that otherwise look baffling.

### 4. Runbooks

Runbooks document how to handle operational events — incidents, deployments, on-call procedures.

```markdown
# Runbook: High Database Connection Count

Trigger

Alert fires when db_connections_active > 80 for 5 minutes

Diagnosis

  1. Check what's holding connections:
    SELECT pid, usename, application_name, state, query_start, query
    FROM pg_stat_activity
    WHERE state != 'idle'
    ORDER BY query_start;
    
  2. Check connection pool metrics in Datadog: db.pool.active by service

Resolution

If connection leak: Find the service with highest connection count, restart it — it likely has a connection leak bug.

If traffic spike: Scale the connection pool (increase max_connections in PgBouncer config) or scale app servers down to reduce connections.

If long-running queries: Kill offending queries:

SELECT pg_terminate_backend(pid) 
FROM pg_stat_activity 
WHERE state = 'active' AND query_start < NOW() - INTERVAL '5 minutes';

Escalation

If unresolved in 30 minutes, page the on-call engineer and open a P1 incident.


---

Documentation Tools (2026)

ToolBest ForHosting
DocusaurusProduct docs, developer portalsSelf-hosted, Vercel
MintlifyAPI docs, beautiful out of the boxCloud
GitBookInternal wikis, team knowledgeCloud
NotionInternal docs, meeting notesCloud
ConfluenceEnterprise, Jira-integratedCloud/self-hosted
MkDocs + MaterialTechnical docs, Python projectsSelf-hosted
OpenAPI (Swagger/Redoc)API referenceEmbedded in app

Recommendation: Docusaurus for external developer docs, Notion for internal team knowledge, ADRs in git alongside code.


Docs-as-Code

Documentation lives in the same repository as code, reviewed in the same pull requests, versioned with the same git history.

repository/
├── src/
├── tests/
└── docs/
    ├── decisions/        ← ADRs
    │   ├── adr-0001-use-postgresql.md
    │   └── adr-0023-use-fastify.md
    ├── runbooks/         ← Operational procedures
    │   ├── high-database-connections.md
    │   └── deployment-rollback.md
    ├── architecture/     ← System design docs
    │   └── overview.md
    └── api/              ← Auto-generated from code

Enforce documentation in your PR checklist: "API documentation updated (if API changed)" and "README updated (if setup process changed)."


What Makes Documentation Go Stale (And How to Prevent It)

Problem: Documentation written once, updated never.

Solutions:

  1. Co-locate with code — docs next to code get updated when code changes
  2. Generate where possible — OpenAPI from code annotations never drifts
  3. Review docs in PR — make documentation a PR requirement, not an afterthought
  4. Date-stamp docs — readers know whether to trust a doc from 2019
  5. Document the "why," not just the "what" — "what" changes constantly; "why" changes rarely

Developer Portal

For APIs consumed externally (by customers or partners), a developer portal dramatically reduces integration support costs.

Components of a good developer portal:

  • Quickstart guide — get a working API call in < 10 minutes
  • API reference — complete endpoint documentation (generated from OpenAPI)
  • Authentication guide — exactly how to get and use API keys
  • Code examples — in the languages your customers actually use
  • Changelog — what changed and when
  • Status page — current availability and incident history
  • Sandbox environment — test without using production credentials

Build cost: $15,000–$40,000 for a custom developer portal; $200–$2,000/month for hosted options (ReadMe, Mintlify).


Working With Viprasol

We write, audit, and build documentation infrastructure for engineering teams — from API reference generation through full developer portals.

Documentation consultation →
API Development Services →
Software Development Services →


See Also


Share this article:

About the Author

V

Viprasol Tech Team

Custom Software Development Specialists

The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.

MT4/MT5 EA DevelopmentAI Agent SystemsSaaS DevelopmentAlgorithmic Trading

Need a Modern Web Application?

From landing pages to complex SaaS platforms — we build it all with Next.js and React.

Free consultation • No commitment • Response within 24 hours

Viprasol · Web Development

Need a custom web application built?

We build React and Next.js web applications with Lighthouse ≥90 scores, mobile-first design, and full source code ownership. Senior engineers only — from architecture through deployment.