I was looking for an all-in-one solution for building apps with golang. Then I found Wails, it is claimed as a cross-platform framework for building apps with Go as backend. On the frontendside, it allows you to use modern web technologies such as Vue.js, React.js, and Angular.js. It sounds fancinating, so I decided to give it a try and dig a little bit on how it works.

Without Wails

To build a full stack app with go is not impossible without using frameworks like wails. If you are convenient of using pure html,css and javascript, you can just use net/http package to build a web server and serve your static files. In terms of dynamic data, you can consume the backend api defined in the same package. The only third party package you might need is gorilla/mux which offers a powerful router for your backend api.
Actually building app in this way seems more go-ish and potentially win you more credits from go community. However, when you have to quickly build a prototype or you are not familiar with frontend technologies, you might want to use a framework to help you out. So you turn to React, Vue or Angular which have numerous established libraries and components.

If you still need to use standard go library to serve frontend files, you need to build your frontend app first and then copy the build files to your backend project. You can use tools like vite(recommended) to build your frontend into static files. The backend api still not changed.

That is a viable solution for most projects. However you may want to go step further and wonder if we have to define backend api at all. Is there a method that we can directly from frontend to backend without defining api? The answer is yes, and that is what wails does.

How Wails works

In its documents it has an article explaining what the structure of a wails app looks like. The key takeaways I list below:

  • The frontend is a webkit window displaying frontend assets and has access to a JavaScript version of the runtime library.
  • Go methods can be bound to the frontend, allowing them to be called as JavaScript methods.
  • Method binding exposes struct methods to the frontend, making it easy to call Go code from JavaScript.
  • TypeScript declarations are generated for bound methods, ensuring strong typing.

This is in essence a “Nextjs” in go world. However I’m curious on how the bound works. So I dig into the source code and found the answer.

How the call happens

It is not too difficult to find out where the magic call happens in the source code. The key file is boundMethod.go where you can find the Call method


func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
	expectedInputLength := len(b.Inputs)
	actualInputLength := len(args)
	if expectedInputLength != actualInputLength {
		return nil, fmt.Errorf("%s takes %d inputs. Received %d", b.Name, expectedInputLength, actualInputLength)
	}

	/** Convert inputs to reflect values **/

	// Create slice for the input arguments to the method call
	callArgs := make([]reflect.Value, expectedInputLength)

	// Iterate over given arguments
	for index, arg := range args {
		// Save the converted argument
		callArgs[index] = reflect.ValueOf(arg)
	}

	// Do the call
	callResults := b.Method.Call(callArgs)

	//** Check results **//
    ...
}

right, so it uses reflection to call the method. It is not a surprise as reflection seems the only way to call a method without having an api defined. It is indeed a clever idea. However I would have some concerns over the performance if this been used to build web apps.

Generate typescript declaration

With reflection it’s possible to call a method with the signature as the parameter. That means you can generate js/ts declaration based on the go struct method signature. In the same package as the boundMethod.go, there is a file generate.go which does the trick. It is not trivial process but luckily it only needs to be done once in the build stage.

That explains what happens when you run wails dev or wails build. It will first look for the Bind and struct defination in the go code and then generate js/ts methods using go reflection tools under the frontend folder.

Multi-page?

The official demo shows a single page app. But I wonder if this also applies to multi-page apps using routers e.g. react-router. So I created a simple app adding router. It works most of time, but I also run into weird issues like this one

Unexpected Application Error!
Cannot read properties of undefined (reading 'main')

It happens on the sub pages when I try to refesh the page. This looks like the binding works not properly when third party route library is introduced.I’m not sure if it is because I didn’t use the route correctly, however I didn’t find related docs on how to set up routes. also it didn’t say it supports multi-page app.

Conclusion

After some playaround with wails, I think first of all it fills the gap of building full stack app with go. If I build a single page desktop app, I would definitely use it. However, if you intend to build a cross platform app with multiple pages, I would suggest you investigate a little more before you decide to use it. Good news is the author is actively developing v3 which I would definitely keep an eye on. Btw there is a benchmark on desktop frameworks here which includes wails as well. It is worth to take a look if you are interested in this topic.