Building Web Applications In Pure Go
Motivation
Nowadays it seems that a language is really only complete once it
has it's own JavaScript
transpiler. Go is one of my favorite
programming languages. Not only for it's simplicity, readability and
toolset but for it's absolute outstanding standard library. Of
course Go is no exception for the JavaScript
transpiler rule. To
transpile Go into JavaScript Gopherjs
(written by Richard Musiol)
is your go to solution. While I thought some time about writing an
application using Gopherjs
I never had the right problem that
could be addressed using Gopherjs
.
Recently I had to deal with a lot of MAIL attachments that exceed the recommended size which should be send per mail. I thought about a public upload solution that would allow any sender to easily post me files of (seemingly) arbitrary size. As having a public upload service could cause a lot of security problems (attackers could exploit the service and spam my hard disk) I needed some sort of authentication. Though I didn't want to force users of the service to enter a username and password. Therefore I developed tokenshare. Using tokenshare I can simply create a token that is referenced by a unique ID. Now I can simply share this token with any user which then may may silently login to the system.
This sort of problem seemed like the right problem to be approached
by Gopherjs
. Using Gopherjs I am able to write the whole
application in pure Go. Even further Gopherjs
is able to present
the user with severally additional user written libraries that
provide Go bindings to popular JavaScript
libraries. However I
only utilized Dominik Honnef's HTML DOM bindings to minimize code
size and strengthen type safety.
The beauty of Gopherjs
is that I can use standard Go code to
implement the client logic. While this sounds obvious the
implications are quiet amazing: I am able to easily write unit tests
for the server which utilizes the same code the client uses. This
means that I can implement the server, write the client
implementations and test everything within a single set of unit test
without even thinking about the DOM or JavaScript. Afterwards the
only thing left is writing a thin layer around that client
implementation that deals with DOM/HTML API interaction.
Furthermore I may use the same types the server uses without even
thinking about changes to the type model. A single go build for
the whole project informs me about a possible code part that relies
on parts of the model I just changed. I don't have to rely on
undependable string-replacements inside the JavaScript
code.
Concept
The applications splits of into two user interfaces:
- an admin page that presents options to create tokens, lists all available tokens and respective download links and presents the user with the link for each token.
- an upload page that provides the end user with the ability to upload the actual file.
Figure 1: Sequence Diagram of Application Lifecycle
All access to the admin page is authenticated using basic HTTP authentication. All requests done by the upload page are restricted to the token ID passed along side to the user. Hence I can create a token while authenticated by Basic HTTP Auth and send the token ID to any user which then may upload the file in question. The idea is that I can now easily create a token per email contact and simply send it withing my email signature. That way if a contact needs to send me a larger file he can use the token I provided for him.
Implementation
As previously stated I started doing this by simply writing the
server and respective database access while simultaneously writing
unit tests. Hence I created to Go packages for the server
tokenshare
as well as tokenshare/backend
. The client
implementation that I needed to write the unit tests are defined in
the tokenshare
package while server and unit test implementation
are obviously defined in tokenshare/backend
. I am now able to
easily create a thin layer on top of the client implementation
already created that deals with DOM interaction.
Whats especially great about Goppherjs is that I still get to use goroutines and channels to do asynchronous calls to the server. Therefore I am able to define a function which uploaded the provided data and sends the transfer progress on a channel.
func Transfer(call, name, id string, data []byte, progress chan int) error {}
Reading this channel I can push the process updates to the dom and give the user feedback about the upload status:
func (c Client) upload(data []byte, name, id string, progress func(int)) error { pr := make(chan int) defer close(pr) go func() { for i := range pr { progress(i) } }() return Transfer(ReqTransfer, name, id, data, pr) }
Having the power of Go's concurrency patterns is a big advantage and
made the implementation of the client quite simple and
straightforward. The Client
type implements several methods that
implement communications with the server while updating the status
to DOM elements. For instance the previously used progress
function just consists of:
func (c Client) progress(total int, div *dom.HTMLDivElement) func(int) { return func(p int) { div.SetInnerHTML(fmt.Sprintf("Progress: %d%%", int(float64(p)/float64(total)*100))) } }
The DOM-bindings package allow a clean and easy way to interact with
the user interface. Based on the methods defined on the Client
type the packages tokenshare/app
as well as tokenshare/upload
are defined. These packages are compiled using the Gopherjs utility
and provide the functionality for the Admin and the User.
Conclusion
Programming in Go over JavaScript provides a lot of advantages, type safety, code sharing, or access to most of the Go standard library. One set of unit tests cover the client and server code. Thus the client implementation leaves less room for error. Additionally not having to switch constantly between two languages makes a big different.
I have to also mention that having the complete Go toolset available
for the client implementation, like go vet
, errcheck
, gofmt
and so forth is pretty amazing. This allows me to write clean code
in a fast pace without dealing with Javascript characteristics.
The code is available at github.com/jostillmanns/tokenshare.