📖 2 min read (~ 400 words).

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.