DEV Community

Alex Spinov
Alex Spinov

Posted on

Wails Has a Free API: Build Desktop Apps With Go and Any Frontend Framework

Wails is a framework for building desktop applications using Go for the backend and web technologies for the frontend. Like Tauri but for Go developers — it uses the OS webview for tiny binaries.

Why Wails?

  • Go backend — full power of Go standard library + ecosystem
  • Any frontend — React, Vue, Svelte, Lit, vanilla JS
  • Small binaries — ~8MB (vs Electron's 150MB)
  • Native menus — OS-native menus, dialogs, file pickers
  • Cross-platform — Windows, macOS, Linux

Install & Create

go install github.com/wailsapp/wails/v2/cmd/wails@latest

# Create project (React template)
wails init -n my-app -t react-ts
cd my-app

# Dev mode with live reload
wails dev

# Build
wails build
Enter fullscreen mode Exit fullscreen mode

Go Backend (Exposed to Frontend)

// app.go
package main

import (
    "context"
    "fmt"
    "os"
)

type App struct {
    ctx context.Context
}

func NewApp() *App {
    return &App{}
}

func (a *App) startup(ctx context.Context) {
    a.ctx = ctx
}

// Exposed to frontend automatically!
func (a *App) Greet(name string) string {
    return fmt.Sprintf("Hello %s, it's nice to meet you!", name)
}

func (a *App) ReadFile(path string) (string, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return "", err
    }
    return string(data), nil
}

func (a *App) GetSystemInfo() map[string]string {
    hostname, _ := os.Hostname()
    return map[string]string{
        "hostname": hostname,
        "os":       runtime.GOOS,
        "arch":     runtime.GOARCH,
    }
}
Enter fullscreen mode Exit fullscreen mode

Frontend (TypeScript)

// frontend/src/App.tsx
import { Greet, ReadFile, GetSystemInfo } from '../wailsjs/go/main/App';

function App() {
  const [greeting, setGreeting] = useState('');

  async function handleGreet() {
    const result = await Greet('World');
    setGreeting(result);
  }

  async function handleSystemInfo() {
    const info = await GetSystemInfo();
    console.log(info); // { hostname: '...', os: 'darwin', arch: 'arm64' }
  }

  return (
    <div>
      <button onClick={handleGreet}>Greet</button>
      <p>{greeting}</p>
      <button onClick={handleSystemInfo}>System Info</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Events (Bi-directional)

// Go: emit event to frontend
import "github.com/wailsapp/wails/v2/pkg/runtime"

func (a *App) StartLongTask() {
    for i := 0; i <= 100; i++ {
        runtime.EventsEmit(a.ctx, "progress", i)
        time.Sleep(50 * time.Millisecond)
    }
    runtime.EventsEmit(a.ctx, "complete", "Done!")
}
Enter fullscreen mode Exit fullscreen mode
// Frontend: listen for events
import { EventsOn } from '../wailsjs/runtime';

EventsOn('progress', (value: number) => {
  console.log(`Progress: ${value}%`);
});

EventsOn('complete', (msg: string) => {
  console.log(msg);
});
Enter fullscreen mode Exit fullscreen mode

Native Dialogs

import "github.com/wailsapp/wails/v2/pkg/runtime"

func (a *App) OpenFile() (string, error) {
    file, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
        Title: "Select a file",
        Filters: []runtime.FileFilter{
            {DisplayName: "Text Files", Pattern: "*.txt;*.md"},
        },
    })
    return file, err
}

func (a *App) ShowMessage(title, message string) {
    runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
        Type:    runtime.InfoDialog,
        Title:   title,
        Message: message,
    })
}
Enter fullscreen mode Exit fullscreen mode

Comparison

Wails Tauri Electron
Backend Go Rust Node.js
Binary size ~8 MB ~600 KB ~150 MB
RAM ~40 MB ~30 MB ~150 MB
Learning curve Low (Go) Medium (Rust) Low (JS)

Resources


Building desktop or web tools? Check my Apify actors or email spinov001@gmail.com.

Top comments (0)