Hey,

I’ve been recentely developing, once again, a service that needs to expose an HTTP api which has to be publicly exposed and well documented. Guess what?! That’s not new! Write a new service that talks via HTTP and you’ll have to document the interface and write, again, all the same boilerplate for dealing with logging requests, handling preflight requests made by browsers, checking parameters and so on and so on.

The greatness of using swagger is that we can simply skip all of that part and write a swagger.yml (pretty declarative) which contains the definition of all of our endpoints.

Given the definition, it generates stub code that we implement in our servers in the language you want (you’d probably do this only one time) and then have the client code generated for as many languages as there are swagger generator.

Illustration of swagger process

Aside from that, documentation is all done for you - write a decent swagger.yml and you can have automatic documentation generated.

In this tutorial I go through what the process of creating a simple server looks like using go.

ps.: if you want to check the final result, see github.com/cirocosta/hello-swagger.

Getting go-swagger

First step is getting the swagger command line interface.

If you already have go, use go get:

go get -u -v github.com/go-swagger/go-swagger/cmd/swagger

swagger --help

Usage:
  swagger [OPTIONS] <command>

Swagger tries to support you as best as possible when building APIs.

It aims to represent the contract of your API with a language agnostic description of your application
in json or yaml.

...

The tools gives us a handful of commands aimed at generating and validating configurations as well as producing code according to an API description.

Initializing the Golang project

To initialize the project, first create a repository in your $GOPATH. In my case:

cd go/src/github.com/cirocosta
mkdir hello-swagger
cd ./hello-swagger

The next step depends on how you manager your project dependencies. I’ve been using Masterminds/glide for a long time so that’s what I’ll be using (feel free to even not vendor your dependencies if you don’t think that’s a necessary thing).

glide init

$ tree
.
├── glide.lock
└── glide.yaml

The first dependency I’ll retrieve is go-arg, which gives us a simple way of retrieving flags from a CLI application.

glide get github.com/alexflint/go-arg

[INFO]	Preparing to install 1 package.
[INFO]	Attempting to get package github.com/alexflint/go-arg
...
[INFO]	--> Exporting github.com/alexflint/go-arg
[INFO]	Replacing existing vendor dependencies
..

With it we can create main.go, the entrypoint of our application:

// main declares the CLI that spins up the server of
// our API.
// It takes some arguments, validates if they're valid
// and match the expected type and then intiialize the
// server.
package main

import (
	"fmt"
	"github.com/alexflint/go-arg"
)


// cliArgs defines the configuration that the CLI
// expects. By using a struct we can very easily
// aggregate them into an object and check what are
// the expected types.
// If we need to mock this later it's just a matter
// of reusing the struct.
type cliArgs struct {
	Port int `arg:"-p,help:port to listen to"`
}

var (
	// args is a reference to an instantiation of
	// the configuration that the CLI expects but
	// with some values set.
	// By setting some values in advance we provide
	// default values that the user might provide
	// or not.
	args = &cliArgs{
		Port: 8080,
	}
)

// main parses the arguments from the CLI as specified
// by our configuration described in `cliArgs` and then
// populates the `args` reference we defined in the `vars`
// section above.
func main () {
	arg.MustParse(args)
	fmt.Printf("port=%d\n", args.Port)
}

To install it by running a single command we can tailor a Makefile:

install:
	go install -v

.PHONY: install

and then check if all went well:

make
hello-swagger --help

Usage: hello-swagger [--port PORT]

Options:
  --port PORT, -p PORT   port to listen to [default: 8080]
  --help, -h             display this help and exit

hello-swagger --port 1337
port=1337

With the CLI configured, let’s move to the actual API and code generation.

Define the API and generate the code

.
├── Makefile            # makefile that summarizes the
│                       # build and code generation procedures
│       
├── glide.lock          # a lock-file with all the dependencies
│                       # specified with their respective versions
│       
├── glide.yaml          # a high-level definition of our directly
│                       # imported dependencies
│                       
├── main.go             # our application entrypoint
│       
├── swagger             # swagger definition and generated code
│   │       
│   └── swagger.yml
└── vendor              # dependencies
    ├── github.com
    └── ...
swagger: "2.0"
info:
  title: 'Hello'
  version: '0.0.1'
paths:
  /hostname:
    get:
      operationId: 'getHostname'
      produces:
      - 'text/plain'
      responses:
        200:
          description: 'returns the hostname of the machine'
          schema:
            description: 'the hostname of the machine'
            type: 'string'

Update the Makefile to properly produce the code that our server will consume and nothing more

# The `validate` target checks for errors and inconsistencies in 
# our specification of an API. This target can check if we're 
# referencing inexistent definitions and gives us hints to where
# to fix problems with our API in a static manner.
validate:
	swagger validate ./swagger/swagger.yml


# The `gen` target depends on the `validate` target as
# it will only succesfully generate the code if the specification
# is valid.
# 
# Here we're specifying some flags:
# --target              the base directory for generating the files;
# --spec                path to the swagger specification;
# --exclude-main        generates only the library code and not a 
#                       sample CLI application;
# --name                the name of the application.
gen: validate
	swagger generate server \
		--target=./swagger \
		--spec=./swagger/swagger.yml \
		--exclude-main \
		--name=hello


# just added `gen` and `validate`
.PHONY: install gen validate

Now generate the swagger code

make gen
swagger generate server \
		--target=./swagger \
		--spec=./swagger/swagger.yml \
		--exclude-main \
		--name=hello
2017/10/24 14:57:06 building a plan for generation
2017/10/24 14:57:06 planning definitions
2017/10/24 14:57:06 planning operations
2017/10/24 14:57:06 grouping operations into packages
...
  * github.com/jessevdk/go-flags

You can get these now with: go get -u -f swagger/...
...
  1. Check that it has been generated
$ tree -L 3
.
├── Makefile
│   ...
├── main.go
└── swagger
    ├── restapi                 # code generated by
    │   │                       # go-swagger.
    │   ├── configure_hello.go
    │   ├── doc.go
    │   ├── embedded_spec.go
    │   ├── operations
    │   └── server.go
    └── swagger.yml             # our definition

Add dependencies to main and let glide update your vendor

// ...

import (
	"github.com/alexflint/go-arg"

        _ "github.com/cirocosta/hello-swagger/swagger/models"
        _ "github.com/cirocosta/hello-swagger/swagger/restapi"
        _ "github.com/cirocosta/hello-swagger/swagger/restapi/operations"
        _ "github.com/go-openapi/loads"
        _ "github.com/go-openapi/runtime/middleware"
        _ "github.com/go-openapi/swag"
)

// ...
$ glide up -v
[INFO]	Downloading dependencies. Please wait...
[INFO]	--> Fetching updates for github.com/alexflint/go-arg
...
[INFO]	--> Fetching updates for github.com/go-openapi/validate
[INFO]	--> Fetching updates for golang.org/x/net
..
[INFO]	Project relies on 24 dependencies.
..

$ tree -L 2
.
├── Makefile
├── glide.lock
├── glide.yaml
├── main.go
├── swagger
│   ├── restapi
│   └── swagger.yml
└── vendor
    ├── github.com      # some new here as well
    ├── golang.org      # new
    └── gopkg.in        # new

Implement the functionality

Now that we have all the dependencies set up we need to create the server and implement the handler functionality.

package main

import (
	"log"
	"os"

	"github.com/alexflint/go-arg"
	"github.com/cirocosta/hello-swagger/swagger/models"
	"github.com/cirocosta/hello-swagger/swagger/restapi"
	"github.com/cirocosta/hello-swagger/swagger/restapi/operations"
	"github.com/go-openapi/swag"
	"github.com/go-openapi/loads"
	"github.com/go-openapi/runtime/middleware"
)

type cliArgs struct {
	Port int `arg:"-p,help:port to listen to"`
}

var (
	args = &cliArgs{
		Port: 8080,
	}
)

func main() {
	arg.MustParse(args)

	swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
	if err != nil {
		log.Fatalln(err)
	}

	api := operations.NewHelloAPI(swaggerSpec)
	server := restapi.NewServer(api)
	defer server.Shutdown()

	server.Port = args.Port


        // Implement the handler functionality.
        // As all we need to do is give an implementation to the interface
        // we can just override the `api` method giving it a method with a valid
        // signature (we didn't need to have this implementation here, it could
        // even come from a different package).
	api.GetHostnameHandler = operations.GetHostnameHandlerFunc(
		func(params operations.GetHostnameParams) middleware.Responder {
			response, err := os.Hostname()
			if err != nil {
				return operations.NewGetHostnameDefault(500).WithPayload(&models.Error{
					Code: 500,
					Message: swag.String("failed to retrieve hostname"),
				})
			}

			return operations.NewGetHostnameOK().WithPayload(response)
		})

        // Start listening using having the handlers and port 
        // already set up.
	if err := server.Serve(); err != nil {
		log.Fatalln(err)
	}
}

To verify whether it’s all fine:

# in one terminal session
$ hello-swagger          
2017/10/24 15:32:47 Serving hello at http://[::]:8080

# in another terminal session
$ hostname
cirocosta.local

$ curl localhost:8080/dadsadas
{"code":404,"message":"path /dadsadas was not found"}

$ curl localhost:8080/hostname
cirocosta.local

Done