# Echo's OpenAPI / Swagger: choosing between code-first (swag) and contract-first (oapi-codegen), and production operation

> A guide to operating OpenAPI / Swagger documentation with Go Echo (v5). It explains, with real code and no sugar-coating, choosing between code-first (swag annotations) and contract-first (generating types/server from a schema with oapi-codegen), serving Swagger UI, verifying documentation consistency in CI, and the state of tool support on Echo v5 (the oapi-codegen stable version supports v4; v5 is experimental).

- Published: 2026-06-28
- Author: 友田 陽大
- Tags: Go, Echo, OpenAPI, 型安全, アーキテクチャ設計, ドキュメント
- URL: https://tomodahinata.com/en/blog/go-echo-openapi-swagger-swag-oapi-codegen-documentation-guide
- Category: Go & Echo in production
- Pillar guide: https://tomodahinata.com/en/blog/go-echo-framework-production-guide

## Key points

- Don't let API docs rot by hand-writing. With code-first (swag) or schema-first (oapi-codegen), bundle code/spec/docs into a single source of truth.
- swag generates OpenAPI from Go comment annotations. Light to introduce and easy to bolt onto existing code. Annotation parsing works on v5 too.
- oapi-codegen generates Go types/server from an OpenAPI spec. The contract becomes the source of truth and stays consistent with multi-language clients. Contract-first is strong for long-term maintenance.
- An honest caveat: the oapi-codegen stable version (v2) supports Echo v4, and v5 support is at the experimental (V3) stage. Swagger UI serving middleware also mostly assumes v4. On v5, handle it with type generation + manual mount or the experimental version.
- Commit the generated artifacts and fail CI if regeneration produces a diff. This guarantees the spec, artifacts, and implementation always match at one point in Git.

---

"The API docs are out of sync with the implementation" — this is a chronic disease of every team that provides an API. Hand-written Markdown always rots, and the frontend or client companies implement believing the old docs, causing incidents. The solution isn't willpower but automation that **bundles code, spec, and docs into a single source of truth.**

This article is the API-documentation installment of the [Go Echo production-operations guide](/blog/go-echo-framework-production-guide). It designs choosing between the two approaches to operating OpenAPI on Echo v5 — **code-first (swag)** and **contract-first (oapi-codegen)** — and ensuring consistency in production. And it tells you, with no sugar-coating, **the state of tool support given that Echo v5 is new.**

> **Rules for this article**: Echo's API is based on the **official documentation (v5, as of June 2026).** **Important**: since Echo v5 is right after release (June 2026), **third-party support** for `oapi-codegen`, Swagger UI serving middleware, etc., is still catching up. After stating [that reality](#4-正直な現状echo-v5-とツールの対応状況), this article shows ways of writing that **work now** on v5. Always confirm each tool's latest support before introduction.

---

## 0. The two approaches: which "source of truth" do you choose?

| Aspect | **Code-first (swag)** | **Contract-first (oapi-codegen)** |
| --- | --- | --- |
| Source of truth | **Go code (annotations)** | **OpenAPI spec (YAML/JSON)** |
| Flow | code → annotations → spec generation | spec → type/server generation → implementation |
| Ease of intro | **Light (can bolt on)** | Somewhat heavy (write the spec first) |
| Type safety | Annotations and implementation are separate (can drift) | **Spec = type. The implementation follows the contract** |
| Multi-language clients | From the generated spec to each language | **Excellent fit since the spec is central** |
| Suited scenario | Bolt docs onto an existing API | Greenfield, multiple teams, contract-driven |

> **Decision guide**: if you **want to quickly add docs to an existing Echo API**, **swag.** If you're **greenfield and want to fix the contract first with the frontend and external clients**, **oapi-codegen (contract-first).** If you aim for **end-to-end type safety** including the frontend (Next.js, etc.), contract-first is the main play ([Next.js × Go × OpenAPI in practice](/blog/nextjs-go-openapi-end-to-end-type-safety)).

---

## 1. Code-first: generate OpenAPI from annotations with swag

`swag` parses handlers' **comment annotations** to generate an OpenAPI spec (`swagger.json` / `swagger.yaml`). Since annotation parsing just reads Go code comments, it **doesn't depend on the framework version** (it works on v5 too).

```go
// CreateUser godoc
// @Summary      ユーザーを作成
// @Description  メールとパスワードで新規ユーザーを登録する
// @Tags         users
// @Accept       json
// @Produce      json
// @Param        request body CreateUserDTO true "作成するユーザー"
// @Success      201 {object} UserResponse
// @Failure      400 {object} ErrorResponse
// @Failure      422 {object} ErrorResponse
// @Router       /api/v1/users [post]
func (h *UserHandler) Create(c *echo.Context) error {
	// ... 実装
}
```

Generation is one command. You can build it into CI.

```bash
# 注釈から docs/swagger.json / swagger.yaml を生成
swag init -g cmd/server/main.go -o ./docs
```

The DTO written in annotations can reference the same struct as your [binding & validation](/blog/go-echo-request-binding-validation-error-handling-guide). The **caveat** is that, since annotations and implementation are **separate**, changing the type in `@Param` doesn't make the implementation follow (it can drift). How to detect this in CI is covered in [Section 5](#5-ci整合性を機械で担保する).

---

## 2. Serving Swagger UI: on v5, "static serving" is the most reliable

Make the generated spec viewable from the browser with **Swagger UI.** There's dedicated middleware like `echo-swagger`, but those **depend on Echo's version** (at this point right after v5, most assume v4). The **version-independent, reliable method** is to **serve the spec JSON and Swagger UI as ordinary static routes.**

```go
// 生成した OpenAPI 仕様を普通のルートで配る（バージョン非依存）
e.GET("/openapi.json", func(c *echo.Context) error {
	return c.File("docs/swagger.json")
})

// Swagger UI / Redoc / Scalar 等の静的アセットを配信し、上の /openapi.json を指す
e.Static("/docs", "third_party/swagger-ui") // index.html が /openapi.json を読む
```

With this method, you can mount any viewer — Swagger UI, Redoc, or Scalar — **regardless of Echo's version.** The production practice is to place doc serving **inside authentication** or narrow the public scope (don't expose an internal API's spec defenselessly).

---

## 3. Contract-first: generate types from the spec with oapi-codegen

In contract-first, you **write the OpenAPI spec first** and generate Go types (and the server's foundation) from it. **The spec becomes the single source of truth**, and the implementation is forced to follow the contract.

```yaml
# openapi.yaml（真実源）
paths:
  /users:
    post:
      operationId: createUser
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateUserRequest' }
      responses:
        '201':
          content:
            application/json:
              schema: { $ref: '#/components/schemas/User' }
components:
  schemas:
    CreateUserRequest:
      type: object
      required: [name, email]
      properties:
        name:  { type: string, minLength: 2, maxLength: 50 }
        email: { type: string, format: email }
```

Generate **types** from this and you **don't have to hand-write** the request/response structs, and spec changes propagate to the implementation as type errors.

```bash
# 仕様から Go の型を生成（型だけなら最も移植性が高い）
oapi-codegen -generate types -package api -o api/types.gen.go openapi.yaml
```

Use the generated types in the Echo handler's [Bind/Validate](/blog/go-echo-request-binding-validation-error-handling-guide). "Change `minLength:2` in the spec, and the generated type and validation follow" — this is contract-first type safety.

---

## 4. The honest current state: Echo v5 and the state of tool support

Many tech articles muddy this, so I'll write it **with no sugar-coating.** Echo v5 was just released in June 2026, and since **the handler signature changed to `func(c *echo.Context) error`**, code-generation tools and middleware are **still catching up.**

| Tool | Echo v5 support (as of June 2026) | Recommended action |
| --- | --- | --- |
| **swag (annotation parsing)** | Since it parses comments, **it works** | Use as-is |
| **swag's UI serving (echo-swagger)** | Mostly assumes v4 | Substitute with [static serving](#2-swagger-ui-の配信v5-では静的配信が最も確実) |
| **oapi-codegen (type generation)** | `-generate types` is **spec→type, so it works** | Type generation usable |
| **oapi-codegen (Echo server generation)** | **The stable version (v2) assumes Echo v4.** v5 support is at the **experimental (V3)** stage (Go 1.25 required) | Type generation + manual mount, or carefully evaluate the experimental version |

**A realistic strategy** (v5, as of June 2026):

1. **Contract-first + type generation only**: generate **types only** with `oapi-codegen -generate types`, and **write the routing and handler wiring yourself** (wire it by hand to Echo v5's `e.POST(...)`). You get the contract's benefit (type safety) right now without waiting for the generated server's v5 support. **This is the most solid.**
2. **Code-first (swag)**: annotations work on v5. UI via static serving. Optimal for bolting on documentation.
3. **If you need the generated server**: wait until the stable version supports v5, or **evaluate the experimental version outside production.**

> This "**a new framework's core leads, and peripheral tools follow**" is a universal phenomenon in every tech migration. That's exactly why a design that **doesn't over-depend on tools (use type generation but don't be bound to server generation)** lowers transition-period risk. This is also a practice of ETC (ease of change).

---

## 5. CI: ensure consistency mechanically

The value of OpenAPI operation arises only when **the docs always match the implementation.** Enforce this not by discipline but mechanically with **CI.**

```yaml
# .github/workflows/ci.yml（抜粋）
- name: Regenerate & verify OpenAPI is in sync
  run: |
    swag init -g cmd/server/main.go -o ./docs   # コードファーストなら再生成
    # oapi-codegen の場合：oapi-codegen ... -o api/types.gen.go openapi.yaml
    git diff --exit-code docs/ api/             # 差分が出たら CI を落とす
```

**Commit the generated artifacts to the repository, and make CI "fail if regeneration produces a diff."** This guarantees the **spec, artifacts, and implementation always match at one point in Git.** The instant someone forgets to update an annotation or the spec, CI fails, so the docs don't rot. Furthermore, adding **breaking-change detection** of the OpenAPI spec (`oasdiff`, etc.) to CI lets you stop backward-incompatible changes at the PR.

This philosophy of "ensure consistency with CI, not human attention" is exactly the same as [quality gates for AI-driven development](/blog/ai-driven-development-quality-gates-ci-type-safety-test-security) and [end-to-end type safety](/blog/nextjs-go-openapi-end-to-end-type-safety) (commit the artifacts and fail on regeneration diff).

---

## Summary: the 7 principles of operating Echo's OpenAPI in production

1. **Don't let docs rot by hand-writing.** Auto-generate with code or spec as the source of truth.
2. **Bolt-on is swag (code-first)**, **greenfield/contract-driven is oapi-codegen (contract-first).**
3. **Swagger UI is most reliable via static serving** (version-independent).
4. **The honest v5 state**: swag annotations and oapi-codegen's **type generation work.** **Echo server generation's stable version assumes v4**, and v5 is at the experimental stage.
5. **In the transition period, type generation + manual mount** lowers tool dependence (ETC).
6. **Commit the artifacts and fail CI on a regeneration diff** (mechanical consistency assurance).
7. **Add breaking-change detection** to CI to protect the contract's backward compatibility.

API documentation isn't "something you write" but "**something where you build a mechanism that doesn't deviate from the implementation.**" On a new platform like Echo v5, the realistic answer of discerning the tools' support state and **taking only the benefit of type safety ahead of time** pays off. For the full picture, the [Go Echo production-operations guide](/blog/go-echo-framework-production-guide); for observability, [OpenTelemetry integration](/blog/go-echo-opentelemetry-distributed-tracing-metrics-observability-guide).
