Building Your First Wails App: Beyond Basics
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:
Minify Frontend Assets: Ensure your frontend build process compresses JavaScript, CSS, and other assets.
Lazy Loading: Use lazy loading for React components to reduce initial load times.
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. 🚀