--- title: Method Bindings description: Call Go methods from JavaScript with type safety sidebar: order: 1 --- import { FileTree, Card, CardGrid } from "@astrojs/starlight/components"; ## Type-Safe Go-JavaScript Bindings Wails **automatically generates type-safe JavaScript/TypeScript bindings** for your Go methods. Write Go code, run one command, and get fully-typed frontend functions with no HTTP overhead, no manual work, and zero boilerplate. ## Quick Start **1. Write Go service:** ```go type GreetService struct{} func (g *GreetService) Greet(name string) string { return "Hello, " + name + "!" } ``` **2. Register service:** ```go app := application.New(application.Options{ Services: []application.Service{ application.NewService(&GreetService{}), }, }) ``` **3. Generate bindings:** ```bash wails3 generate bindings ``` **4. Use in JavaScript:** ```javascript import { Greet } from './bindings/myapp/greetservice' const message = await Greet("World") console.log(message) // "Hello, World!" ``` **That's it!** Type-safe Go-to-JavaScript calls. ## Creating Services ### Basic Service ```go package main import "github.com/wailsapp/wails/v3/pkg/application" type CalculatorService struct{} func (c *CalculatorService) Add(a, b int) int { return a + b } func (c *CalculatorService) Subtract(a, b int) int { return a - b } func (c *CalculatorService) Multiply(a, b int) int { return a * b } func (c *CalculatorService) Divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil } ``` **Register:** ```go app := application.New(application.Options{ Services: []application.Service{ application.NewService(&CalculatorService{}), }, }) ``` **Key points:** - Only **exported methods** (PascalCase) are bound - Methods can return values or `(value, error)` - Services are **singletons** (one instance per application) ### Service with State ```go type CounterService struct { count int mu sync.Mutex } func (c *CounterService) Increment() int { c.mu.Lock() defer c.mu.Unlock() c.count++ return c.count } func (c *CounterService) Decrement() int { c.mu.Lock() defer c.mu.Unlock() c.count-- return c.count } func (c *CounterService) GetCount() int { c.mu.RLock() defer c.mu.RUnlock() return c.count } func (c *CounterService) Reset() { c.mu.Lock() defer c.mu.Unlock() c.count = 0 } ``` **Important:** Services are shared across all windows. Use mutexes for thread safety. ### Service with Dependencies ```go type DatabaseService struct { db *sql.DB } func NewDatabaseService(db *sql.DB) *DatabaseService { return &DatabaseService{db: db} } func (d *DatabaseService) GetUser(id int) (*User, error) { var user User err := d.db.QueryRow("SELECT * FROM users WHERE id = ?", id).Scan(&user) return &user, err } ``` **Register with dependencies:** ```go db, _ := sql.Open("sqlite3", "app.db") app := application.New(application.Options{ Services: []application.Service{ application.NewService(NewDatabaseService(db)), }, }) ``` ## Generating Bindings ### Basic Generation ```bash wails3 generate bindings ``` **Output:** ``` INFO 347 Packages, 3 Services, 12 Methods, 0 Enums, 0 Models in 1.98s INFO Output directory: /myproject/frontend/bindings ``` **Generated structure:** - frontend/bindings - myapp - calculatorservice.js - counterservice.js - databaseservice.js - index.js ### TypeScript Generation ```bash wails3 generate bindings -ts ``` **Generates `.ts` files** with full TypeScript types. ### Custom Output Directory ```bash wails3 generate bindings -d ./src/bindings ``` ### Watch Mode (Development) ```bash wails3 dev ``` **Automatically regenerates bindings** when Go code changes. ## Using Bindings ### JavaScript **Generated binding:** ```javascript // frontend/bindings/myapp/calculatorservice.js /** * @param {number} a * @param {number} b * @returns {Promise} */ export function Add(a, b) { return window.wails.Call('CalculatorService.Add', a, b) } ``` **Usage:** ```javascript import { Add, Subtract, Multiply, Divide } from './bindings/myapp/calculatorservice' // Simple calls const sum = await Add(5, 3) // 8 const diff = await Subtract(10, 4) // 6 const product = await Multiply(7, 6) // 42 // Error handling try { const result = await Divide(10, 0) } catch (error) { console.error("Error:", error) // "division by zero" } ``` ### TypeScript **Generated binding:** ```typescript // frontend/bindings/myapp/calculatorservice.ts export function Add(a: number, b: number): Promise export function Subtract(a: number, b: number): Promise export function Multiply(a: number, b: number): Promise export function Divide(a: number, b: number): Promise ``` **Usage:** ```typescript import { Add, Divide } from './bindings/myapp/calculatorservice' const sum: number = await Add(5, 3) try { const result = await Divide(10, 0) } catch (error: unknown) { if (error instanceof Error) { console.error(error.message) } } ``` **Benefits:** - Full type checking - IDE autocomplete - Compile-time errors - Better refactoring ### Index Files **Generated index:** ```javascript // frontend/bindings/myapp/index.js export * as CalculatorService from './calculatorservice.js' export * as CounterService from './counterservice.js' export * as DatabaseService from './databaseservice.js' ``` **Simplified imports:** ```javascript import { CalculatorService } from './bindings/myapp' const sum = await CalculatorService.Add(5, 3) ``` ## Type Mapping ### Primitive Types | Go Type | JavaScript/TypeScript | |---------|----------------------| | `string` | `string` | | `bool` | `boolean` | | `int`, `int8`, `int16`, `int32`, `int64` | `number` | | `uint`, `uint8`, `uint16`, `uint32`, `uint64` | `number` | | `float32`, `float64` | `number` | | `byte` | `number` | | `rune` | `number` | ### Complex Types | Go Type | JavaScript/TypeScript | |---------|----------------------| | `[]T` | `T[]` | | `[N]T` | `T[]` | | `map[string]T` | `Record` | | `map[K]V` | `Map` | | `struct` | `class` (with fields) | | `time.Time` | `Date` | | `*T` | `T` (pointers transparent) | | `interface{}` | `any` | | `error` | Exception (thrown) | ### Unsupported Types These types **cannot** be passed across the bridge: - `chan T` (channels) - `func()` (functions) - Complex interfaces (except `interface{}`) - Unexported fields (lowercase) **Workaround:** Use IDs or handles: ```go // ❌ Can't return file handle func OpenFile(path string) (*os.File, error) // ✅ Return file ID instead var files = make(map[string]*os.File) func OpenFile(path string) (string, error) { file, err := os.Open(path) if err != nil { return "", err } id := generateID() files[id] = file return id, nil } func ReadFile(id string) ([]byte, error) { file := files[id] return io.ReadAll(file) } func CloseFile(id string) error { file := files[id] delete(files, id) return file.Close() } ``` ## Error Handling ### Go Side ```go func (d *DatabaseService) GetUser(id int) (*User, error) { if id <= 0 { return nil, errors.New("invalid user ID") } var user User err := d.db.QueryRow("SELECT * FROM users WHERE id = ?", id).Scan(&user) if err == sql.ErrNoRows { return nil, fmt.Errorf("user %d not found", id) } if err != nil { return nil, fmt.Errorf("database error: %w", err) } return &user, nil } ``` ### JavaScript Side ```javascript import { GetUser } from './bindings/myapp/databaseservice' try { const user = await GetUser(123) console.log("User:", user) } catch (error) { console.error("Error:", error) // Error: "user 123 not found" } ``` **Error types:** - Go `error` → JavaScript exception - Error message preserved - Stack trace available ## Performance ### Call Overhead **Typical call:** <1ms ``` JavaScript → Bridge → Go → Bridge → JavaScript ↓ ↓ ↓ ↓ ↓ <0.1ms <0.1ms [varies] <0.1ms <0.1ms ``` **Compared to alternatives:** - HTTP/REST: 5-50ms - IPC: 1-10ms - Wails: <1ms ### Optimisation Tips **✅ Batch operations:** ```javascript // ❌ Slow: N calls for (const item of items) { await ProcessItem(item) } // ✅ Fast: 1 call await ProcessItems(items) ``` **✅ Cache results:** ```javascript // ❌ Repeated calls const config1 = await GetConfig() const config2 = await GetConfig() // ✅ Cache const config = await GetConfig() // Use config multiple times ``` **✅ Use events for streaming:** ```go func ProcessLargeFile(path string) error { // Emit progress events for line := range lines { app.Event.Emit("progress", line) } return nil } ``` ## Complete Example **Go:** ```go package main import ( "fmt" "github.com/wailsapp/wails/v3/pkg/application" ) type TodoService struct { todos []Todo } type Todo struct { ID int `json:"id"` Title string `json:"title"` Completed bool `json:"completed"` } func (t *TodoService) GetAll() []Todo { return t.todos } func (t *TodoService) Add(title string) Todo { todo := Todo{ ID: len(t.todos) + 1, Title: title, Completed: false, } t.todos = append(t.todos, todo) return todo } func (t *TodoService) Toggle(id int) error { for i := range t.todos { if t.todos[i].ID == id { t.todos[i].Completed = !t.todos[i].Completed return nil } } return fmt.Errorf("todo %d not found", id) } func (t *TodoService) Delete(id int) error { for i := range t.todos { if t.todos[i].ID == id { t.todos = append(t.todos[:i], t.todos[i+1:]...) return nil } } return fmt.Errorf("todo %d not found", id) } func main() { app := application.New(application.Options{ Services: []application.Service{ application.NewService(&TodoService{}), }, }) app.Window.New() app.Run() } ``` **JavaScript:** ```javascript import { GetAll, Add, Toggle, Delete } from './bindings/myapp/todoservice' class TodoApp { async loadTodos() { const todos = await GetAll() this.renderTodos(todos) } async addTodo(title) { try { const todo = await Add(title) this.loadTodos() } catch (error) { console.error("Failed to add todo:", error) } } async toggleTodo(id) { try { await Toggle(id) this.loadTodos() } catch (error) { console.error("Failed to toggle todo:", error) } } async deleteTodo(id) { try { await Delete(id) this.loadTodos() } catch (error) { console.error("Failed to delete todo:", error) } } renderTodos(todos) { const list = document.getElementById('todo-list') list.innerHTML = todos.map(todo => `
${todo.Title}
`).join('') } } const app = new TodoApp() app.loadTodos() ``` ## Best Practices ### ✅ Do - **Keep methods simple** - Single responsibility - **Return errors** - Don't panic - **Use thread-safe state** - Mutexes for shared data - **Batch operations** - Reduce bridge calls - **Cache on Go side** - Avoid repeated work - **Document methods** - Comments become JSDoc ### ❌ Don't - **Don't block** - Use goroutines for long operations - **Don't return channels** - Use events instead - **Don't return functions** - Not supported - **Don't ignore errors** - Always handle them - **Don't use unexported fields** - Won't be bound ## Next Steps Deep dive into the service system. [Learn More →](/features/bindings/services) Bind complex data structures. [Learn More →](/features/bindings/models) Understand the bridge mechanism. [Learn More →](/concepts/bridge) Use events for pub/sub communication. [Learn More →](/features/events/system) --- **Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding).