Go Frontend-Backend revisited: Leveraging Swagger on the Frontend

Seán Murphy
4 min readJul 3, 2020

In previous posts, I described how I put together a simple todo application which uses Go on the Frontend (FE) and AWS Lambda functions running Go on the Backend (BE). In the previous implementation, the FE-BE communication was done using the standard Go net/http mechanisms — creating a request, calling the Do() method and parsing the response. Although this worked, it was obviously suboptimal to have a Swagger defined API on the BE and not use this at all on the FE. Here I describe how I was able to use the Swagger auto-generated client code in the FE.

Generating the Swagger Client

The first step was to generate the Swagger client: in previous work, I had used the stratoscale modifications to the standard go-swagger tool as it provides a couple of nice advantages such as better logging support, easier integration into other contexts etc as described here. Generating the client code and the associated data models was a simple as running the following command in the directory containing swagger.yaml:

docker run --rm -e GOPATH=$GOPATH:/go -v $PWD:$PWD -w $PWD -u (id -u):(id -u) stratoscale/swagger:v1.0.27 generate client

In general, the content in the models directory is simply a set of types which are used in the client. The code in theclient directory is much richer containing data marshaling logic, response handling and one or mote interfaces which provide functions which call the defined REST endpoints.

An example of an interface generated for a simple todo list backend is shown below: the functions map directly to the REST endpoints defined in the swagger.yaml and specific data types are generated for parameter passing and response handling for each function in the interface.

// API is the interface of the todo client
type API interface {
/*
AddTodo adds an item to the todo list
*/
AddTodo(ctx context.Context, params *AddTodoParams) (*AddTodoCreated, error)
/*
DeleteTodo deletes a specific todo
*/
DeleteTodo(ctx context.Context, params *DeleteTodoParams) (*DeleteTodoOK, error)
/*
GetAllTodos gets todos
*/
GetAllTodos(ctx context.Context, params *GetAllTodosParams) (*GetAllTodosOK, error)
/*
GetTodo gets given todo
*/
GetTodo(ctx context.Context, params *GetTodoParams) (*GetTodoOK, error)
/*
UpdateTodo updates an item in the todo list
*/
UpdateTodo(ctx context.Context, params *UpdateTodoParams) (*UpdateTodoOK, error)
}

Using Swagger from the Frontend

Changing the FE to use the Swagger API was straightforward. It comprised two basic aspects:

  • modifying the data models in the FE to be consistent with those generated by Swagger
  • modifying the BE communication logic to use the Swagger client

Rather than replacing the FE data models with those generated by Swagger, I augmented the previous FE data models to include a BackendModel field which contained the BE representation of the data — this allowed for some (managed) divergence between the FE data model and that of the BE which could be used, for example, to handle cases when there are changes to FE data which have not yet been acknowledge by the BE.

Of course, such changes are optional — it is not absolutely necessary to have commonality between FE data models and BE data models, but it makes reasoning about the application more straightforward if there is alignment.

Introducing the Swagger client code was also straightforward: the only minor issue — as noted in a previous post —was the addition of a HTTP header to instruct the browser to perform CORS handling. In the previous implementation, this was added using the standard req.Headers.Add() when creating the HTTP request but it was not possible to do this with the Swagger client as the HTTP request generation is hidden inside the client. The solution was to use the HTTPRoundTripper interface provided by the net/http library — this is a hook which can be used to modify a HTTP request in arbitrary ways just before it is sent to a server.

The code below shows how the HTTPRoundTripper is inserted into the Swagger client: the HTTPRoundTripper interface requires a RoundTrip() function which is called after the HTTP request has been created and is ready to be sent to the BE; the extra CORS header is added to the request in this function and then the standard Do() function is then called to perform the REST API call. Adding this to the Swagger client is done by simply using an object of type BrowserCompatibleRoundTripperfor the transport in the client configuration.

type BrowserCompatibleRoundTripper struct {
}
func (rt BrowserCompatibleRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
r.Header.Add("js.fetch:mode", "cors")
resp, err := http.DefaultClient.Do(r)
return resp, err
}
func createClient() *client.SimpleTodo {
rt := BrowserCompatibleRoundTripper{}
url, _ := url.Parse(restEndpoint)
conf := client.Config{
URL: url,
Transport: rt,
}
c := client.New(conf)
return c
}

Posting a new item to the backend is then very straightforward and essentially the same as the use of the Swagger auto generated code outside a browser context; the only real difference is the use of the new transport which is captured in the createClient()function. (This, of course, assumes that there is a correct CORS configuration in both the browser application and on the REST API).

func postItemToBackend(i model.Item) {
c := createClient()
t := swaggermodel.Todo{
Completed: i.BackEndModel.Completed,
ID: i.BackEndModel.ID,
Title: i.BackEndModel.Title,
CreationDate: strfmt.DateTime(time.Now()),
}
params := developers.NewAddTodoParams()
params.Todo = &t
ctx := context.TODO()
if _, err := c.Developers.AddTodo(ctx, params); err != nil {
log.Printf("Error posting new item on backend - error %v\n", err)
}
}

Concluding remarks

Using Swagger auto generated code in the browser with the strong typing supports offered by Go is attractive. In this example, I have shown how standard Go Swagger clients can be used in the FE without difficulty. This can make FE-BE communication easier and less prone to error.

The next step is to add security to the API and determine what changes are needed on the FE and BE to support this.

Link to github repo

--

--

Seán Murphy

Tech Tinkerer, Curious Thinker(er). Lost Leprechaun. Always trying to improve.