API

RESTful API: go-swagger

Swagger is a powerful tool used to represent an API. It grants you the capability to layout and organize your API’s endpoints and responses and then displays an interactive documentation of it.

Swagger allows users to generate code for the client or server API. Some languages or frameworks that have support for this include: NodeJS, Java’s Spring, Python’s Flask, and many more. This example will cover Go. Instead of using Swagger’s built-in code generator for Go, we will be using the opensource package: go-swagger. This package has more features and a lot of support from programmers throughout the Go community.

We are going to make a very simple dog creation API with one GET and one POST request. We will POST our dog’s name to the server and it will randomly create a dog for us. Then we will GET our dog from the server by finding it by it’s name. The good news is that the majority of the code will be generated for us by Swagger and we will mostly only have to worry about our additional randomization functionality.

Using the Swagger Editor

To start, open the Swagger Online Editor. If you never opened this editor before, there is probably already a default API example showing. We are going to start from scratch, so in the editor do:

File > Clear Editor

Now we can begin our Swagger Document Specification. The start of the document is just a simple setup. Most of the below specs are self-explanatory, but I will explain a few.

swagger: "2.0"
info:
  description: "A simple API which allow users to create a random dog with a chosen name and then retrieve that dog's information by it's name."
  version: "1.0.0"
  title: "Dog Creator"
  termsOfService: "http://mydogcreator.com/terms/"
  contact:
    email: "developers@mydogcreator.com"
  license:
    name: "Apache 2.0"
    url: "http://www.apache.org/licenses/LICENSE-2.0.html"
host: "mydogcreator.com"
basePath: "/api/v1"
tags:
- name: "dog"
  description: "Everything about your Pets"
schemes:
- "http"

The swagger version should be 2.0 to work with the latest go-swagger code generator, schemes might also include https if your site needs to be secure, and also basePath is what each API endpoint will extend, ie: localhost:3000/api/v1/myEndpoint

Now we can make our first endpoint. Our POST endpoint will create a random dog with a name of our choice and it will look like the following:

paths:
  /dog/create/{name}:
    post:
      tags:
      - dog
      summary: Create a dog
      description: Creates a dog with random properties and the given name
      parameters:
      - in: path
        name: name
        required: true
        type: string
      responses:
        200:
          description: Random dog created

The name parameter is a string of whatever we want to call our dog. When we hit the endpoint we will include that name.

Now we need a way to retrieve our new dog. This will be a GET endpoint and it will look like this:

/dog/find/{name}:
    get:
      tags:
      - dog
      summary: Find a dog by name
      description: Return a randomly created dog that has the given name
      produces:
        - application/json      
      parameters:
      - in: path
        name: name
        required: true
        type: string
      responses:
        200:
          description: Found dog
          schema:
            $ref: '#/definitions/Dog'
        404:
          description: Dog name not found

In this spec, we want to return a Dog schema if the endpoint responds OK. We can define schema definitions at the bottom of our swagger document like so:

definitions:
  Dog:
    type: object
    properties:
      name:
        type: string
      breed:
        type: string
      color:
        type: string
      age:
        type: integer
        format: int8

This Dog object will actually become a struct in Go when we generate our code with go-swagger. If you copy and pasted every swagger code block into the editor then you should now be good to move on with the code generation process.

Generating the Go Code from Swagger

Create a directory inside your GOPATH called “dogcreator”. Inside that directory all you need is a single file called “swagger.yml”. Copy and paste everything from your Swagger Online Editor into this swagger.yml file.

Next, open terminal, navigate inside the “dogcreator” directory and type:

swagger validate ./swagger.yml

Even though your online editor already validates your swagger syntax it’s still good to double check everything is correct.

Finally, type the following to generate your server:

swagger generate server -A dogcreator -f ./swagger.yml

The -A flag is the server’s name and the -f flag uses the filename specified for your specifications.

Go-swagger should have generated the three folders and your structure should be:

.dogcreator
| cmd/
| models/
| restapi/
| swagger.yml

You can view the models/dog.go to see the Dog struct that was created from your Dog schema in the swagger specification. The endpoints generated can be viewed in restapi/configure_dogcreator.go. This file is expected to be edited, while most other swagger generated files should not be edited because they will be overwritten the next time you generate a server.

To start create two files in restapi/operations/dog, call them “handler_create.go” and “handler_find.go”.

Copy and paste this into the handler_create.go file:

package dog

import (
 "math/rand"
 "time"
 "github.com/YOUR_PARENT_FOLDER/dogcreator/models"
 "github.com/go-openapi/runtime/middleware"
)

var breeds = [5]string{"Beagle", "Bulldog", "Pug", "Poodle", "Husky"}
var colors = [6]string{"Brown", "Black", "White", "Yellow", "Tan", "Cream"}
// DogList is a non-persistent list of all the dogs that have been created
// so far in this session
var DogList = make(map[string]*models.Dog, 0)
// HandleCreate creates a dog with the specified name and fills the other dog
// fields with random values
func HandleCreate(name string) middleware.Responder {
 rand.Seed(time.Now().Unix())
 dog := models.Dog{
 Name:  name,
 Age:   int8(rand.Intn(19) + 1),
 Breed: breeds[rand.Intn(len(breeds))],
 Color: colors[rand.Intn(len(colors))],
 }
 DogList[name] = &dog
 return NewPostDogCreateNameOK()
}

Notice my models, import says YOUR_PARENT_FOLDER. This is because my dogcreator folder is inside another directory which is my name. If you have a folder after github.com then rename it to that or just remove it if dogcreator is the top-most directory.

This file makes a function HandleCreate which accepts a name. It creates a dog with that name, gives it a random age from 1-20, gives it a random breed from 5 possible choices, and gives it a random color of 6 possible choices. Finally it adds that dog to the master dog map which will later allow us to find our dog by it’s name.

Copy and paste the following into the handler_find.go file:

package dog

import "github.com/go-openapi/runtime/middleware"

// HandleFind searches the list of dogs with the name given and returns the
// dog if found
func HandleFind(name string) middleware.Responder {
	if dog, ok := DogList[name]; ok {
		response := NewGetDogFindNameOK()
		response.Payload = dog
		return response
	}
	return NewGetDogFindNameNotFound()
}

This file makes a function HandleFind which also accepts a name. It tries to index the DogList from the previous file with the name. If there is a match, then it returns the dog that was found, if not then it returns a 404 error.

Lastly, open the restapi/configure_dogcreator.go file.

Change:

api.DogGetDogFindNameHandler = dog.GetDogFindNameHandlerFunc(func(params dog.GetDogFindNameParams) middleware.Responder {
  return middleware.NotImplemented("operation dog.GetDogFindName has not yet been implemented")
})
api.DogPostDogCreateNameHandler = dog.PostDogCreateNameHandlerFunc(func(params dog.PostDogCreateNameParams) middleware.Responder {
  return middleware.NotImplemented("operation dog.PostDogCreateName has not yet been implemented")
})

To:

api.DogGetDogFindNameHandler = dog.GetDogFindNameHandlerFunc(func(params dog.GetDogFindNameParams) middleware.Responder {
  return dog.HandleFind(params.Name)
})
api.DogPostDogCreateNameHandler = dog.PostDogCreateNameHandlerFunc(func(params dog.PostDogCreateNameParams) middleware.Responder {
  return dog.HandleCreate(params.Name)
})

This makes our two endpoints use the two new functions we just created.

Running our API

Finally we are ready to run our applications!

From the top level of the dogcreator directory, run:

go run cmd/dogcreator-server/main.go --port=8080

This will start our API server on localhost:8080

Let’s try creating a dog with our POST. Open up your terminal and type the following:

curl -X POST http://127.0.0.1:8080/api/v1/dog/create/Spike

This will create a dog named Spike and the rest of Spike’s attributes will be determined randomly.

Now let’s try finding Spike with our GET. Open up your browser and go to the following: http://127.0.0.1:8080/api/v1/dog/find/Spike

Our responses will be different because it’s randomized values, but you should receive something like this:

{
  "age": 9,
  "breed": "Bulldog",
  "color": "Black",
  "name": "Spike"
}

Congratulations you just created a API in Go that used both GET and POST with very minimal work!