Calculator Service in Go

· 4mins

In this chapter, we’ll explore how to communicate with the Wails backend using Services.
We’ll create a minimal calculator service in our Go backend, capable of interpreting basic mathematical tokens, constructing an expression tree, and evaluating it to compute the final result.

Interfaces available in Service

The following are the one’s I’m aware of. There may be others, and I’ll update this documentation as I encounter more interfaces within the Wails codebase that can be extended to customize the Service’s behavior.

ServiceStartup
Services can implement APIs from this interface. If a service implements the OnStartup method, it will be invoked when the App{} instance is created, even before a webview window is opened.

http.Handler
Any service that extends http.Handler interface’s ServeHTTP handler, will be bound with service’s provided Route and it’s custom ServeHTTP handler.

Sample

type Custom struct {}

// For every request to `/custom/path`, this handler will be called
func (c *Custom) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	// ...
}

application.NewService(
	&Custom{},
	application.ServiceOptions{ Route: "/custom/path", Name: "custom" }
)

Creating a Service

Creating a service in Wails is straightforward. A service is simply a struct with public methods. Each public method becomes a service API that can be called from JavaScript, similar to how you use the fetch API in JavaScript to interact with backend endpoints.

Here’s how to define the struct for a service that we’ll later extend with APIs:

// in services/calculator/main.go
type Calculator struct {
	ExpressionTree *Node
}

func New() *Calculator {
	return &Calculator{}
}


Instantiate the Struct as a Service when creating the Wails App instance

// in main.go
func main() {
	// ...
	app := application.New(application.Options{
		// ...
		Services: []application.Service{
			application.NewService(calculator.New()),
		},
		// ...
	})
	// ...
}

You can find better examples on how to work with Services in Wails v3-alpha branch

Creating APIs

I have created a new package called calculator which resides under services folder (or parent package). The package exposes APIs for basic calculator functionality.

// in services/calculator/main.go
// ...
func (calc *Calculator) Insert(in string) {
	newNode := &Node{Token: *NewToken(in)}
	if calc.ExpressionTree == nil {
		calc.ExpressionTree = newNode
	} else {
		calc.ExpressionTree = calc.insertNode(calc.ExpressionTree, newNode)
	}
}

func (calc *Calculator) Evaluate() float64 {
	return evaluateNode(calc.ExpressionTree).Value
}

func (calc *Calculator) Reset() {
	calc.ExpressionTree = nil
}
// ...

Remember: Only public methods of a struct (not standalone functions) are considered Service APIs.

Using Wails generated bindings in Frontend

We can consume these exposed Service APIs in JS as follows:

import { type EventHandler } from "svelte/elements";
import { Calculator } from "$lib/wailsjs/learn-wails/services/calculator";
import { calculatorStore } from "./store.svelte";

// ...

const evaluate = async () => {
	calculatorStore.lastResult = await Calculator.Evaluate();
	calculatorStore.number = "0";
};

const handleOperatorInput: EventHandler<Event, HTMLButtonElement> = async (ev) => {
	const input = ev.currentTarget.dataset["key"];
	if (input == null) return;
	await Calculator.Insert(calculatorStore.number);
	await Calculator.Insert(input);
	await evaluate();
};

const resetHandler: EventHandler<Event, HTMLButtonElement> = async (ev) => {
	await Calculator.Reset();
	calculatorStore.reset();
};

// ...

Result

Since including the entire code example would take up too much space here, I created a separate commit to demo this change.

Go vs NodeJS

Are there any benefits of using Golang over NodeJS or any other JS runtimes for the Backend?

Initially, I believed that Go would significantly outperform JavaScript runtimes. However, after researching, I realised that performance often depends more on the overall codebase and its architecture than the language itself—especially as a project scales.

Please don’t take the above comment personally, I like both the languages. Scripted and Compiled languages will always have performance differences, but writing better code and ultimately creating a good product out of it is the only thing that should matter.

I found this helpful SO thread that helped me stop worrying about the performance too much.
You can find a lot of such comparisons all over the place:

While performance benchmarks can be interesting, I’ve come to understand that performance isn’t always the primary concern when building a business. Factors like maintainability, scalability, developer experience and many others often weigh more heavily.

Why I chose Go

  • Simplicity and Ease of Use: Go is much simpler to learn and write compared to alternatives like Rust.
  • Quick Prototyping: Wails with Go offers a sweet spot for rapid development of hybrid applications.
  • Scalability: Go performs well on both single-core and multi-core systems, provided the code is well-written.
  • Developer-Friendly: Rust, while powerful, presents a steeper learning curve. Go allowed me to quickly turn my ideas into working prototypes.

I was previously thinking of learning Tauri and I did a bit as well, but the steep learning curve of Rust makes it challenging for me at this time. I wanted to quickly prototype my ideas and Wails with Go was an ideal choice when comparing any other Hybrid Application development frameworks.

Every language has its pros and cons, and there are many excellent blogs that delve deeper into these comparisons. Ultimately, I chose Go for its simplicity, efficiency, and suitability for my needs.

Further Reading: