---
title: Go-Frontend Bridge
description: Deep dive into how Wails enables direct communication between Go and JavaScript
sidebar:
  order: 3
---

import { Tabs, TabItem } from "@astrojs/starlight/components";

## Direct Go-JavaScript Communication

Wails provides a **direct, in-memory bridge** between Go and JavaScript, enabling seamless communication without HTTP overhead, process boundaries, or serialisation bottlenecks.

## The Big Picture

```d2
direction: right

Frontend: "Frontend (JavaScript)" {
  UI: "React/Vue/Vanilla" {
    shape: rectangle
    style.fill: "#8B5CF6"
  }
  
  Bindings: "Auto-Generated Bindings" {
    shape: rectangle
    style.fill: "#A78BFA"
  }
}

Bridge: "Wails Bridge" {
  Encoder: "JSON Encoder" {
    shape: rectangle
    style.fill: "#10B981"
  }
  
  Router: "Method Router" {
    shape: diamond
    style.fill: "#10B981"
  }
  
  Decoder: "JSON Decoder" {
    shape: rectangle
    style.fill: "#10B981"
  }
  
  TypeGen: "Type Generator" {
    shape: rectangle
    style.fill: "#10B981"
  }
}

Backend: "Backend (Go)" {
  Services: "Your Services" {
    shape: rectangle
    style.fill: "#00ADD8"
  }
  
  Registry: "Service Registry" {
    shape: rectangle
    style.fill: "#00ADD8"
  }
}

Frontend.UI -> Frontend.Bindings: "import { Method }"
Frontend.Bindings -> Bridge.Encoder: "Call Method('arg')"
Bridge.Encoder -> Bridge.Router: "Encode to JSON"
Bridge.Router -> Backend.Registry: "Find service"
Backend.Registry -> Backend.Services: "Invoke method"
Backend.Services -> Bridge.Decoder: "Return result"
Bridge.Decoder -> Frontend.Bindings: "Decode to JS"
Frontend.Bindings -> Frontend.UI: "Promise resolves"
Bridge.TypeGen -> Frontend.Bindings: "Generate types"
```

**Key insight:** No HTTP, no IPC, no process boundaries. Just **direct function calls** with **type safety**.

## How It Works: Step by Step

### 1. Service Registration (Startup)

When your application starts, Wails scans your services:

```go
type GreetService struct {
    prefix string
}

func (g *GreetService) Greet(name string) string {
    return g.prefix + name + "!"
}

func (g *GreetService) Add(a, b int) int {
    return a + b
}

// Register service
app := application.New(application.Options{
    Services: []application.Service{
        application.NewService(&GreetService{prefix: "Hello, "}),
    },
})
```

**What Wails does:**
1. **Scans the struct** for exported methods
2. **Extracts type information** (parameters, return types)
3. **Builds a registry** mapping method names to functions
4. **Generates TypeScript bindings** with full type definitions

### 2. Binding Generation (Build Time)

Wails generates TypeScript bindings automatically:

```typescript
// Auto-generated: frontend/bindings/GreetService.ts
export function Greet(name: string): Promise<string>
export function Add(a: number, b: number): Promise<number>
```

**Type mapping:**

| Go Type | TypeScript Type |
|---------|-----------------|
| `string` | `string` |
| `int`, `int32`, `int64` | `number` |
| `float32`, `float64` | `number` |
| `bool` | `boolean` |
| `[]T` | `T[]` |
| `map[string]T` | `Record<string, T>` |
| `struct` | `interface` |
| `time.Time` | `Date` |
| `error` | Exception (thrown) |

### 3. Frontend Call (Runtime)

Developer calls the Go method from JavaScript:

```javascript
import { Greet, Add } from './bindings/GreetService'

// Call Go from JavaScript
const greeting = await Greet("World")
console.log(greeting)  // "Hello, World!"

const sum = await Add(5, 3)
console.log(sum)  // 8
```

**What happens:**
1. **Binding function called** - `Greet("World")`
2. **Message created** - `{ service: "GreetService", method: "Greet", args: ["World"] }`
3. **Sent to bridge** - Via WebView's JavaScript bridge
4. **Promise returned** - Awaits response

### 4. Bridge Processing (Runtime)

The bridge receives the message and processes it:

```d2
direction: down

Receive: "Receive Message" {
  shape: rectangle
  style.fill: "#10B981"
}

Parse: "Parse JSON" {
  shape: rectangle
}

Validate: "Validate" {
  Check: "Service exists?" {
    shape: diamond
  }
  
  CheckMethod: "Method exists?" {
    shape: diamond
  }
  
  CheckTypes: "Types correct?" {
    shape: diamond
  }
}

Invoke: "Invoke Go Method" {
  shape: rectangle
  style.fill: "#00ADD8"
}

Encode: "Encode Result" {
  shape: rectangle
}

Send: "Send Response" {
  shape: rectangle
  style.fill: "#10B981"
}

Error: "Send Error" {
  shape: rectangle
  style.fill: "#EF4444"
}

Receive -> Parse
Parse -> Validate.Check
Validate.Check -> Validate.CheckMethod: "Yes"
Validate.Check -> Error: "No"
Validate.CheckMethod -> Validate.CheckTypes: "Yes"
Validate.CheckMethod -> Error: "No"
Validate.CheckTypes -> Invoke: "Yes"
Validate.CheckTypes -> Error: "No"
Invoke -> Encode: "Success"
Invoke -> Error: "Error"
Encode -> Send
```

**Security:** Only registered services and exported methods are callable.

### 5. Go Execution (Runtime)

The Go method executes:

```go
func (g *GreetService) Greet(name string) string {
    // This runs in Go
    return g.prefix + name + "!"
}
```

**Execution context:**
- Runs in a **goroutine** (non-blocking)
- Has access to **all Go features** (file system, network, databases)
- Can call **other Go code** freely
- Returns result or error

### 6. Response (Runtime)

Result is sent back to JavaScript:

```javascript
// Promise resolves with result
const greeting = await Greet("World")
// greeting = "Hello, World!"
```

**Error handling:**

```go
func (g *GreetService) Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}
```

```javascript
try {
    const result = await Divide(10, 0)
} catch (error) {
    console.error("Go error:", error)  // "division by zero"
}
```

## Performance Characteristics

### Speed

**Typical call overhead:** &lt;1ms

```
Frontend Call → Bridge → Go Execution → Bridge → Frontend Response
     ↓            ↓           ↓            ↓            ↓
   &lt;0.1ms      &lt;0.1ms      [varies]     &lt;0.1ms      &lt;0.1ms
```

**Compared to alternatives:**
- **HTTP/REST:** 5-50ms (network stack, serialisation)
- **IPC:** 1-10ms (process boundaries, marshalling)
- **Wails Bridge:** &lt;1ms (in-memory, direct call)

### Memory

**Per-call overhead:** ~1KB (message buffer)

**Zero-copy optimisation:** Large data (>1MB) uses shared memory where possible.

### Concurrency

**Calls are concurrent:**
- Each call runs in its own goroutine
- Multiple calls can execute simultaneously
- No blocking between calls

```javascript
// These run concurrently
const [result1, result2, result3] = await Promise.all([
    SlowOperation1(),
    SlowOperation2(),
    SlowOperation3(),
])
```

## Type System

### Supported Types

#### Primitives

```go
// Go
func Example(
    s string,
    i int,
    f float64,
    b bool,
) (string, int, float64, bool) {
    return s, i, f, b
}
```

```typescript
// TypeScript (auto-generated)
function Example(
    s: string,
    i: number,
    f: number,
    b: boolean,
): Promise<[string, number, number, boolean]>
```

#### Slices and Arrays

```go
// Go
func Sum(numbers []int) int {
    total := 0
    for _, n := range numbers {
        total += n
    }
    return total
}
```

```typescript
// TypeScript
function Sum(numbers: number[]): Promise<number>

// Usage
const total = await Sum([1, 2, 3, 4, 5])  // 15
```

#### Maps

```go
// Go
func GetConfig() map[string]interface{} {
    return map[string]interface{}{
        "theme": "dark",
        "fontSize": 14,
        "enabled": true,
    }
}
```

```typescript
// TypeScript
function GetConfig(): Promise<Record<string, any>>

// Usage
const config = await GetConfig()
console.log(config.theme)  // "dark"
```

#### Structs

```go
// Go
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

func GetUser(id int) (*User, error) {
    return &User{
        ID:    id,
        Name:  "Alice",
        Email: "alice@example.com",
    }, nil
}
```

```typescript
// TypeScript (auto-generated)
interface User {
    id: number
    name: string
    email: string
}

function GetUser(id: number): Promise<User>

// Usage
const user = await GetUser(1)
console.log(user.name)  // "Alice"
```

**JSON tags:** Use `json:` tags to control field names in TypeScript.

#### Time

```go
// Go
func GetTimestamp() time.Time {
    return time.Now()
}
```

```typescript
// TypeScript
function GetTimestamp(): Promise<Date>

// Usage
const timestamp = await GetTimestamp()
console.log(timestamp.toISOString())
```

#### Errors

```go
// Go
func Validate(input string) error {
    if input == "" {
        return errors.New("input cannot be empty")
    }
    return nil
}
```

```typescript
// TypeScript
function Validate(input: string): Promise<void>

// Usage
try {
    await Validate("")
} catch (error) {
    console.error(error)  // "input cannot be empty"
}
```

### Unsupported Types

These types **cannot** be passed across the bridge:

- **Channels** (`chan T`)
- **Functions** (`func()`)
- **Interfaces** (except `interface{}` / `any`)
- **Pointers** (except to structs)
- **Unexported fields** (lowercase)

**Workaround:** Use IDs or handles:

```go
// ❌ Can't pass file handle
func OpenFile(path string) (*os.File, error) {
    return os.Open(path)
}

// ✅ 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()
}
```

## Advanced Patterns

### Context Passing

Services can access the call context:

```go
type UserService struct{}

func (s *UserService) GetCurrentUser(ctx context.Context) (*User, error) {
    // Access window that made the call
    window := application.ContextWindow(ctx)
    
    // Access application
    app := application.ContextApplication(ctx)
    
    // Your logic
    return getCurrentUser(), nil
}
```

**Context provides:**
- Window that made the call
- Application instance
- Request metadata

### Streaming Data

For large data, use events instead of return values:

```go
func ProcessLargeFile(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return err
    }
    defer file.Close()
    
    scanner := bufio.NewScanner(file)
    lineNum := 0
    
    for scanner.Scan() {
        lineNum++
        // Emit progress events
        app.Event.Emit("file-progress", map[string]interface{}{
            "line": lineNum,
            "text": scanner.Text(),
        })
    }
    
    return scanner.Err()
}
```

```javascript
import { Events } from '@wailsio/runtime'
import { ProcessLargeFile } from './bindings/FileService'

// Listen for progress
Events.On('file-progress', (data) => {
    console.log(`Line ${data.line}: ${data.text}`)
})

// Start processing
await ProcessLargeFile('/path/to/large/file.txt')
```

### Cancellation

Use context for cancellable operations:

```go
func LongRunningTask(ctx context.Context) error {
    for i := 0; i < 1000; i++ {
        // Check if cancelled
        select {
        case <-ctx.Done():
            return ctx.Err()
        default:
            // Continue work
            time.Sleep(100 * time.Millisecond)
        }
    }
    return nil
}
```

**Note:** Context cancellation on frontend disconnect is automatic.

### Batch Operations

Reduce bridge overhead by batching:

```go
// ❌ Inefficient: N bridge calls
for _, item := range items {
    await ProcessItem(item)
}

// ✅ Efficient: 1 bridge call
await ProcessItems(items)
```

```go
func ProcessItems(items []Item) ([]Result, error) {
    results := make([]Result, len(items))
    for i, item := range items {
        results[i] = processItem(item)
    }
    return results, nil
}
```

## Debugging the Bridge

### Enable Debug Logging

```go
app := application.New(application.Options{
    Name: "My App",
    Logger: application.NewDefaultLogger(),
    LogLevel: logger.DEBUG,
})
```

**Output shows:**
- Method calls
- Parameters
- Return values
- Errors
- Timing information

### Inspect Generated Bindings

Check `frontend/bindings/` to see generated TypeScript:

```typescript
// frontend/bindings/MyService.ts
export function MyMethod(arg: string): Promise<number> {
    return window.wails.Call('MyService.MyMethod', arg)
}
```

### Test Services Directly

Test Go services without the frontend:

```go
func TestGreetService(t *testing.T) {
    service := &GreetService{prefix: "Hello, "}
    result := service.Greet("Test")
    if result != "Hello, Test!" {
        t.Errorf("Expected 'Hello, Test!', got '%s'", result)
    }
}
```

## Performance Tips

### ✅ Do

- **Batch operations** - Reduce bridge calls
- **Use events for streaming** - Don't return large arrays
- **Keep methods fast** - &lt;100ms ideal
- **Use goroutines** - For long operations
- **Cache on Go side** - Avoid repeated calculations

### ❌ Don't

- **Don't make excessive calls** - Batch when possible
- **Don't return huge data** - Use pagination or streaming
- **Don't block** - Use goroutines for long operations
- **Don't pass complex types** - Keep it simple
- **Don't ignore errors** - Always handle them

## Security

The bridge is secure by default:

1. **Whitelist only** - Only registered services callable
2. **Type validation** - Arguments checked against Go types
3. **No eval()** - Frontend can't execute arbitrary Go code
4. **No reflection abuse** - Only exported methods accessible

**Best practices:**
- **Validate input** in Go (don't trust frontend)
- **Use context** for authentication/authorisation
- **Rate limit** expensive operations
- **Sanitise** file paths and user input

## Next Steps

**Build System** - Learn how Wails builds and bundles your application  
[Learn More →](/concepts/build-system)

**Services** - Deep dive into the service system  
[Learn More →](/features/bindings/services)

**Events** - Use events for pub/sub communication  
[Learn More →](/features/events/system)

---

**Questions about the bridge?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding).
