go-swagger examples

A curated collection of runnable examples for go-swagger β€” generating servers, clients and CLIs from an OpenAPI 2.0 (Swagger) spec.

Every example here is committed to the go-swagger/examples repository and kept in sync with the latest go-swagger release by automated regeneration.

Status

Fork me Actively maintained. Regenerated weekly against swagger@master.

Which site do I want?

These examples are all spec-first: you have an OpenAPI spec and want swagger generate to produce typed code. The sibling sites cover the other two approaches β€” pick by what you start from:

I start from…I want…Go here
an OpenAPI specgenerate a typed server / client / CLIthis site
Go interfaces, no codegenhand-wire an untyped client or servergo-openapi/runtime
Go codeproduce a spec from the code (code-first)go-openapi/codescan

New to go-swagger?

Install the toolchain and read the command reference on go-swagger’s own site:

go install github.com/go-swagger/go-swagger/cmd/swagger@latest

β†’ go-swagger.io for install, the generate command families, and project-layout reference. This site assumes you have swagger on your PATH and focuses on what to build with it.

Where to go next

  • Guides

    The example catalog, grouped by concern β€” servers, clients & CLI, authentication, streaming, and codegen customization. One page per example.

    β†’ guides

  • Tutorials

    Sequential, end-to-end walkthroughs. Start with the todo-list tutorial to build a server and client from scratch.

    β†’ tutorials

  • Project

    Repository README, licensing, contributing guidelines and how the examples stay in sync with go-swagger.

    β†’ project

Licensing

SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers

These examples ship under the Apache-2.0 license.

Contributing

Issues and pull requests welcome. See project/ for guidelines.


  • The example catalog, grouped by concern. Each page covers one runnable example: what it demonstrates, the spec excerpt, the generate command, how to run it, and the key generated files to look at.
  • Sequential, end-to-end walkthroughs. Unlike the guides β€” which are reference recipes you dip into β€” these build something from scratch, step by step. Start with the todo-list to generate both a server and a client.
  • About the go-swagger/examples repository β€” licensing, contributing, and how the examples are kept in sync with go-swagger.
Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of go-swagger examples

Guides

Browse by concern. Every guide maps to a directory in the go-swagger/examples repository, so you can clone it and run the code alongside the page.

  • Generating HTTP servers from a spec β€” from the canonical todo-list server to strict handlers, custom error handling, file upload/download and CRUD APIs.
  • Generating a typed SDK client (classic and stratoscale flavors) and a command-line client tool from the same spec.
  • Wiring security into a generated server β€” basic and API-key auth, composed security requirements, and a full OAuth2 access-code handshake.
  • Generating a server that streams newline-delimited JSON bodies, and a client that consumes the stream.
  • Going beyond the defaults β€” custom templates, external type bindings, generation flags, plugging in net/http middleware, and alias compatibility.
Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Guides

Servers

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Servers

Todo list server

The todo-list example is the canonical go-swagger server: a small CRUD API generated from a spec, wired to a trivial in-memory store. It’s the best place to see what swagger generate server produces and which files you’re expected to edit.

Prefer a step-by-step build? Start with the todo-list tutorial. This page is the reference tour of the finished example.

Tip

Source: todo-list/. Regenerate with go run ./hack/tools regen (or the //go:generate directive in restapi/configure_todo_list.go).

The spec

Two definitions drive everything: an item (with a required, minLength: 1 description and a read-only id) and a generic error.

item:
  type: object
  required:
    - description
  properties:
    id:
      type: integer
      format: int64
      readOnly: true
    description:
      type: string
      minLength: 1
    completed:
      type: boolean
error:
  type: object
  required:
    - message
  properties:
    code:
      type: integer
      format: int64
    message:
      type: string

Full source: todo-list/swagger.yml

The readOnly: true on id means the server assigns it β€” clients that send one have it ignored on create.

Generated models

Each definition becomes a Go struct. Note how the spec constraints map onto the type: description is required and minLength: 1, so it’s generated as a non-pointer-safe *string carrying validation, while the read-only id is a plain int64.

type Item struct {

	// completed
	Completed bool `json:"completed,omitempty"`

	// description
	// Required: true
	// Min Length: 1
	Description *string `json:"description"`

	// id
	// Read Only: true
	ID int64 `json:"id,omitempty"`
}

Full source: todo-list/models/item.go

The generator also emits the validation methods that enforce the spec’s constraints at runtime β€” here the description field’s required and minLength: 1 rules, wired straight from the spec. You never hand-write this:

func (m *Item) validateDescription(formats strfmt.Registry) error {

	if err := validate.Required("description", "body", m.Description); err != nil {
		return err
	}

	if err := validate.MinLength("description", "body", *m.Description, 1); err != nil {
		return err
	}

	return nil
}

Full source: todo-list/models/item.go

Wiring the handlers

restapi/configure_todo_list.go is the one file you edit β€” it’s marked safe to edit and survives regeneration. The generated scaffold leaves each handler returning 501 Not Implemented; you replace those with real logic. Here’s the implemented version from the tutorial’s server-complete, backing the store with a map:

api.TodosAddOneHandler = todos.AddOneHandlerFunc(func(params todos.AddOneParams) middleware.Responder {
	if err := addItem(params.Body); err != nil {
		return todos.NewAddOneDefault(500).WithPayload(&models.Error{Code: 500, Message: conv.Pointer(err.Error())})
	}
	return todos.NewAddOneCreated().WithPayload(params.Body)
})
api.TodosDestroyOneHandler = todos.DestroyOneHandlerFunc(func(params todos.DestroyOneParams) middleware.Responder {
	if err := deleteItem(params.ID); err != nil {
		return todos.NewDestroyOneDefault(500).WithPayload(&models.Error{Code: 500, Message: conv.Pointer(err.Error())})
	}
	return todos.NewDestroyOneNoContent()
})

Full source: tutorials/todo-list/server-complete/restapi/configure_todo_list.go

Each response constructor (NewAddOneCreated, NewDestroyOneNoContent, …) is generated from a response you declared in the spec, so the compiler keeps your handlers honest against the contract.

Running it

The generated server listens on a Unix socket, HTTP and HTTPS by default. For a quick local test, enable just the HTTP listener on a fixed port:

$ go run ./todo-list/cmd/todo-list-server --scheme=http --port=8765
serving todo list at http://127.0.0.1:8765
$ curl -i localhost:8765 \
    -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json' \
    -d '{"description":"go shopping"}'
HTTP/1.1 201 Created
...
{"description":"go shopping","id":1}

See the todo-list README for the full listener matrix (unix/http/https) and TLS options.

Variations

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Strict server

The strict server is generated with --strict-responders. Instead of every handler returning a generic middleware.Responder β€” which lets you hand back any response, including ones the spec never declared β€” each operation gets its own responder interface. The compiler then enforces that a handler can only return responses declared for that operation.

Tip

Source: todo-list-strict/. Generated with swagger generate server -A todo-list -f ./swagger.yml --strict-responders --regenerate-configureapi.

The generated responder interface

For each operation, the generator emits a marker interface embedding middleware.Responder. Only the response types declared for addOne implement AddOneResponder, so nothing else can be returned:

type AddOneResponder interface {
	middleware.Responder
	AddOneResponder()
}

Full source: todo-list-strict/restapi/operations/todos/add_one_responses.go

Every generated response for the operation β€” AddOneCreated, AddOneDefault, AddOneNotImplemented β€” carries a no-op AddOneResponder() method, which is what admits it to the interface. A FindDefault value, for instance, simply won’t compile inside an addOne handler.

The handler signature

Compare with the default todo-list server, where the handler returns middleware.Responder. Here the return type is the operation-specific todos.AddOneResponder:

if api.TodosAddOneHandler == nil {
	api.TodosAddOneHandler = todos.AddOneHandlerFunc(func(params todos.AddOneParams, principal any) todos.AddOneResponder {
		_ = params
		_ = principal

		return todos.AddOneNotImplemented()
	})
}

Full source: todo-list-strict/restapi/configure_simple_to_do_list_api.go

The scaffold guards each assignment with if … == nil, so you can wire a handler from elsewhere and leave the rest returning NotImplemented. You replace the body with real logic, returning one of the operation’s typed responders.

When to use it

Reach for strict responders when you want the type system to guarantee your handlers stay in sync with the contract β€” you can’t accidentally return a response shape the spec doesn’t describe. The cost is a little more generated surface (one interface per operation) and slightly more verbose returns.

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Custom error handling

By default a generated handler returns a middleware.Responder and you build error responses yourself (NewAddOneDefault(500).WithPayload(...)). The todo-list-errors example shows the alternative: generate with --return-errors so handlers may return a plain error, and install a single custom error handler that shapes every error response in one place.

Tip

Source: todo-list-errors/. Generated with swagger generate server -A TodoList -f ./swagger.yml --return-errors (the //go:generate line in configure_todo_list.go uses the long-form flags).

Handlers that return an error

With --return-errors, the handler signature becomes func(params) (middleware.Responder, error). A handler can now short-circuit by returning an error instead of constructing a response:

api.TodosAddOneHandler = todos.AddOneHandlerFunc(
	func(params todos.AddOneParams) (middleware.Responder, error) {
		_ = params

		return nil, errAlreadyExists
	})

Full source: todo-list-errors/restapi/configure_todo_list.go

Here errAlreadyExists is a sentinel the handler returns directly β€” no response plumbing at the call site.

The centralized error handler

Returned errors flow through api.ServeError, which you can override. This example wires it to a catcher that recognizes the sentinel (with errors.Is), logs it, then defers to the runtime’s default ServeError for the actual HTTP response:

func catcher(w http.ResponseWriter, r *http.Request, err error) {
	if errors.Is(err, errAlreadyExists) {
		slog.Info("we catch custom error! congratulations!")
	}

	oapierrors.ServeError(w, r, err)
}

var errAlreadyExists = errors.New("already exists")

Full source: todo-list-errors/restapi/configure_todo_list.go

Installing it is one line in configureAPI:

api.ServeError = catcher

Because every operation’s error funnels through the same hook, you get one place to classify errors, attach correlation IDs, translate domain errors to status codes, or emit metrics β€” without repeating that logic in each handler.

When to use it

Use returned errors when your handlers naturally surface Go error values (a data layer, a validation step) and you’d rather map them to responses centrally than build a *Default responder at every return. Stick with the default responder style when each handler already knows the exact response it wants.

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Auto-configure

Normally you edit configure_*.go by hand to attach each handler. The auto-configure example takes a different route: generate with --implementation-package, point it at a package you write, and the generator emits the wiring for you. Every operation is routed to a method on your implementation β€” no hand-editing of a configure file.

Tip

Source: auto-configure/. Generated with swagger generate server --name AToDoListApplication --spec ./swagger.yml --implementation-package github.com/go-swagger/examples/auto-configure/implementation --principal any.

The generated contract

Instead of a configure_*.go, the generator produces auto_configure_*.go. It declares the Handler interface your package must satisfy, and binds a package-level Impl to your constructor via implementation.New():

var Impl Handler = implementation.New()

// Handler handles all api server backend configurations and requests
type Handler interface {
	Authable
	Configurable
	TodosHandler
}

// Configurable handles all server configurations
type Configurable interface {
	ConfigureFlags(api *operations.AToDoListApplicationAPI)
	ConfigureTLS(tlsConfig *tls.Config)
	ConfigureServer(s *http.Server, scheme, addr string)
	CustomConfigure(api *operations.AToDoListApplicationAPI)
	SetupMiddlewares(handler http.Handler) http.Handler
	SetupGlobalMiddleware(handler http.Handler) http.Handler
}

// Authable handles server authentication
type Authable interface {
	// Applies when the "x-todolist-token" header is set
	KeyAuth(token string) (any, error)
}

// TodosHandler
type TodosHandler interface {
	AddOne(params todos.AddOneParams, principal any) middleware.Responder
	DestroyOne(params todos.DestroyOneParams, principal any) middleware.Responder
	FindTodos(params todos.FindTodosParams, principal any) middleware.Responder
	UpdateOne(params todos.UpdateOneParams, principal any) middleware.Responder
}

Full source: auto-configure/restapi/auto_configure_a_to_do_list_application.go

Each generated handler then simply delegates to that Impl:

	api.TodosAddOneHandler = todos.AddOneHandlerFunc(func(params todos.AddOneParams, principal any) middleware.Responder {

		return Impl.AddOne(params, principal)
	})

Full source: auto-configure/restapi/auto_configure_a_to_do_list_application.go

Your implementation package

You write a package that implements Handler. This is ordinary hand-written code β€” nothing generated β€” so it’s the natural home for your business logic. Here’s the AddOne method backing the in-memory store:

func (i *TodosHandlerImpl) AddOne(params todos.AddOneParams, principal any) middleware.Responder {
	_ = principal

	i.lock.Lock()
	defer i.lock.Unlock()
	newItem := params.Body
	if newItem == nil {
		return todos.NewAddOneDefault(http.StatusBadRequest).
			WithPayload(&models.Error{
				Code:    http.StatusBadRequest,
				Message: &[]string{"Item Body is nil"}[0],
			})
	}
	// assign new id
	newItem.ID = i.idx
	i.idx++

	i.items[newItem.ID] = newItem
	return todos.NewAddOneCreated().WithPayload(newItem)
}

Full source: auto-configure/implementation/todos_impl.go

The example splits the interface across small types β€” TodosHandlerImpl, ConfigureImpl, AuthImpl β€” composed by a single HandlerImpl that New() returns. That keeps handlers, server configuration, and authentication in separate files while still satisfying the one generated Handler interface.

When to use it

Auto-configure shines when you regenerate often and don’t want a hand-edited configure_*.go in the loop: your implementation lives entirely in a package you own, and regeneration only ever touches the generated wiring. It’s also a clean way to keep the transport-facing glue separate from your domain code.

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

File server

The file-server example demonstrates a file-upload endpoint: how the spec’s type: file maps onto the generated server and client, and how the runtime surfaces the uploaded file to your handler.

Tip

Source: file-server/. Build the server under restapi/cmd/file-upload-server, then run the client with go run upload_file.go swagger.yml.

The spec

An upload is a multipart/form-data operation with a formData parameter of type: file:

/upload:
  post:
    tags:
    - uploads
    summary: uploads
    operationId: uploadFile
    consumes:
    - multipart/form-data
    parameters:
    - name: file
      in: formData
      type: file
      required: true

Full source: file-server/swagger.yml

Server side

The generated handler receives the file as an io.ReadCloser on params.File. At runtime it’s a *runtime.File, so a type assertion gives you the multipart header β€” filename and size β€” before you stream the body to disk:

api.UploadsUploadFileHandler = uploads.UploadFileHandlerFunc(func(params uploads.UploadFileParams) middleware.Responder {
	if params.File == nil {
		return middleware.Error(http.StatusNotFound, stderrors.New("no file provided"))
	}
	defer func() {
		_ = params.File.Close()
	}()

	if namedFile, ok := params.File.(*runtime.File); ok {
		log.Printf("received file name: %s", namedFile.Header.Filename)
		log.Printf("received file size: %d", namedFile.Header.Size)
	}

	// uploads file and save it locally
	filename := path.Join(uploadFolder, fmt.Sprintf("uploaded_file_%d.dat", uploadCounter))
	uploadCounter++
	f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
	if err != nil {
		return middleware.Error(http.StatusInternalServerError, stderrors.New("could not create file on server"))
	}

	n, err := io.Copy(f, params.File)
	if err != nil {
		return middleware.Error(http.StatusInternalServerError, stderrors.New("could not upload file on server"))
	}

	log.Printf("copied bytes %d", n)

	log.Printf("file uploaded copied as %s", filename)

	return uploads.NewUploadFileOK()
})

Full source: file-server/restapi/configure_file_upload.go

Note the defer params.File.Close() and the io.Copy into a fresh file β€” the handler owns the stream and is responsible for draining and closing it.

Client side

On the client, a file argument is a runtime.NamedReadCloser β€” an io.ReadCloser that also reports a Name(). A plain *os.File satisfies it, so you open the file and pass it straight to the generated parameter builder:

func upload(reader runtime.NamedReadCloser) error {
	config := client.DefaultTransportConfig().WithHost("localhost:8000")

	uploader := client.NewHTTPClientWithConfig(nil, config)

	params := uploads.NewUploadFileParams().WithFile(reader)

	_, err := uploader.Uploads.UploadFile(params)

	return err
}

Full source: file-server/upload_file.go

The generated UploadFile client method handles the multipart encoding; you only supply the reader.

  • Streaming request/response bodies instead of a one-shot upload? See Streaming.
  • Hand-wiring multipart without codegen? See the go-openapi/runtime examples.
Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Task tracker

The task-tracker example is a larger, realistic CRUD API β€” an issue tracker with tasks, comments and file attachments. Its spec is the one go-swagger uses to exercise code generation, so it deliberately packs in almost every construct: nested resources, composition, arrays, file uploads, and multiple security schemes. It’s the best example to see what the generator produces for a non-trivial contract.

Tip

Source: task-tracker/. Generated with swagger generate server --name TaskTracker --spec ./swagger.yml --principal any.

The API surface

The spec defines a full CRUD surface across three nested resources:

PathOperations
/taskslistTasks, createTask
/tasks/{id}getTaskDetails, updateTask, deleteTask
/tasks/{id}/commentsaddCommentToTask, getTaskComments
/tasks/{id}/filesuploadTaskFile

Each becomes a typed handler on the generated API, with parameters (path, query, body, multipart) bound and validated for you.

Model composition

The spec builds Task on top of a shared TaskCard, and the generator preserves that with Go embedding β€” plus read-only fields, maps of attachments, and slices of related models:

type Task struct {
	TaskCard

	// The attached files.
	//
	// An issue can have at most 20 files attached to it.
	//
	Attachments map[string]TaskAttachmentsAnon `json:"attachments,omitempty"`

	// The 5 most recent items for this issue.
	//
	// The detail view of an issue includes the 5 most recent comments.
	// This field is read only, comments are added through a separate process.
	//
	// Read Only: true
	Comments []*Comment `json:"comments"`

	// The time at which this issue was last updated.
	//
	// This field is read only so it's only sent as part of the response.
	//
	// Read Only: true
	// Format: date-time
	LastUpdated strfmt.DateTime `json:"lastUpdated,omitempty"`

	// last updated by
	LastUpdatedBy *UserCard `json:"lastUpdatedBy,omitempty"`

	// reported by
	ReportedBy *UserCard `json:"reportedBy,omitempty"`
}

Full source: task-tracker/models/task.go

Read-only fields (Comments, LastUpdated) are populated by the server on responses and ignored on input, exactly as the spec declares.

Two API-key schemes

The spec declares two apiKey security definitions β€” one carried as a query parameter, one as a header:

api_key:
  type: apiKey
  name: token
  in: query
token_header:
  type: apiKey
  name: X-Token
  in: header

Full source: task-tracker/swagger.yml

The generator turns each into an authenticator hook you implement. The scaffold leaves them returning NotImplemented; you fill in the token validation:

// Applies when the "token" query is set
if api.APIKeyAuth == nil {
	api.APIKeyAuth = func(token string) (any, error) {
		_ = token

		return nil, errors.NotImplemented("api key auth (api_key) token from query param [token] has not yet been implemented")
	}
}
// Applies when the "X-Token" header is set
if api.TokenHeaderAuth == nil {
	api.TokenHeaderAuth = func(token string) (any, error) {
		_ = token

		return nil, errors.NotImplemented("api key auth (token_header) X-Token from header param [X-Token] has not yet been implemented")
	}
}

Full source: task-tracker/restapi/configure_task_tracker.go

For worked authentication examples (basic, api-key, composed, OAuth2), see the Authentication guides.

  • Todo list server β€” the minimal CRUD server to start from.
  • File server β€” the type: file upload mechanics used by /tasks/{id}/files.
  • Petstore β€” another full spec, generated end to end.
Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Petstore

The Swagger Petstore is the canonical OpenAPI 2.0 sample. The generated/ example is that spec run through swagger generate server β€” a complete server scaffold for a multi-resource API, useful as a reference for what a “full” spec produces.

Tip

Source: generated/. Generated with swagger generate server --name Petstore --spec ./swagger-petstore.json --principal any.

Three resource groups

The petstore spec organizes operations under three tags, each becoming its own package of generated handlers:

  • pet β€” addPet, updatePet, findPetsByStatus, findPetsByTags, getPetById, deletePet, uploadFile
  • store β€” getInventory, placeOrder, getOrderById, deleteOrder
  • user β€” createUser, getUserByName, loginUser, logoutUser, …

A generated model

The Pet model shows the usual spec-to-Go mapping β€” required fields as pointers, nested models by reference, arrays as slices β€” alongside a generated Validate method (not shown) that enforces the spec’s constraints:

type Pet struct {

	// category
	Category *Category `json:"category,omitempty"`

	// id
	ID int64 `json:"id,omitempty"`

	// name
	// Example: doggie
	// Required: true
	Name *string `json:"name"`

	// photo urls
	// Required: true
	PhotoUrls []string `json:"photoUrls"`

	// pet status in the store
	Status string `json:"status,omitempty"`

	// tags
	Tags []*Tag `json:"tags"`
}

Full source: generated/models/pet.go

Two security schemes

Petstore mixes an api-key header with an OAuth2 flow. The generator emits a distinct authenticator hook for each β€” note the OAuth2 hook receives the required scopes so you can authorize per operation:

// Applies when the "api_key" header is set
if api.APIKeyAuth == nil {
	api.APIKeyAuth = func(token string) (any, error) {
		_ = token

		return nil, errors.NotImplemented("api key auth (api_key) api_key from header param [api_key] has not yet been implemented")
	}
}
if api.PetstoreAuthAuth == nil {
	api.PetstoreAuthAuth = func(token string, scopes []string) (any, error) {
		_ = token
		_ = scopes

		return nil, errors.NotImplemented("oauth2 bearer auth (petstore_auth) has not yet been implemented")
	}
}

Full source: generated/restapi/configure_petstore.go

See the OAuth2 guide for a fully implemented flow.

Typed vs untyped

This repo also ships the same petstore API hand-wired without codegen, using the go-openapi runtime directly, under 2.0/petstore. That untyped style β€” building the API from runtime primitives rather than generated code β€” is the subject of the go-openapi/runtime site. Compare the two to see exactly what swagger generate buys you.

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Clients & CLI

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Clients & CLI

Generated client SDK

From the same spec that drives a server, swagger generate client produces a typed Go SDK: one method per operation, with generated parameter and response types. The tutorials/client example generates that SDK in two flavors from one spec β€” the classic go-swagger client and the stratoscale contributed template β€” so you can compare the ergonomics.

Tip

Source: tutorials/client/. Two client packages are generated from one swagger.yml:

  • classic β€” swagger generate client -A TodoList --spec swagger.yml --client-package classic-client
  • stratoscale β€” the same, plus --template stratoscale (reusing --existing-models).

Classic client

The classic client generates a ClientService interface. Each method takes the operation’s params plus an explicit runtime.ClientAuthInfoWriter and variadic ClientOptions; a parallel …Context variant threads a context.Context:

	AddOne(params *AddOneParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*AddOneCreated, *AddOneNoContent, error)

	AddOneContext(ctx context.Context, params *AddOneParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*AddOneCreated, *AddOneNoContent, error)

Full source: tutorials/client/classic_client/todos/todos_client.go

Stratoscale client

The stratoscale template generates a leaner API interface: context-first, auth folded into the transport, no options parameter. It also emits a //go:generate mockery directive so the interface is trivially mockable in tests:

	// AddOne add one API
	AddOne(ctx context.Context, params *AddOneParams) (*AddOneCreated, *AddOneNoContent, error)

Full source: tutorials/client/stratoscale_client/todos/todos_client.go

Pick classic for the full go-swagger surface (per-call auth, per-call options); pick stratoscale for a compact, context-first, easily-mocked client.

Multiple success responses

This example deliberately exercises a tricky spec shape: addOne declares two success responses (201 Created and 204 No Content). Both flavors reflect that in the return signature β€” the method hands back a pointer for each possible success, and exactly one is non-nil:

created, noContent, err := c.AddOne(ctx, params)
switch {
case err != nil:
    // transport or error response
case created != nil:
    // 201 β€” use created.Payload
case noContent != nil:
    // 204 β€” nothing to read
}

The same mechanism covers operations with no default response: without a default, an undeclared status code surfaces as a generic error rather than a typed payload, because the generated response reader only knows the codes the spec listed.

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

CLI client

swagger generate cli produces a command-line tool that wraps the generated client: it reads flags and arguments, builds the operation parameters, calls the server, and prints the response. It’s built on cobra and viper, with shell-completion support.

Tip

Source: cli/. Generated with swagger generate cli --spec ./swagger.yml --cli-app-name todoctl. It targets the same spec as auto-configure, so you can run that server and drive it from this CLI.

Command layout

The generated command tree mirrors the spec:

  • the root command holds global flags (--hostname, --scheme, auth tokens);
  • each tag becomes a sub-command (an operation group);
  • each operationId becomes a sub-command under its tag;
  • each path/query parameter becomes a flag; the body becomes a --body JSON flag, with a flag per body field layered on top.

The tag β†’ sub-command mapping is a cobra.Command per group, wiring in one child per operation:

func makeGroupOfOperationsTodosCmd() (*cobra.Command, error) {
	parent := &cobra.Command{
		Use:  "todos",
		Long: ``,
	}

	sub0, err := makeOperationTodosAddOneCmd()
	if err != nil {
		return nil, err
	}
	parent.AddCommand(sub0)

	sub1, err := makeOperationTodosDestroyOneCmd()
	if err != nil {
		return nil, err
	}
	parent.AddCommand(sub1)

	sub2, err := makeOperationTodosFindTodosCmd()
	if err != nil {
		return nil, err
	}
	parent.AddCommand(sub2)

	sub3, err := makeOperationTodosUpdateOneCmd()
	if err != nil {
		return nil, err
	}
	parent.AddCommand(sub3)

	return parent, nil
}

Full source: cli/cli/cli.go

An operation command

Each operationId gets its own command with a RunE that calls the server, plus generated flag registration for its parameters:

func makeOperationTodosAddOneCmd() (*cobra.Command, error) {
	cmd := &cobra.Command{
		Use:   "addOne",
		Short: ``,
		RunE:  runOperationTodosAddOne,
	}

	if err := registerOperationTodosAddOneParamFlags(cmd); err != nil {
		return nil, err
	}

	return cmd, nil
}

Full source: cli/cli/add_one_operation.go

Body parameters are handled two ways at once β€” a whole-body --body JSON string as a base payload, and a generated flag per field (recursing into sub-definitions) that overrides it. That’s where --item.description comes from:

func registerOperationTodosAddOneBodyParamFlags(cmdPrefix string, cmd *cobra.Command) error {

	var flagBodyName string
	if cmdPrefix == "" {
		flagBodyName = "body"
	} else {
		flagBodyName = fmt.Sprintf("%v.%s", cmdPrefix, "body")
	}

	_ = cmd.PersistentFlags().String(flagBodyName, "", `Optional json string for [body]. `)

	// add flags for body
	if err := registerModelItemFlags(0, "item", cmd); err != nil {
		return err
	}

	return nil
}

Full source: cli/cli/add_one_operation.go

Running it

Drive the auto-configure server with the tool:

$ go run ./cli/cmd/todoctl/main.go --hostname localhost:12345 \
    --x-todolist-token "example token" \
    todos addOne --item.description "hi" --body "{}"
{"description":"hi"}

The path is todoctl β†’ todos (the tag) β†’ addOne (the operationId), with --item.description setting a body field.

Config files and completion

Common flags β€” hostname, scheme, base_path, auth tokens β€” can live in a config file instead of the command line, loaded via viper from ~/.config/<app>/config.json (or --config, in JSON/YAML/env form):

{
    "hostname": "localhost:12345",
    "scheme": "http",
    "x-todolist-token": "example token"
}

Shell completions (bash, zsh, fish, PowerShell) come for free from cobra:

$ source <(./todoctl completion bash)
Note

The CLI generator is under active development. A few spec shapes aren’t covered yet β€” arrays/maps in a body, and enums in help text and completions.

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Authentication

Info

These examples wire authentication into generated servers. Looking to hand-wire auth on an untyped runtime server instead? See the runtime auth examples.

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Authentication

Basic & API-key auth

This is the starting point for security in a generated server. A securityDefinition in the spec becomes a generated authenticator hook you implement, and β€” when you generate with a typed principal β€” every protected handler receives that principal as a typed argument.

Tip

Source: authentication/. Generated with swagger generate server --name AuthSample --spec ./swagger.yml --principal models.Principal.

Declaring the scheme

The authentication example uses a single API-key scheme, carried in the x-token header, and applies it to every endpoint via a top-level security requirement:

securityDefinitions:
  key:
    type: apiKey
    in: header
    name: x-token
security:
  - key: []

Full source: authentication/swagger.yml

The generated authenticator hook

Because the spec names one apiKey scheme called key, the generated API exposes an api.KeyAuth hook. The --principal models.Principal flag makes it return a typed *models.Principal; the scaffold leaves it returning NotImplemented:

if api.KeyAuth == nil {
	api.KeyAuth = func(token string) (*models.Principal, error) {
		_ = token

		return nil, errors.NotImplemented("api key auth (key) x-token from header param [x-token] has not yet been implemented")
	}
}

Full source: authentication/restapi/configure_auth_sample.go

You replace the body with your token check. On success return a principal; on failure return an errors.New(401, …):

api.KeyAuth = func(token string) (*models.Principal, error) {
    if token == "abcdefuvwxyz" {
        prin := models.Principal(token)
        return &prin, nil
    }
    return nil, errors.New(401, "incorrect api key auth")
}

The returned principal is then passed to every handler protected by this scheme:

api.CustomersGetIDHandler = customers.GetIDHandlerFunc(
    func(params customers.GetIDParams, principal *models.Principal) middleware.Responder {
        // principal is the value your KeyAuth hook returned
        ...
    })

Basic auth is the same shape

A type: basic scheme works identically, except the runtime decodes the Authorization: Basic header for you and the hook receives a username/password pair instead of a single token:

api.MyBasicAuth = func(user, pass string) (*models.Principal, error) { ... }

For a worked basic-auth authenticator β€” plus mixing several schemes β€” see Composed auth.

Trying it

$ curl -i -H 'X-Token: abcdefuvwxyz' http://127.0.0.1:35307/api/customers
HTTP/1.1 501 Not Implemented          # authenticated, handler not implemented

$ curl -i -H 'X-Token: wrong' http://127.0.0.1:35307/api/customers
HTTP/1.1 401 Unauthorized
{"code":401,"message":"incorrect api key auth"}
Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Composed auth

Real APIs rarely have a single security scheme. The composed-auth example mixes four β€” basic auth, an API key (by header or query), and scoped JWT tokens β€” and composes them per operation with AND and OR semantics. It’s the reference for anything beyond a single authenticator.

Tip

Source: composed-auth/. Generated with swagger generate server --name multi-auth-example --spec ./swagger.yml --principal models.Principal. The restapi/configure_*.go and auth/authorizers.go files are hand-written.

Four schemes

The spec declares basic auth (isRegistered), an API key in either the header (isReseller) or a query param (isResellerQuery), and a scoped oauth2-typed scheme (hasRole) used purely to carry JWT scopes β€” go-swagger does not run an OAuth2 flow here, it just extracts the required scopes and hands them to your authorizer:

isRegistered:
  # This scheme uses the header: "Authorization: Basic {base64 encoded string defined by username:password}"
  # Scopes are not supported with this type of authorization.
  type: basic
isReseller:
  # This scheme uses the header: "X-Custom-Key: {base64 encoded string}"
  # Scopes are not supported with this type of authorization.
  type: apiKey
  in: header
  name: X-Custom-Key
isResellerQuery:
  # This scheme uses the query parameter "CustomKeyAsQuery"
  # Scopes are not supported with this type of authorization.
  type: apiKey
  in: query
  name: CustomKeyAsQuery
hasRole:
  # This scheme uses the header: "Authorization: Bearer {base64 encoded string representing a JWT}"
  # Alternatively, the query param: "access_token" may be used.
  #
  # In our scenario, we must use the query param version in order to avoid
  # passing several headers with key 'Authorization'
  type: oauth2
  # The flow and URLs in spec are for documentary purpose: go-swagger does not implement OAuth workflows
  flow: accessCode
  authorizationUrl: 'https://dummy.oauth.net/auth'
  tokenUrl: 'https://dumy.oauth.net/token'
  # Required scopes are passed by the runtime to the authorizer
  scopes:
    customer: scope of registered customers
    inventoryManager: scope of resellers acting as inventory managers

Full source: composed-auth/swagger.yml

Composing requirements with AND / OR

A security block on an operation is a list of alternatives (OR), and each alternative is a map of schemes that must all pass (AND). So /order/add accepts a registered customer, or a reseller (by header), or a reseller (by query) β€” each combined with the right JWT role:

security:
  - isRegistered: []
    hasRole: [ customer ]
  - isReseller: []
    hasRole: [ inventoryManager ]
  - isResellerQuery: []
    hasRole: [ inventoryManager ]

Full source: composed-auth/swagger.yml

An empty security: [] on an operation opts it out entirely (public), overriding the spec’s top-level default.

The authorizers

Each scheme maps to a hook whose signature depends on its type: basic auth gets (user, pass), an API key gets (token), and a scoped scheme gets (token, scopes). The example delegates each to a function in auth/authorizers.go:

api.HasRoleAuth = func(token string, scopes []string) (*models.Principal, error) {
	// The header: Authorization: Bearer {base64 string} (or ?access_token={base 64 string} param) has already
	// been decoded by the runtime as a token
	api.Logger("HasRoleAuth handler called")
	return auth.HasRole(token, scopes)
}
// Applies when the Authorization header is set with the Basic scheme
api.IsRegisteredAuth = func(user string, password string) (*models.Principal, error) {
	// The header: Authorization: Basic {base64 string} has already been decoded by the runtime as a
	// username:password pair
	api.Logger("IsRegisteredAuth handler called")
	return auth.IsRegistered(user, password)
}
// Applies when the "X-Custom-Key" header is set
api.IsResellerAuth = func(token string) (*models.Principal, error) {
	api.Logger("IsResellerAuth handler called")
	return auth.IsReseller(token)
}
// Applies when the "CustomKeyAsQuery" query is set
api.IsResellerQueryAuth = func(token string) (*models.Principal, error) {
	api.Logger("ResellerQueryAuth handler called")
	return auth.IsReseller(token)
}

Full source: composed-auth/restapi/configure_multi_auth_example.go

The basic-auth authorizer is a plain credential check returning a typed principal:

// IsRegistered determines if the user is properly registered,
// i.e if a valid username:password pair has been provided.
func IsRegistered(user, pass string) (*models.Principal, error) {
	password, ok := userDb[user]
	if !ok || pass != password {
		return nil, errors.New(401, "Unauthorized: not a registered user")
	}

	return &models.Principal{
		Name: user,
	}, nil
}

Full source: composed-auth/auth/authorizers.go

The scoped HasRole authorizer goes further: it parses the JWT, then checks the token’s claimed roles against the scopes the runtime passed in β€” the mechanism that makes hasRole: [ customer ] in the spec actually mean something.

Trying it

Generate test keys and JWTs, then exercise the composed requirements:

$ cd hack/tools && go run . gen-tokens        # RSA keypair + role JWTs
$ ./composed-auth/exerciser.sh                # sends a sequence of curl requests
Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

OAuth2 access-code

The other auth examples validate a token the client already has. This one runs the full OAuth2 access-code handshake β€” redirecting the user to an identity provider (Google), receiving a callback, exchanging the code for a token, then using that token to authenticate API calls.

Tip

Source: oauth2/. Generated with swagger generate server --name oauthSample --spec ./swagger.yml --principal models.Principal. The handshake lives in the hand-written restapi/implementation.go.

The scheme

The spec declares an oauth2 scheme with the accessCode flow and its authorization/token URLs. Unlike composed-auth β€” which only borrows the oauth2 type to carry scopes β€” here the URLs are real and drive an actual handshake:

securityDefinitions:
  OauthSecurity:
    type: oauth2
    flow: accessCode
    authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth'
    tokenUrl: 'https://www.googleapis.com/oauth2/v4/token'
    scopes:
      admin: Admin scope
      user: User scope

Full source: oauth2/swagger.yml

Note

go-swagger does not implement the OAuth2 workflow for you: the generator produces the authenticator hook and the routing, but the redirect/callback/exchange dance is application code. That’s exactly what this example provides.

Step 1 β€” redirect to the provider

The public /login endpoint sends the user to Google’s consent screen, using the golang.org/x/oauth2 config built in implementation.go:

func login(r *http.Request) middleware.Responder {
	// implements the login with a redirection
	return middleware.ResponderFunc(
		func(w http.ResponseWriter, _ runtime.Producer) {
			http.Redirect(w, r, config.AuthCodeURL(state), http.StatusFound)
		})
}

Full source: oauth2/restapi/implementation.go

Step 2 β€” handle the callback and exchange the code

Google redirects back to /auth/callback with a state and a code. The handler verifies state, then exchanges the code for an access token via the oauth2 client:

func callback(r *http.Request) (string, error) {
	// we expect the redirected client to call us back
	// with 2 query params: state and code.
	// We use directly the Request params here, since we did not
	// bother to document these parameters in the spec.

	if r.URL.Query().Get("state") != state {
		log.Println("state did not match")
		return "", errors.New("state did not match")
	}

	myClient := &http.Client{}

	parentContext := context.Background()
	ctx := oidc.ClientContext(parentContext, myClient)

	authCode := r.URL.Query().Get("code")
	log.Printf("Authorization code: %v\n", authCode)

	// Exchange converts an authorization code into a token.
	// Under the hood, the oauth2 client POST a request to do so
	// at tokenURL, then redirects...
	oauth2Token, err := config.Exchange(ctx, authCode)
	if err != nil {
		log.Println("failed to exchange token", err.Error())
		return "", errors.New("failed to exchange token")
	}

	// the authorization server's returned token
	log.Println("Raw token data:", oauth2Token)
	return oauth2Token.AccessToken, nil
}

Full source: oauth2/restapi/implementation.go

Step 3 β€” authenticate API calls with the token

Every protected endpoint runs through the generated OauthSecurityAuth hook. It validates the bearer token (here by calling Google’s userinfo endpoint) and returns the principal β€” the token string itself in this minimal example:

api.OauthSecurityAuth = func(token string, scopes []string) (*models.Principal, error) {
	_ = scopes

	ok, err := authenticated(token)
	if err != nil {
		return nil, errors.New(401, "error authenticate")
	}
	if !ok {
		return nil, errors.New(401, "invalid token")
	}
	prin := models.Principal(token)

	return &prin, nil
}

Full source: oauth2/restapi/configure_oauth_sample.go

Setup

Register an OAuth client at the Google credentials console, set the callback URL to http://127.0.0.1:12345/api/auth/callback, and put the resulting client ID/secret into the var block of implementation.go. Then:

$ go run ./oauth2/cmd/oauth-sample-server/main.go --port 12345
# open http://127.0.0.1:12345/api/login in a browser, log in, copy the token
$ curl -i -H 'Authorization: Bearer <TOKEN>' http://127.0.0.1:12345/api/customers
Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Streaming

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Streaming

Streaming server

Swagger 2.0 has no first-class notion of a streaming response, but you can still generate a server that streams. The stream-server example is a countdown API: GET /elapse/{length} emits one newline-delimited JSON object per second until it reaches zero.

Tip

Source: stream-server/. Generated with swagger generate server --spec ./swagger.yml.

Declaring a streaming response

The trick is the response schema: type: string, format: binary. That’s the closest Swagger 2.0 gets to “this endpoint streams bytes”, and it makes the generator produce a response the handler writes to directly rather than a typed payload it serializes for you:

responses:
  200:
    description: Secondly update on remaining time
    # This is the best representation there is in Swagger 2.0 to
    # say that this endpoint has a streaming response.
    # In this implementation, it will be newline delimited JSON
    # bodies, of the `Mark` type defined below
    schema:
      type: string
      format: binary
  403:
    description: Contrived - thrown when length of 11 is chosen

Full source: stream-server/swagger.yml

Writing the stream from the handler

Instead of returning a generated responder, the handler returns a middleware.ResponderFunc β€” a closure with raw access to the http.ResponseWriter. It grabs the http.Flusher and writes through a small wrapper so each write is pushed to the client immediately:

api.ElapseHandler = operations.ElapseHandlerFunc(func(params operations.ElapseParams) middleware.Responder {
	if params.Length == 11 {
		return operations.NewElapseForbidden()
	}

	return middleware.ResponderFunc(func(rw http.ResponseWriter, _ runtime.Producer) {
		f, _ := rw.(http.Flusher)
		rw.WriteHeader(http.StatusOK)
		_ = myCounter.Down(params.Length, &flushWriter{f: f, w: rw})
	})
})

Full source: stream-server/restapi/configure_countdown.go

The flushWriter is what turns a normal write into a streamed chunk β€” it flushes after every write, so the client sees each line as it’s produced rather than at the end:

// Via https://play.golang.org/p/PpbPyXbtEs
type flushWriter struct {
	f http.Flusher
	w io.Writer
}

// Via https://play.golang.org/p/PpbPyXbtEs
func (fw *flushWriter) Write(p []byte) (n int, err error) {
	n, err = fw.w.Write(p)
	if fw.f != nil {
		fw.f.Flush()
	}
	return
}

Full source: stream-server/restapi/configure_countdown.go

Producing the chunks

The business logic just encodes one Mark per iteration into the writer, with a one-second pause between them. Because the writer flushes on every Encode, each {"remains":N} line reaches the client in real time:

// Down is the concrete implementation that spits out the JSON bodies.
func (mc *MyCounter) Down(maximum int64, w io.Writer) error {
	if maximum == 11 {
		return errors.New("we don't *do* elevensies")
	}
	e := json.NewEncoder(w)
	for ix := int64(0); ix <= maximum; ix++ {
		r := maximum - ix
		fmt.Printf("Iteration %d\n", r)
		if err := e.Encode(models.Mark{Remains: &r}); err != nil {
			return err
		}

		if ix != maximum {
			time.Sleep(1 * time.Second)
		}
	}

	return nil
}

Full source: stream-server/biz/count.go

Trying it

$ go run ./stream-server/cmd/countdown-server --port=8000
$ curl -N http://127.0.0.1:8000/elapse/5
{"remains":5}
{"remains":4}
{"remains":3}
{"remains":2}
{"remains":1}
{"remains":0}

The response uses Transfer-Encoding: chunked; each line arrives a second apart. A length of 11 returns 403 (a contrived error to show non-streaming responses still work normally).

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Streaming client

A generated client normally reads the entire response body and unmarshals it into a typed payload. To consume a stream you override that behavior β€” swap the consumer, and either buffer the whole thing or read it chunk-by-chunk. Two examples show both approaches.

Tip

Sources: stream-server/elapsed_client.go (non-blocking, pairs with the streaming server) and stream-client/jigsaw.go (blocking, against an external server).

Why the default doesn’t stream

The generated client hands the response body to a consumer, which does an io.Copy into the destination you pass. With the default JSONConsumer that copy blocks until the body is complete and then unmarshals β€” exactly what you don’t want for a stream. The fix is to install a ByteStreamConsumer for the response mime, so the bytes flow through untouched.

Non-blocking: consume chunks as they arrive

Override the consumer, then pass an io.Pipe writer as the destination. The client’s io.Copy writes into the pipe while a goroutine reads the other end β€” so bytes are processed the moment they arrive:

customized := httptransport.New("localhost:8000", "/", []string{"http"})
customized.Consumers[runtime.JSONMime] = runtime.ByteStreamConsumer()

countdowns := client.New(customized, nil)

reader, writer := io.Pipe()

scanner := bufio.NewScanner(reader)

Full source: stream-server/elapsed_client.go

A bufio.Scanner on the read end splits the stream on newlines and unmarshals each line independently. cancel() (via defer) tears down the request if the reader stops early:

wg.Add(1)
go func(wg *sync.WaitGroup) {
	defer wg.Done()
	defer cancel()

	// read response items line by line
	for scanner.Scan() {
		// each response item is JSON
		txt := scanner.Text()
		log.Printf("received countdown mark - raw: %s", txt)

		var mark models.Mark

		err := json.Unmarshal([]byte(txt), &mark)
		if err != nil {
			log.Printf("unmarshal error: %v", err)
			return
		}

		log.Printf("received countdown mark - remaining: %d", conv.Value(mark.Remains))
	}

	if err := scanner.Err(); err != nil {
		log.Printf("scanner err: %v", err)
	}

	log.Println("EOF")
}(&wg)

Full source: stream-server/elapsed_client.go

The request runs on the main goroutine, writing into the pipe the scanner drains; a context timeout bounds how long it will keep the connection open:

elapsed := operations.NewElapseParamsWithContext(queryCtx).WithLength(n)

_, err := countdowns.Operations.Elapse(elapsed, writer)

Full source: stream-server/elapsed_client.go

Blocking: buffer the whole stream

If you don’t need incremental processing, the simplest path is a destination that knows how to accept text/plain. Give a buffer an UnmarshalText method and the default consumer will fill it:

// Buffer knows how to UnmarshalText
type Buffer struct {
	*bytes.Buffer
}

// UnmarshalText handles text/plain
func (b *Buffer) UnmarshalText(text []byte) error {
	_, err := b.Write(text)
	return err
}

Full source: stream-client/jigsaw.go

Then pass it straight to the operation and read it once the call returns:

func chunkedBlocking(withChunks bool) error {
	c := client.New(customTransport(withChunks), nil).Operations

	// we just need to specify a buffer that knows how to UnmarshalText()
	buf := NewBuffer()
	_, err := c.Chunked(operations.NewChunkedParams(), buf)
	if err != nil {
		return err
	}

	data, err := io.ReadAll(buf)
	if err != nil {
		return err
	}

	log.Printf("result: %v", string(data))
	return nil
}

Full source: stream-client/jigsaw.go

The jigsaw.go example also has a non-blocking variant that installs transport.Consumers[runtime.TextMime] = runtime.ByteStreamConsumer() β€” the same technique as above, for a text/plain stream instead of newline-delimited JSON.

Choosing an approach

  • Blocking + UnmarshalText β€” least code; fine when you can wait for the full response.
  • Non-blocking + ByteStreamConsumer + io.Pipe β€” process items as they arrive, cancel early, bound with a context. Use this for long-lived or unbounded streams.
Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Customizing code generation

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Customizing code generation

Custom templates

go-swagger renders code from Go templates, and you can swap in your own. The contributed-templates example uses the built-in --template stratoscale option β€” a community template set that produces a different, interface-first shape optimized for testability.

Tip

Source: contributed-templates/stratoscale/. Generated with swagger generate server -A Petstore --template stratoscale (and the matching swagger generate client --template stratoscale).

What the template changes

Instead of the default’s per-operation handler func fields, the stratoscale template groups operations into interfaces β€” one per tag β€” that your code implements, and emits //go:generate mockery directives so those interfaces are trivially mockable in tests:

//go:generate mockery --name PetAPI --inpackage

// PetAPI
type PetAPI interface {
	// PetCreate Add a new pet to the store
	PetCreate(ctx context.Context, params pet.PetCreateParams) middleware.Responder
	// PetDelete Deletes a pet
	PetDelete(ctx context.Context, params pet.PetDeleteParams) middleware.Responder
	// PetGet Get pet by it's ID
	PetGet(ctx context.Context, params pet.PetGetParams) middleware.Responder
	// PetList List pets
	PetList(ctx context.Context, params pet.PetListParams) middleware.Responder
	// PetUpdate Update an existing pet
	PetUpdate(ctx context.Context, params pet.PetUpdateParams) middleware.Responder
	// PetUploadImage uploads an image
	PetUploadImage(ctx context.Context, params pet.PetUploadImageParams) middleware.Responder
}

Full source: contributed-templates/stratoscale/restapi/configure_petstore.go

Every handler is context-first (ctx context.Context, params …), matching the stratoscale client flavor. You provide one implementation per interface (PetAPI, StoreAPI, …) rather than assigning individual handler funcs.

When to use it

Reach for a custom template set when the default output doesn’t fit your codebase conventions β€” here, interface-based handlers plus generated mocks. Because it’s a whole template family, it changes server and client output consistently.

To go further, --template-dir points the generator at your own template directory, letting you override any individual template go-swagger ships.

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

External types

By default every schema in your spec becomes a generated Go struct. Sometimes you want a schema to map onto a type you already have β€” a hand-written type, one from another package, or a shared domain type. The external-types example shows how x-go-type binds a schema to an externally defined Go type instead of generating one.

Tip

Source: external-types/. See also the go-swagger external types reference.

The x-go-type extension

Attach x-go-type to a schema to name the Go type it should use, and where to import it from. Here a property is bound to MyAlternateInteger from the fred package instead of getting a generated type:

gamma:
  description: |
    Property defined as an external type from package "fred"

  x-go-type:
    type: MyAlternateInteger
    import:
      package: "github.com/go-swagger/examples/external-types/fred"

Full source: external-types/example-external-types.yaml

The import.package (and optional alias) tell the generator which import to add. The generated code references your type directly β€” no definition is emitted for it.

The generated result

A definition bound to an external type collapses to exactly that type, with the external package imported (and its name mangled to avoid collisions). This MyExtCollection is a slice of an external go-ext type:

// MyExtCollection This type demonstrates the import generation with name mangling
//
// swagger:model MyExtCollection
type MyExtCollection []go_ext.MyExtType

Full source: external-types/models/my_ext_collection.go

Because the external type is expected to satisfy the runtime’s Validatable interface, the generated Validate still calls into it per item β€” so your type participates in validation like any generated model.

What it covers

The example exercises the full range of external-type use cases:

  • an external type as its own definition, or nested inside an object/slice/map/tuple;
  • types pulled from the default models package or from an arbitrary import path;
  • embedding an external type to add the Validatable interface;
  • annotation hints to resolve nullable/struct-vs-interface questions and to skip validation of an external type.
Note

The example spec adds an additionalItems clause to demonstrate tuples, which makes it not strictly valid against the Swagger 2.0 meta-schema β€” intentional, to show the tuple binding.

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Generation flags

The generated main.go for a server isn’t fixed β€” a couple of flags change how it parses command-line options and whether it carries the spec inside the binary. The flags example generates the same API six ways so you can compare the results side by side.

Tip

Source: flags/. Six sub-packages (pflag/, flag/, go-flags/ and their x… variants) are each generated from one swagger.yml with a different flag combination.

--flag-strategy β€” how the server parses CLI options

The flag strategy selects the library the generated main.go uses for its command-line flags. The API is identical; only the flag plumbing differs:

--flag-strategyLibraryFlag style
go-flags (default)jessevdk/go-flags--port=8080, env-var bindings ([$PORT]), grouped options
pflagspf13/pflagGNU-style --port 8080
flagstdlib flagsingle-dash -port 8080
(mkdir pflag && cd pflag && swagger generate server --spec=../swagger.yml --flag-strategy=pflag)

All three expose the same server options β€” listeners (--scheme), timeouts, TLS settings, socket path β€” just rendered in each library’s idiom. Pick the one that matches the rest of your CLI.

--exclude-spec β€” embedded vs. runtime spec

By default the spec is embedded in the generated binary (the x… variants drop this):

  • default (embedded) β€” the spec is baked in; the server is fully self-contained and serves its own swagger.json.
  • --exclude-spec β€” the spec is not embedded. An extra --spec CLI flag appears so the server loads the document at startup instead.

Embed for a single shippable artifact; exclude when you want to swap the spec without rebuilding, or to keep the binary small.

Trying it

Build any variant’s server and ask for help to see that strategy’s flag layout:

$ go build -o srv ./flags/pflag/cmd/simple-to-do-list-api-server && ./srv -h
  • Custom templates β€” change the generated code shape, not just its flags.
  • CLI client β€” a different use of flags: a generated cobra command-line client.
Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Custom middleware

A generated server is a standard net/http stack, so any http.Handler middleware composes with it. The middleware example wires two real concerns β€” security response headers and Prometheus metrics β€” into a generated server using only the hook points codegen already leaves for you. No --exclude-main and no custom router required.

Tip

Source: middleware/. Generated with swagger generate server -A Greeter -f ./swagger.yml. Answers go-swagger issues #2683 (security headers) and #1120 (a /metrics endpoint).

Two extension points

Codegen leaves two hooks in restapi/configure_*.go, and they run at different stages of the request:

HookRunsSees the matched route?Use it for
setupGlobalMiddlewareBefore swagger routing β€” wraps everything (spec, UI, all routes)NoCross-cutting concerns: security headers, panic recovery, a metrics mount
setupMiddlewaresAfter routing β€” only matched operationsYes (middleware.MatchedRouteFrom)Per-route concerns: instrumentation labelled by route template

The global hook β€” headers + metrics mount

setupGlobalMiddleware wraps the entire server. Reading outermost to innermost: metrics.Mount intercepts GET /metrics so scrape traffic bypasses routing; unrolled/secure adds HSTS and other headers to every response, including the spec and UI:

// setupGlobalMiddleware wraps everything the server serves, including the
// swagger spec document and the embedded UI.
//
// Order matters. From outermost to innermost:
//
//  1. metrics.Mount intercepts GET /metrics so scrape traffic bypasses the
//     swagger router as well as the headers and instrumentation below.
//  2. unrolled/secure injects HSTS and other security response headers on
//     every response, including /swagger.json and the UI assets.
//  3. The swagger handler itself (which will pass through setupMiddlewares
//     once a route is matched).
func setupGlobalMiddleware(handler http.Handler) http.Handler {
	sec := secure.New(secure.Options{
		STSSeconds:           63072000,
		STSIncludeSubdomains: true,
		// ForceSTSHeader: true emits HSTS even on plain-HTTP requests so the
		// example is easy to exercise with `curl http://...`. In production
		// you would either serve over HTTPS directly (the library detects it
		// from the TLS connection) or set SSLProxyHeaders so detection works
		// behind a TLS-terminating proxy β€” and leave ForceSTSHeader at false.
		ForceSTSHeader:     true,
		FrameDeny:          true,
		ContentTypeNosniff: true,
		ReferrerPolicy:     "no-referrer",
		// Send "X-XSS-Protection: 0" explicitly. The default BrowserXssFilter
		// option sends "1; mode=block", which is now considered harmful in
		// some browsers; modern OWASP / MDN guidance is to disable the
		// legacy XSS auditor outright. See README.
		CustomBrowserXssValue: "0",
	})

	return metrics.Mount(sec.Handler(handler))
}

Full source: middleware/restapi/configure_greeter.go

The per-route hook β€” instrumentation

setupMiddlewares runs after routing, so the matched route is available. That’s what lets metrics be labelled by route:

// setupMiddlewares wraps the swagger handler after routing.
//
// At this point, [middleware.MatchedRouteFrom] returns the matched route, so
// this is the right place to plug per-route instrumentation. Requests that do
// not match an operation (404/405) never reach this layer; that is by design
// for an example whose metrics describe API operation behaviour.
func setupMiddlewares(handler http.Handler) http.Handler {
	return metrics.Instrument(handler)
}

Full source: middleware/restapi/configure_greeter.go

The go-swagger-specific glue: the route label

The one piece that’s specific to a go-swagger server is the metrics route label. It uses middleware.MatchedRouteFrom(r).PathPattern β€” the swagger path template (/greet/{name}) rather than the literal path (/greet/alice) β€” so Prometheus label cardinality stays bounded instead of exploding one series per distinct URL:

// Instrument records request count and latency for the wrapped handler.
//
// It is meant to be installed in the generated server's setupMiddlewares hook
// (i.e. after swagger routing) so that [middleware.MatchedRouteFrom] returns
// the matched route. The route label is the swagger path template (e.g.
// "/greet/{name}") rather than the literal request path, to keep the metric
// label cardinality bounded.
func Instrument(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		rec := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
		next.ServeHTTP(rec, r)

		route := "unmatched"
		if mr := middleware.MatchedRouteFrom(r); mr != nil && mr.PathPattern != "" {
			route = mr.PathPattern
		}

		obs := prometheus.Labels{
			labelMethod: r.Method,
			labelRoute:  route,
			labelCode:   strconv.Itoa(rec.status),
		}
		requestsTotal.With(obs).Inc()
		requestDuration.With(obs).Observe(time.Since(start).Seconds())
	})
}

Full source: middleware/internal/metrics/metrics.go

Trying it

$ go run ./middleware/cmd/greeter-server --port 8080
$ curl -i http://127.0.0.1:8080/api/greet
HTTP/1.1 200 OK
Strict-Transport-Security: max-age=63072000; includeSubDomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
...
{"message":"hello"}

$ curl http://127.0.0.1:8080/metrics
http_requests_total{code="200",method="GET",route="/api/greet/{name}"} 3

The route label carries the basePath (/api) because the matched template is the full path the router serves.

Other middleware, same pattern

Any http.Handler middleware composes identically via setupGlobalMiddleware β€” panic recovery (gorilla/handlers.RecoveryHandler), a request ID, an access log, and so on. The example keeps to headers + metrics to stay focused on the wiring.

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Alias compatibility

Info

This example runs the other direction β€” code β†’ spec (swagger generate spec), not spec β†’ code. That code-first workflow is the subject of the go-openapi/codescan site; it lives here only because the repo hosts the example. The rest of this site is spec-first codegen.

The alias-compatibility example shows how a Go type alias is reflected when you generate a spec from Go code, and how the --transparent-aliases flag controls it.

Tip

The aliases

UserID is a true Go alias (=) of Identifier, not a distinct named type:

// Identifier represents a unique identifier.
type Identifier string

// UserID is an alias to Identifier for user-specific IDs.
type UserID = Identifier

Full source: alias-compatibility/api.go

What the flag does

When swagger generate spec walks this code, the alias can be treated two ways:

  • Default (post-#3227) β€” UserID appears as its own definition, and User.id references #/definitions/UserID.
  • --transparent-aliases β€” UserID is not emitted; User.id references #/definitions/Identifier directly (the pre-#3227 behavior).

See the difference by generating both and diffing:

$ swagger generate spec -m -o without-flag.json
$ swagger generate spec -m --transparent-aliases -o with-flag.json
$ diff <(jq . without-flag.json) <(jq . with-flag.json)
  • go-openapi/codescan β€” the code-first (code β†’ spec) workflow this example belongs to.
  • External types β€” the spec-first counterpart: binding a schema to an existing Go type.
Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Tutorials

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Tutorials

Todo list tutorial

This tutorial walks you through a hypothetical project, building a todo list.

It uses a todo list because this is a well-understood application, so you can focus on the go-swagger pieces. Here we build the server; when you’re done, head to the client SDK tutorial to generate a typed client against the same spec.

Info

You’ll need the swagger CLI on your PATH. See goswagger.io for installation, and the command reference for the full set of generate options. The finished code for each stage of this tutorial lives under tutorials/todo-list/ in the examples repository (server-1, server-2, server-complete).

To create your application start with swagger init:

swagger init spec \
  --title "A Todo list application" \
  --description "From the todo list tutorial on goswagger.io" \
  --version 1.0.0 \
  --scheme http \
  --consumes application/io.goswagger.examples.todo-list.v1+json \
  --produces application/io.goswagger.examples.todo-list.v1+json

This gives you a skeleton swagger.yml file:

definitions:
  item:
    type: object
    required:
      - description
    properties:
      id:
        type: integer
        format: int64
        readOnly: true
      description:
        type: string
        minLength: 1
      completed:
        type: boolean

In this model definition we say that the model item is an object with a required property description. This item model has 3 properties: id, description, and completed. The id property is an int64 value and is marked as readOnly, meaning that it will be provided by the API server and it will be ignored when the item is created.

This document also says that the description must be at least 1 char long, which results in a string property that’s not a pointer.

At this moment you have enough so that actual code could be generated, but let’s continue defining the rest of the API so that the code generation will be more useful. Now that you have a model so you can add some endpoints to list the todo’s:

paths:
  /:
    get:
      tags:
        - todos
      parameters:
        - name: since
          in: query
          type: integer
          format: int64
        - name: limit
          in: query
          type: integer
          format: int32
          default: 20
      responses:
        200:
          description: list the todo operations
          schema:
            type: array
            items:
              $ref: "#/definitions/item"

With this new version of the operation you now have query params. These parameters have defaults so users can leave them off and the API will still function as intended.

However, this definition is extremely optimistic and only defines a response for the “happy path”. It’s very likely that the API will need to return errors too. That means you have to define a model errors, as well as at least one more response definition to cover the error response.

The error definition looks like this:

paths:
  /:
    get:
      tags:
        - todos
      parameters:
        - name: since
          in: query
          type: integer
          format: int64
        - name: limit
          in: query
          type: integer
          format: int32
          default: 20
      responses:
        200:
          description: list the todo operations
          schema:
            type: array
            items:
              $ref: "#/definitions/item"
        default:
          description: generic error response
          schema:
            $ref: "#/definitions/error"

At this point you’ve defined your first endpoint completely. To improve the strength of this contract you could define responses for each of the status codes and perhaps return different error messages for different statuses. For now, the status code will be provided in the error message.

Try validating the specification again with swagger validate ./swagger.yml to ensure that code generation will work as expected. Generating code from an invalid specification leads to unpredictable results.

Your completed spec should look like this:

paths:
  /:
    get:
      tags:
        - todos
      operationId: find_todos
      ...

These operationId values are used to name the generated files:

.
β”œβ”€β”€ cmd
β”‚   └── todo-list-server
β”‚       └── main.go
β”œβ”€β”€ models
β”‚   β”œβ”€β”€ error.go
β”‚   β”œβ”€β”€ find_todos_okbody.go
β”‚   β”œβ”€β”€ get_okbody.go
β”‚   └── item.go
β”œβ”€β”€ restapi
β”‚   β”œβ”€β”€ configure_todo_list.go
β”‚   β”œβ”€β”€ doc.go
β”‚   β”œβ”€β”€ embedded_spec.go
β”‚   β”œβ”€β”€ operations
β”‚   β”‚   β”œβ”€β”€ todo_list_api.go
β”‚   β”‚   └── todos
β”‚   β”‚       β”œβ”€β”€ find_todos.go
β”‚   β”‚       β”œβ”€β”€ find_todos_parameters.go
β”‚   β”‚       β”œβ”€β”€ find_todos_responses.go
β”‚   β”‚       └── find_todos_urlbuilder.go
β”‚   └── server.go
└── swagger.yml

You can see that the files under restapi/operations/todos now use the operationId as part of the generated file names.

At this point can start the server, but first let’s see what --help gives you. First install the server binary and then run it:

Β± ~/go/src/.../examples/tutorials/todo-list/server-1
Β» go install ./cmd/todo-list-server/
Β± ~/go/src/.../examples/tutorials/todo-list/server-1
Β» todo-list-server --help
Usage:
  todo-list-server [OPTIONS]

From the todo list tutorial on goswagger.io

Application Options:
      --scheme=            the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec
      --cleanup-timeout=   grace period for which to wait before shutting down the server (default: 10s)
      --max-header-size=   controls the maximum number of bytes the server will read parsing the request header's keys and values, including the
                           request line. It does not limit the size of the request body. (default: 1MiB)
      --socket-path=       the unix socket to listen on (default: /var/run/todo-list.sock)
      --host=              the IP to listen on (default: localhost) [$HOST]
      --port=              the port to listen on for insecure connections, defaults to a random value [$PORT]
      --listen-limit=      limit the number of outstanding requests
      --keep-alive=        sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)
                           (default: 3m)
      --read-timeout=      maximum duration before timing out read of the request (default: 30s)
      --write-timeout=     maximum duration before timing out write of the response (default: 60s)
      --tls-host=          the IP to listen on for tls, when not specified it's the same as --host [$TLS_HOST]
      --tls-port=          the port to listen on for secure connections, defaults to a random value [$TLS_PORT]
      --tls-certificate=   the certificate to use for secure connections [$TLS_CERTIFICATE]
      --tls-key=           the private key to use for secure connections [$TLS_PRIVATE_KEY]
      --tls-ca=            the certificate authority file to be used with mutual tls auth [$TLS_CA_CERTIFICATE]
      --tls-listen-limit=  limit the number of outstanding requests
      --tls-keep-alive=    sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)
      --tls-read-timeout=  maximum duration before timing out read of the request
      --tls-write-timeout= maximum duration before timing out write of the response

Help Options:
  -h, --help               Show this help message

If you run your application now it will start on a random port by default. This might not be what you want, so you can configure a port through a command line argument or a PORT env var.

git:(master) βœ— !? Β» todo-list-server
serving todo list at http://127.0.0.1:64637

You can use curl to check your API:

git:(master) βœ— !? Β» curl -i http://127.0.0.1:64637/
HTTP/1.1 501 Not Implemented
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Thu, 31 Dec 2015 22:42:10 GMT
Content-Length: 57

"operation todos.FindTodos has not yet been implemented"

As you can see, the generated API isn’t very usable yet, but we know it runs and does something. To make it useful you’ll need to implement the actual logic behind those endpoints. And you’ll also want to add some more endpoints, like adding a new todo item and updating an existing item to change its description or mark it completed.

To supporting adding a todo item you should define a POST operation:

paths:
  /{id}:
    delete:
      tags:
        - todos
      operationId: destroyOne
      parameters:
        - type: integer
          format: int64
          name: id
          in: path
          required: true
      responses:
        204:
          description: Deleted
        default:
          description: error
          schema:
            $ref: "#/definitions/error"

This time you’re defining a parameter that is part of the path. This operation will look in the URI templated path for an id. Since there’s nothing to return after a delete, the success response is 204 No Content.

Finally, you need to define a way to update an existing item:

swagger: "2.0"
info:
  description: From the todo list tutorial on goswagger.io
  title: A Todo list application
  version: 1.0.0
consumes:
- application/io.goswagger.examples.todo-list.v1+json
produces:
- application/io.goswagger.examples.todo-list.v1+json
schemes:
- http
- https
paths:
  /:
    get:
      tags:
        - todos
      operationId: findTodos
      parameters:
        - name: since
          in: query
          type: integer
          format: int64
        - name: limit
          in: query
          type: integer
          format: int32
          default: 20
      responses:
        200:
          description: list the todo operations
          schema:
            type: array
            items:
              $ref: "#/definitions/item"
        default:
          description: generic error response
          schema:
            $ref: "#/definitions/error"
    post:
      tags:
        - todos
      operationId: addOne
      parameters:
        - name: body
          in: body
          schema:
            $ref: "#/definitions/item"
      responses:
        201:
          description: Created
          schema:
            $ref: "#/definitions/item"
        default:
          description: error
          schema:
            $ref: "#/definitions/error"
  /{id}:
    parameters:
      - type: integer
        format: int64
        name: id
        in: path
        required: true
    put:
      tags:
        - todos
      operationId: updateOne
      parameters:
        - name: body
          in: body
          schema:
            $ref: "#/definitions/item"
      responses:
        200:
          description: OK
          schema:
            $ref: "#/definitions/item"
        default:
          description: error
          schema:
            $ref: "#/definitions/error"
    delete:
      tags:
        - todos
      operationId: destroyOne
      responses:
        204:
          description: Deleted
        default:
          description: error
          schema:
            $ref: "#/definitions/error"
definitions:
  item:
    type: object
    required:
      - description
    properties:
      id:
        type: integer
        format: int64
        readOnly: true
      description:
        type: string
        minLength: 1
      completed:
        type: boolean
  error:
    type: object
    required:
      - message
    properties:
      code:
        type: integer
        format: int64
      message:
        type: string

This is a good time to sanity check and by validating the schema:

Β± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-2
git:(master) βœ— !? Β» swagger validate ./swagger.yml
The swagger spec at "./swagger.yml" is valid against swagger specification 2.0

Now you’re ready to generate the API and start filling in the actual operations:

git:(master) βœ— !? Β» swagger generate server -A TodoList -f ./swagger.yml
... elided output ...
2015/12/31 18:16:28 rendered main template: server.TodoList
Β± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-2
git:(master) βœ— !? Β» tree
.
β”œβ”€β”€ cmd
β”‚   └── todo-list-server
β”‚       └── main.go
β”œβ”€β”€ models
β”‚   β”œβ”€β”€ error.go
β”‚   β”œβ”€β”€ find_todos_okbody.go
β”‚   └── item.go
β”œβ”€β”€ restapi
β”‚   β”œβ”€β”€ configure_todo_list.go
β”‚   β”œβ”€β”€ doc.go
β”‚   β”œβ”€β”€ embedded_spec.go
β”‚   β”œβ”€β”€ operations
β”‚   β”‚   β”œβ”€β”€ todo_list_api.go
β”‚   β”‚   └── todos
β”‚   β”‚       β”œβ”€β”€ add_one.go
β”‚   β”‚       β”œβ”€β”€ add_one_parameters.go
β”‚   β”‚       β”œβ”€β”€ add_one_responses.go
β”‚   β”‚       β”œβ”€β”€ add_one_urlbuilder.go
β”‚   β”‚       β”œβ”€β”€ destroy_one.go
β”‚   β”‚       β”œβ”€β”€ destroy_one_parameters.go
β”‚   β”‚       β”œβ”€β”€ destroy_one_responses.go
β”‚   β”‚       β”œβ”€β”€ destroy_one_urlbuilder.go
β”‚   β”‚       β”œβ”€β”€ find_todos.go
β”‚   β”‚       β”œβ”€β”€ find_todos_parameters.go
β”‚   β”‚       β”œβ”€β”€ find_todos_responses.go
β”‚   β”‚       β”œβ”€β”€ find_todos_urlbuilder.go
β”‚   β”‚       β”œβ”€β”€ update_one.go
β”‚   β”‚       β”œβ”€β”€ update_one_parameters.go
β”‚   β”‚       β”œβ”€β”€ update_one_responses.go
β”‚   β”‚       └── update_one_urlbuilder.go
β”‚   └── server.go
└── swagger.yml

6 directories, 26 files

To implement the core of your application you start by editing restapi/configure_todo_list.go. This file is safe to edit. Its content will not be overwritten if you run swagger generate again the future.

The simplest way to implement this application is to simply store all the todo items in a golang map. This provides a simple way to move forward without bringing in complications like a database or files.

To do this you’ll need a map and a counter to track the last assigned id:

// the variables we need throughout our implementation
var items = make(map[int64]*models.Item)
var lastID int64

The simplest handler to implement now is the delete handler. Because the store is a map and the id of the item is provided in the request it’s a one liner.

api.TodosDestroyOneHandler = todos.DestroyOneHandlerFunc(func(params todos.DestroyOneParams) middleware.Responder {
  delete(items, params.ID)
  return todos.NewDestroyOneNoContent()
})

After deleting the item from the store, you need to provide a response. The code generator created responders for each response you defined in the swagger specification, and you can see how one of those is being used in the example above.

The other 3 handler implementations are similar to this one. They are provided in the source for this tutorial.

So assuming you go ahead and implement the remainder of the endpoints, you’re all set to test it out:

Β» curl -i localhost:8765
HTTP/1.1 200 OK
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:01 GMT
Content-Length: 3

[]
Β± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete
Β» curl -i localhost:8765 -d "{\"description\":\"message $RANDOM\"}"
HTTP/1.1 415 Unsupported Media Type
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:11 GMT
Content-Length: 157

{"code":415,"message":"unsupported media type \"application/x-www-form-urlencoded\", only [application/io.goswagger.examples.todo-list.v1+json] are allowed"}
~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete
Β» curl -i localhost:8765 -d "{\"description\":\"message $RANDOM\"}" -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json'
HTTP/1.1 201 Created
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:20 GMT
Content-Length: 39

{"description":"message 30925","id":1}
Β± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete
Β» curl -i localhost:8765 -d "{\"description\":\"message $RANDOM\"}" -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json'
HTTP/1.1 201 Created
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:23 GMT
Content-Length: 37

{"description":"message 104","id":2}
Β± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete
Β» curl -i localhost:8765 -d "{\"description\":\"message $RANDOM\"}" -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json'
HTTP/1.1 201 Created
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:24 GMT
Content-Length: 39

{"description":"message 15225","id":3}
Β± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete
Β» curl -i localhost:8765
HTTP/1.1 200 OK
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:26 GMT
Content-Length: 117

[{"description":"message 30925","id":1},{"description":"message 104","id":2},{"description":"message 15225","id":3}]
Β± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete
Β» curl -i localhost:8765/3 -X PUT -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json' -d '{"description":"go shopping"}'
HTTP/1.1 200 OK
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:32 GMT
Content-Length: 37

{"description":"go shopping","id":3}
Β± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete
Β» curl -i localhost:8765
HTTP/1.1 200 OK
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:34 GMT
Content-Length: 115

[{"description":"message 30925","id":1},{"description":"message 104","id":2},{"description":"go shopping","id":3}]
Β± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete
Β» curl -i localhost:8765/1 -X DELETE -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json'
HTTP/1.1 204 No Content
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:57:04 GMT
Β± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete
Β» curl -i localhost:8765
HTTP/1.1 200 OK
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:57:06 GMT
Content-Length: 76

[{"description":"message 104","id":2},{"description":"go shopping","id":3}]

Next steps

  • Generate a typed client against this same spec in the client SDK tutorial.
  • Browse the servers guides for variations: strict handlers, custom error handling, file serving and more.
Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Client SDK tutorial

The todo-list tutorial built a server from a spec. This one takes the same kind of spec and generates a typed client SDK β€” one Go method per operation, with generated parameter and response types β€” then walks through actually calling an API with it.

You’ll generate the SDK in two flavors from one spec (the classic go-swagger client and the leaner stratoscale contributed template) and see how the generated signature copes with two spec shapes that trip people up: an operation with multiple success responses, and one with no default response.

Info

You’ll need the swagger CLI on your PATH β€” see goswagger.io. The finished code lives under tutorials/client/ in the examples repository. For a side-by-side reference comparison of the two flavors, see the generated client SDK guide; this page is the hands-on walkthrough.

Step 1 β€” the spec

Start from a todo-list swagger.yml. The only detail that shapes the client here is the security scheme: the API is protected by an API-key header, so every request the client sends must carry a x-todolist-token:

securityDefinitions:
  key:
    type: apiKey
    in: header
    name: x-todolist-token
security:
  - key: []

Full source: tutorials/client/swagger.yml

That security requirement is what forces an auth writer into the calls below.

Step 2 β€” generate the classic client

Point swagger generate client at the spec:

swagger generate client -A TodoList --spec swagger.yml --client-package classic_client

This writes a classic_client/ package: a top-level TodoList client whose fields group the operations by tag (Todos, Experimental), plus generated …Params and …Responses types under each tag package.

Step 3 β€” call the API

Instantiate the client over a transport, then call an operation. Because the spec requires an API key, you pass a runtime.ClientAuthInfoWriter built from the transport’s APIKeyAuth helper β€” the classic client takes it per call:

import (
	httptransport "github.com/go-openapi/runtime/client"
	"github.com/go-openapi/strfmt"
	"github.com/go-openapi/swag/conv"

	client "github.com/go-swagger/examples/tutorials/client/classic_client"
	"github.com/go-swagger/examples/tutorials/client/classic_client/todos"
	"github.com/go-swagger/examples/tutorials/client/models"
)

// point the transport at the running server
transport := httptransport.New("localhost:8080", "/", []string{"http"})
c := client.New(transport, strfmt.Default)

// the API-key writer that satisfies the `key` security scheme
auth := httptransport.APIKeyAuth("x-todolist-token", "header", "my-secret-token")

params := todos.NewAddOneParams().WithBody(&models.Item{
	Description: conv.Pointer("write the client tutorial"),
})

created, noContent, err := c.Todos.AddOne(params, auth)

Notice the call returns three values, not two β€” that’s the next step.

Step 4 β€” the stratoscale flavor

Regenerate the same spec with the stratoscale template for a leaner, context-first client (auth folded into construction, no per-call options, and a //go:generate mockery directive for easy mocking):

swagger generate client -A TodoList --spec swagger.yml \
  --template stratoscale --existing-models ... --client-package stratoscale_client

Usage folds the auth writer into the constructor and threads a context.Context through each call instead of an auth argument:

import (
	"github.com/go-openapi/runtime"
	httptransport "github.com/go-openapi/runtime/client"

	client "github.com/go-swagger/examples/tutorials/client/stratoscale_client"
	"github.com/go-swagger/examples/tutorials/client/stratoscale_client/todos"
)

c := client.New(client.Config{
	URL:      mustParse("http://localhost:8080/"),
	AuthInfo: httptransport.APIKeyAuth("x-todolist-token", "header", "my-secret-token"),
})

created, noContent, err := c.Todos.AddOne(ctx, todos.NewAddOneParams().WithBody(item))

Same operation, same three return values β€” only the ergonomics differ. Pick classic for the full go-swagger surface (per-call auth and options); pick stratoscale for a compact, mockable client.

Step 5 β€” multiple success responses

Why three return values? Because addOne declares two success responses in the spec β€” 201 Created and 204 No Content:

    post:
      operationId: addOne
      responses:
        '201':
          description: Created
          schema:
            $ref: "#/definitions/item"
        '204':
          description: Already there

The generated method reflects that by handing back a pointer for each possible success; exactly one is non-nil. Switch on them:

created, noContent, err := c.Todos.AddOne(params, auth)
switch {
case err != nil:
	// transport failure, or a typed error response
case created != nil:
	// 201 β€” the new item is in created.Payload
	log.Printf("created #%d", created.Payload.ID)
case noContent != nil:
	// 204 β€” the item already existed; nothing to read
	log.Print("already there")
}

The no default response case (the experimental operations declare 401/405 but no default) works by the same mechanism in reverse: with no default, any status code the spec didn’t list can’t map to a typed payload, so it surfaces as a generic error from the response reader rather than a *…Default value.

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Custom server tutorial

The todo-list tutorial generated a whole server β€” main.go and all β€” and you edited the configure_*.go file it left for you. Sometimes you want the opposite balance: keep go-swagger’s generated core (the models, router, and typed operations) but own the main yourself, so the CLI is a thin hand-written layer that wires configuration and handlers around that core.

That’s what --exclude-main is for. This tutorial builds a tiny greeter server that way.

Info

You’ll need the swagger CLI on your PATH β€” see goswagger.io. The finished code lives under tutorials/custom-server/.

Step 1 β€” the spec

The greeter is deliberately minimal: one GET /hello that takes an optional name query parameter and returns a plain-text greeting.

swagger: '2.0'
info:
  version: 1.0.0
  title: Greeting Server
paths:
  /hello:
    get:
      produces:
        - text/plain
      parameters:
        - name: name
          required: false
          type: string
          in: query
          description: defaults to World if not given
      operationId: getGreeting
      responses:
        200:
          description: returns a greeting
          schema:
            type: string
            description: contains the actual greeting as plain text

Step 2 β€” generate the core only

Generate the server into a gen/ sub-tree with --exclude-main, so go-swagger emits everything except a main.go:

rm -rf gen && mkdir gen
swagger generate server --exclude-main -A greeter -t gen -f ./swagger/swagger.yml

You get gen/restapi/ β€” the embedded spec, the NewServer constructor, the router, and operations/ with the typed GreeterAPI, its GetGreetingParams, and the GetGreetingOK responder. What you don’t get is a cmd/ entry point. That’s yours to write.

Step 3 β€” write your own main

Your main does what the generated main.go would have β€” load the embedded spec, construct the API, and hand it to NewServer β€” but it’s plain code you control:

// load embedded swagger file
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
if err != nil {
	return err
}

// create new service API
api := operations.NewGreeterAPI(swaggerSpec)
server := restapi.NewServer(api)

Full source: tutorials/custom-server/cmd/greeter/main.go

Because you own this file, you can add your own flags, config loading, dependency injection, logging, or lifecycle management around this core β€” none of it is generated, none of it gets overwritten on regeneration.

Step 4 β€” attach the handler

The generated GreeterAPI exposes one handler field per operation. Assign your implementation to GetGreetingHandler before serving β€” this is the same handler you’d otherwise place in a generated configure_*.go, but here it lives in your main:

// GetGreetingHandler greets the given name,
// in case the name is not given, it will default to World
api.GetGreetingHandler = operations.GetGreetingHandlerFunc(
	func(params operations.GetGreetingParams) middleware.Responder {
		name := conv.Value(params.Name)
		if name == "" {
			name = "World"
		}

		greeting := fmt.Sprintf("Hello, %s!", name)
		return operations.NewGetGreetingOK().WithPayload(greeting)
	})

Full source: tutorials/custom-server/cmd/greeter/main.go

conv.Value dereferences the optional *string parameter, defaulting to World, and NewGetGreetingOK().WithPayload(...) returns the typed 200 responder the generated code defined.

Step 5 β€” run it

$ go run ./cmd/greeter/main.go --port 3000

Then exercise it (here with httpie):

$ http get :3000/hello                  # Hello, World!
$ http get :3000/hello name==Swagger    # Hello, Swagger!

Regenerating safely

The whole point of the split is that regeneration only ever touches gen/. When the spec changes, rerun the Step 2 command β€” gen/ is rewritten, your cmd/ main is untouched. Keep the two apart (generated core under gen/, your code outside it) and the two never collide.

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Project

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Project

Repository README

This site documents the go-swagger/examples repository β€” a collection of runnable, committed examples for go-swagger, the spec-first code generator for OpenAPI 2.0 (Swagger).

What’s here

Every example is a real Go project generated from an OpenAPI 2.0 spec: servers, typed client SDKs, and CLIs. The generated code is committed and kept in sync with go-swagger master by automated regeneration, so what you read on this site matches what the current generator emits.

Browse the material two ways:

  • Guides β€” reference recipes you dip into (servers, clients & CLI, authentication, streaming, customizing codegen).
  • Tutorials β€” sequential, end-to-end walkthroughs that build something from scratch.

Where this fits β€” three sibling sites

go-swagger and go-openapi split their example material across three sites by workflow. This one is the spec-first corner: you write an OpenAPI spec and generate typed Go from it.

SiteWorkflowYou start from
this sitespec-first codegenan OpenAPI 2.0 spec β†’ generated server/client/CLI
go-openapi/runtimeuntyped / hand-wiredthe runtime API, no codegen
go-openapi/codescancode-firstGo code β†’ generated spec

When a topic straddles two workflows, the page links across.

Getting started

git clone https://github.com/go-swagger/examples

You’ll need the swagger CLI on your PATH to regenerate or follow the tutorials β€” see the installation instructions. Then head to the todo-list tutorial to build a server and client from one spec.

Status & releasing

The examples track go-swagger code generation for servers and clients. The repository is deliberately left unreleased: it follows the generator on go-swagger/go-swagger@master rather than tagging versions of its own.

Licensing

This software ships under the Apache-2.0 license.

Other documentation

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

License

The go-swagger/examples repository is licensed under the Apache License, Version 2.0.

Tip

Full text: LICENSE in the repository, or the canonical apache.org/licenses/LICENSE-2.0.

SPDX headers

Every .go file in the repository β€” hand-written and generated β€” carries an SPDX header so the license is machine-verifiable per file:

// SPDX-FileCopyrightText: Copyright 2015-2026 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0

The header is part of the contribution rules and is checked in CI. Generated files carry it too, emitted by the templates, so a regeneration never strips it.

Using the examples

Apache-2.0 is a permissive license: you may use, modify, and redistribute the example code, including in commercial and closed-source work, provided you retain the license and copyright notices and state significant changes. The code is provided as-is, without warranty. See the full text for the authoritative terms.

Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Contributing

Contributions are always welcome β€” and not just code. Reporting issues, improving docs, triaging bugs, and adding test coverage all help. These guidelines are the standard ones shared across every go-openapi and go-swagger repository; if you’ve contributed to a Go project on GitHub before, you’ll feel at home.

Tip

Authoritative sources: .github/CONTRIBUTING.md and docs/STYLE.md. This page summarizes the essentials.

Git flow

Fork the repo, branch from master, and open a pull request from your fork. Branch naming is not enforced (it’s your fork), but the common convention is fix/XXX-something or feature/XXX-something, where XXX is the issue number.

Keep pull requests focused β€” small, single-purpose PRs are reviewed faster and are less likely to lose the thread than large ones.

A special note for generated code

Most of this repository is generated and must not be hand-edited β€” a regeneration would silently overwrite your change. If your contribution affects generated output:

  • change the spec or the generation command (in hack/tools/regen.go), not the generated .go files;
  • run go run ./hack/tools regen and commit the regenerated result;
  • hand-written glue (the configure_*.go files, custom handlers, main.go in the custom-server example) is editable β€” that’s the code these examples exist to illustrate.

Tests

Submit unit tests for your changes and run the full suite before opening a PR:

go test ./...

CI measures patch coverage; aim for at least 80% of your change. It’s an indicator maintainers weigh, not a hard gate.

Code style & linting

The project runs the golangci-lint meta-linter with a deliberate posture: default: all, then disable what doesn’t earn its keep. The disabled list in .golangci.yml is a design rationale, not technical debt. Two rules matter most when contributing:

  • every //nolint directive must carry an inline comment explaining why;
  • prefer disabling a linter globally over scattering //nolint β€” if a linter fights an intentional pattern, the linter goes, not the code.

Run it (and the formatter) before committing:

golangci-lint run
golangci-lint fmt

Sign your work (DCO)

Every commit must be signed off under the Developer Certificate of Origin, using your real name and email:

Signed-off-by: Joe Smith <[email protected]>

Add it automatically with git commit -s. PGP-signed commits are appreciated but not required. Squash your commits into logical units (git rebase -i) before requesting review.

AI agents

Agentic contributors are welcome, with a few rules:

  1. Issues and PRs written or posted by an agent should mention the original human poster for reference.
  2. PRs must not be attributed to an agent as author β€” no commits authored by @claude.code or similar. Agents and bots may be listed as Co-Authored-By:; the commit author must be the human sponsor.
  3. Security reports produced by an agent must be filed privately (see the security policy) and mention the human poster.
Last edited by: fredbi Jul 22, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Regeneration

The generated code in this repository is committed, yet it always reflects the current go-swagger generator. That’s the whole point of the examples: what you read here is what swagger generate emits from master today, not a frozen snapshot. A single tool keeps them in sync.

Regenerating everything

go run ./hack/tools regen

The regen command drives the whole repository from one place β€” hack/tools/regen.go holds a table with one entry per example: which directories to clean, and the exact swagger generate command(s) to run. For each example it:

  1. ensures the generator is present β€” if swagger isn’t on your PATH, it installs it from source (go install github.com/go-swagger/go-swagger/cmd/swagger@master), pinning regeneration to the latest generator;
  2. cleans the generated sub-directories (models, restapi, client, cmd, …) so nothing stale survives;
  3. runs that example’s generate command(s) β€” server, client, or both, with the flags and templates each example needs;
  4. restores any preserved hand-written files that live inside a cleaned tree;
  5. finally, once every example is regenerated, runs go test ./... across the whole module as a smoke test.

Because the command list is data, adding or changing an example’s generation is a one-line edit to that table β€” not a shell script to maintain.

Auth material for runnable examples

A few examples need secrets to actually run (TLS certificates, JWT signing keys). These are deliberately not committed. Generate them locally with the same tool:

go run ./hack/tools gen-certs    # self-signed TLS certs (todo-list-errors)
go run ./hack/tools gen-tokens   # RSA keypair + JWT tokens (composed-auth)

Automated regeneration in CI

Regeneration also runs unattended, so the committed code never drifts from the generator:

WorkflowTriggerPurpose
regen.ymlWeekly (Mon 06:00 UTC) + manualRegenerate from swagger@master; open an auto-merged PR if output changed
go-test.ymlPR, push to masterLint + build matrix (2 Go versions Γ— 3 OS)
auto-merge.ymlPRAuto-approve/merge bot PRs (dependabot, scheduled regen)
codeql.ymlPR, push, weeklyCodeQL semantic analysis
scanner.ymlPush, weeklyTrivy + govulncheck vulnerability scans
contributors.ymlWeekly + manualRefresh the all-time contributors list

On top of the weekly job, go-swagger itself triggers a cross-repo pipeline: PRs to go-swagger that touch code generation open a regeneration PR here automatically, so a generator change and its effect on the examples are reviewed together.