In Part 1, we explored the basics of setting up a Wails project and replacing an Electron app. Now, in Part 2, we’ll dive deeper into Wails’ capabilities by extending our initial application. This guide will cover:

  • Integrating external APIs.

  • Handling complex backend logic.

  • Packaging your app for cross-platform distribution.

Step 1: Extending Backend Logic

To make your Wails app more functional, you’ll need a robust backend. Let’s expand our simple greeting app to fetch a random joke from a public API.

Update the Backend

Modify main.go to include HTTP requests using Go’s net/http package:

package main

import (
	"context"
	"embed"
	"encoding/json"
	"io"
	"net/http"

	"github.com/wailsapp/wails/v2"
	"github.com/wailsapp/wails/v2/pkg/options"
	"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)

//go:embed all:frontend/dist
var assets embed.FS

// App struct
type App struct {
	ctx context.Context
}

type Joke struct {
	Setup     string `json:"setup"`
	Punchline string `json:"punchline"`
}

// NewApp creates a new App application struct
func NewApp() *App {
	return &App{}
}

// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
	a.ctx = ctx
}

// FetchJoke retrieves a random joke from an external API
func (a *App) FetchJoke() (string, error) {
	resp, err := http.Get("https://official-joke-api.appspot.com/jokes/random")
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	var joke Joke
	if err := json.Unmarshal(body, &joke); err != nil {
		return "", err
	}

	marshal, err := json.Marshal(joke)
	if err != nil {
		return "", err
	}

	return string(marshal), nil
}

func main() {
	// Create an instance of the app structure
	app := NewApp()

	// Create application with options
	err := wails.Run(&options.App{
		Title:  "notes",
		Width:  1024,
		Height: 768,
		AssetServer: &assetserver.Options{
			Assets: assets,
		},
		BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
		OnStartup:        app.startup,
		Bind: []interface{}{
			app,
		},
	})

	if err != nil {
		println("Error:", err.Error())
	}
}

Here, we’ve added a FetchJoke function that calls an external API to retrieve a random joke.

Step 2: Enhance the Frontend

Now let’s create a button in the frontend to fetch and display the joke.

Update frontend/src/App.tsx

Modify your React code to include the new API call:

import {useState} from 'react';
import logo from './assets/images/logo-universal.png';
import './App.css';
import {FetchJoke} from "../wailsjs/go/main/App";

function App() {
    const [joke, setJoke] = useState({
        setup: "Click the button to hear a new joke.",
        punchline: ""
    });
    const updateResultText = (result: string) => setJoke(JSON.parse(result));

    function fetchJoke() {
        FetchJoke().then(updateResultText);
    }

    return (
        <div id="App">
            <img src={logo} id="logo" alt="logo"/>
            <div id="input" className="input-box">
                <button className="btn" onClick={fetchJoke}>Joke</button>
            </div>
            <div id="setup" className="setup">{joke.setup}</div>
            <div id="punchline" className="punchline">{joke.punchline}</div>
        </div>
    )
}

export default App

This simple addition allows your app to interact with the backend API and display the results.

Step 3: Cross-Platform Packaging

One of Wails’ strengths is its ability to build native executables for multiple platforms.

Build for macOS, Windows, and Linux

Run the following command to generate executables:

wails build -platform all

This will produce platform-specific binaries in the build/bin directory. If you need a binary for a specific OS, use:

wails build -platform darwin   # macOS
wails build -platform windows  # Windows
wails build -platform linux    # Linux

Customize Build Options

Customize your build settings in the wails.json file to include metadata like your app's icon, version, and more:

Step 4: Optimizing Performance

Wails is inherently lightweight, but you can further optimize:

  1. Minify Frontend Assets: Ensure your frontend build process compresses JavaScript, CSS, and other assets.

  2. Lazy Loading: Use lazy loading for React components to reduce initial load times.

  3. Backend Profiling: Use Go’s pprof package to identify and resolve bottlenecks.

Step 5: Adding Advanced Features

Now that your app is functional, consider adding advanced features such as:

  • Database Integration: Use SQLite or PostgreSQL to store persistent data.

  • Notifications: Integrate desktop notifications using Wails’ native capabilities.

  • Offline Mode: Cache essential data for offline access.

Conclusion

By following this guide, you've extended your basic Wails app into a more functional, real-world application. From API integration to cross-platform packaging, Wails provides a streamlined and powerful framework for building desktop apps.

Stay tuned for Part 3, where we’ll explore adding offline functionality and native integrations for a fully polished application. 🚀

Previous
Previous

Part 3: Enhancing Your Wails App with Offline Functionality and Native Integrations

Next
Next

Building an Advanced Note-Taking App with Wails: Part 1