---
name: tako-sdk-go
description: >-
  tako.sh Go SDK: ListenAndServe for http.Handler, Listener for custom servers,
  typed secrets via tako generate, authenticated presence and live Stores with cached offline reads.
type: framework
library: tako.sh
sources:
  - tako-sh/tako:tako.go
  - tako-sh/tako:presence.go
  - tako-sh/tako:store.go
  - tako-sh/tako:internal
---

# Tako SDK (`tako.sh` Go module)

Runtime SDK for Go apps deployed with Tako.

> **CRITICAL**: The Go SDK is **required** — your app must call `tako.ListenAndServe()` or use `tako.Listener()` to speak the Tako protocol (health checks, graceful shutdown). Secrets are loaded from the Tako bootstrap envelope at init time: fd 3 first for native processes, then `TAKO_BOOTSTRAP_DATA` for containers.

> **CRITICAL**: Any framework that implements `http.Handler` works directly with `ListenAndServe`. Use `Listener` for frameworks that own their server loop, such as Fiber (fasthttp), and retain authenticated Tako protocol handling.

## Core Concept: http.Handler

Tako wraps any standard `http.Handler`:

```go
package main

import (
	"net/http"
	"tako.sh"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello from Tako!"))
	})
	tako.ListenAndServe(mux)
}
```

This is a complete Tako app. `ListenAndServe` handles:

- Binding to the correct address (from Tako environment or `0.0.0.0:3000`)
- Intercepting `Host: <app>.tako` requests for health checks and channel auth
- Graceful shutdown on SIGTERM/SIGINT (10-second grace period)

## API

### `tako.ListenAndServe(handler http.Handler) error`

Wraps the handler with Tako protocol and starts serving. Blocks until shutdown.

Works with any `http.Handler`:

```go
// net/http
mux := http.NewServeMux()
tako.ListenAndServe(mux)

// Gin
r := gin.Default()
tako.ListenAndServe(r)

// Echo
e := echo.New()
tako.ListenAndServe(e)

// Chi
r := chi.NewRouter()
tako.ListenAndServe(r)
```

### `tako.Listener() (net.Listener, error)`

Returns a `net.Listener` for frameworks that manage their own server:

```go
// Fiber (fasthttp — does not implement http.Handler)
ln, err := tako.Listener()
if err != nil {
    log.Fatal(err)
}
app := fiber.New()
app.Listener(ln)
```

**Important:** When using `Listener()` directly, your app must handle the Tako protocol itself (authenticated health checks on `Host: <app>.tako`). Most apps should use `ListenAndServe` instead.

### `tako.InstanceID() string`

Returns the 8-char instance identifier assigned by tako-server. Empty in dev mode.

```go
slog.Info("request handled", "instance", tako.InstanceID())
```

### `tako.Version() string`

Returns the deploy version string. Empty in dev mode.

### `tako.Uptime() time.Duration`

Returns time since process start.

### `tako.GetSecret(name string) string`

Returns a secret value by name. Prefer the generated `Secrets` struct instead of calling this directly.

## Secrets

Secrets are accessed via a generated struct — type-safe with autocompletion:

```go
db := Secrets.DatabaseUrl()
key := Secrets.ApiKey()
```

Generate (or update) the typed secrets file:

```bash
tako generate
```

This reads secret names from `.tako/secrets.json` and generates `tako_secrets.go`:

```go
// Code generated by tako generate. DO NOT EDIT.
package main

import "tako.sh"

var Secrets = struct {
	ApiKey      func() string
	DatabaseUrl func() string
}{
	ApiKey:      func() string { return tako.GetSecret("API_KEY") },
	DatabaseUrl: func() string { return tako.GetSecret("DATABASE_URL") },
}
```

Run `tako generate` after adding or removing secrets. `tako gen` and `tako g` are aliases.

## Presence

Register presence spaces with `tako.RegisterPresence` before calling `tako.ListenAndServe`. Presence shares the channel authorization model and internal discovery endpoints. It uses a separate namespace and WebSocket transport automatically; there is no `Transport` option.

```go
import (
    "encoding/json"
    "tako.sh"
)

func registerChatPresence() {
    tako.RegisterPresence("chat", tako.PresenceDefinition{
        ParamsSchema: json.RawMessage(`{
            "type": "object",
            "properties": {"chatId": {"type": "string", "minLength": 1}},
            "required": ["chatId"]
        }`),
        Auth: &tako.ChannelAuthScheme{HeaderName: "authorization"},
        MaxConnectionLifetimeMs: 60_000,
        Verify: func(input tako.VerifyInput) tako.ChannelAuthDecision {
            var params struct { ChatID string `json:"chatId"` }
            if input.Header == nil || json.Unmarshal(input.Params, &params) != nil {
                return tako.RejectChannel()
            }
            user, err := verifyToken(input.Header.Value)
            if err != nil || !canAccessChat(user.ID, params.ChatID) {
                return tako.RejectChannel()
            }
            return tako.AllowChannel(tako.ChannelGrant{Subject: user.ID})
        },
    })
}
```

`verifyToken` and `canAccessChat` are application auth functions. `input.Channel` is the public space name (`"chat"`), and `input.Params` contains params validated by the proxy. Different validated params create separate member lists, so different `chatId` values stay isolated. Auth guards both joining and seeing members. Omit `Auth` for a public space. For cookie-only auth, set `Auth: &tako.ChannelAuthScheme{CookieName: "session"}` and inspect `input.Cookie`.

`KeepaliveIntervalMs` configures WebSocket keepalives, and `MaxConnectionLifetimeMs` bounds the interval before reconnecting clients must authorize again. A subject-only `AllowChannel` grant inherits these definition settings; nonzero lifecycle fields in a grant override the corresponding settings.

A React frontend uses `usePresence` from `tako.sh/react` with the same public name, params, and authorization token. The Go SDK registers backend policies; it does not create a browser or native WebSocket client. `tako.PresenceSnapshot` and `tako.PresenceMember` expose the wire shapes for consumers that manage their own connections. Member metadata is `json.RawMessage` and must be validated before use.

Every browser tab has its own server-assigned connection ID. The verified `Subject` identifies the user across tabs; deduplicate subjects when counting people. Presence metadata and membership are ephemeral, have no durable replay, and are scoped to the deployed app/environment. Shared Postgres carries membership across production servers. Local development uses in-memory state and bypasses auth callbacks, as with local channels. Verify private-room rejection and trusted identities in a deployed test environment.

## Stores

Register live reads over your existing database with `tako.RegisterStore` before
`ListenAndServe`. It returns `*tako.Store`; invalidate exact params after a write
commits:

```go
messages := tako.RegisterStore("messages", tako.StoreDefinition{
    ParamsSchema: json.RawMessage(`{
        "type":"object",
        "properties":{"chatId":{"type":"string"}},
        "required":["chatId"]
    }`),
    Auth: &tako.ChannelAuthScheme{HeaderName: "authorization"},
    Verify: func(input tako.VerifyInput) tako.ChannelAuthDecision {
        userID, ok := authorizeMessages(input)
        if !ok { return tako.RejectChannel() }
        return tako.AllowChannel(tako.ChannelGrant{Subject: userID})
    },
    Load: func(ctx context.Context, input tako.StoreLoadInput) (any, error) {
        return loadMessages(ctx, input.Params, input.Subject)
    },
    Offline: &tako.StoreOfflineConfig{MaxAgeMs: 3_600_000, Version: "1"},
})

// Commit the database write first, then handle any publication error:
err := messages.Invalidate(ctx, map[string]any{"chatId": chatID})
```

Import `context`, `encoding/json`, and `tako.sh`. The app-owned helpers verify
credentials/chat access and read your database. Return JSON-serializable data;
loader errors are hidden from browser responses. The verifier receives the
public name in `input.Channel`; loader params are schema-validated and `Subject`
comes only from authorization. Reads and subscriptions authorize independently,
including local development. Omit `Auth` for public reads; cookie-only auth uses
`ChannelAuthScheme{CookieName: "session"}`. `KeepaliveIntervalMs` and
`MaxConnectionLifetimeMs` configure subscription lifetime.

React frontends use `useStore<Data>("messages", { params, authorization,
cacheScope: userId, cacheVersion: "1" })` from `tako.sh/react`. Go registers
backend behavior; the browser SDK manages reconnect catch-up and optional
IndexedDB caching. Persistence requires both `Offline` and a stable non-secret
scope per reader/tenant; no token is persisted. Offline version defaults to
`"1"`; match frontend `cacheVersion` and bump both after incompatible changes.
The browser bounds cache size/expiry and caps TTL at seven days. Clear the scope
with `clearStoreCache` on logout and disable/unmount readers. Offline reads cannot
learn about remote revocation, install an app shell, or queue writes.

Invalidation carries no snapshot data and affects only exact params (nil means
`{}`, not a wildcard). Every reader reloads its own authorized snapshot. External
writers must explicitly invalidate; no SQL dependency tracking is performed.
Invalidation failure does not undo or retry the committed database write. Use
an application outbox for durable commit-and-notify. Params are limited to 16 KiB
and read responses to 1 MiB. Multi-server Go apps must explicitly configure shared
`postgres_url`; JS directory detection does not find inline Go registrations.
See https://tako.sh/docs/stores/ and verify isolation, reconnects, and cache expiry.

## Internal Protocol

The SDK transparently handles these — you don't interact with them directly:

- `GET /status` on `Host: <app>.tako` — health check (returns JSON with status, instance_id, version, pid, uptime_seconds)
- `POST /channels/authorize` on `Host: <app>.tako` — channel, presence, and Store auth callbacks for `tako-server`
- `GET /channels/registry` on `Host: <app>.tako` — channel, presence, and Store discovery metadata
- `POST /stores/load` on `Host: <app>.tako` — trusted, already authorized Store reads; a nonempty internal token is required
- Token authentication via `x-tako-internal-token` whenever bootstrap supplies a token, including managed development
- Secrets are read from the Tako bootstrap envelope at process startup, not via HTTP. Native processes use fd 3; containers use `TAKO_BOOTSTRAP_DATA`.

## Common Mistakes

### 1. CRITICAL: Not using the SDK

```go
// WRONG — app won't respond to Tako protocol
http.ListenAndServe(":3000", mux)

// CORRECT — Tako handles health checks, graceful shutdown, bootstrap secrets
tako.ListenAndServe(mux)
```

### 2. HIGH: Hardcoding the listen address

```go
// WRONG — Tako sets HOST/PORT for the instance
ln, _ := net.Listen("tcp", ":8080")

// CORRECT — let Tako choose the address
tako.ListenAndServe(mux)
// or
ln, _ := tako.Listener()
```

### 4. MEDIUM: Using Listener without protocol handling

```go
// WRONG — Listener alone doesn't add health checks
ln, _ := tako.Listener()
http.Serve(ln, mux)

// CORRECT — use ListenAndServe (adds protocol wrapping)
tako.ListenAndServe(mux)

// or if you need Listener (Fiber), the framework must handle
// Host: <app>.tako requests itself — this is an advanced path
```
