Go

Primate runs Go with WebAssembly compilation, strongly-typed validation, sessions, and server-side routing.

Setup

Install Go

First, install Go 1.27 or later from go.dev.

Install Module

npm install @primate/go

Configure

import config from "primate/config";
import go from "@primate/go";

export default config({
  modules: [go()],
});

Initialize Go

Create a go.mod file in your project root:

go mod init your-project-name

Add the Primate Go dependency:

go get primate.run/go@latest

The primate.run/go vanity metadata directs the Go toolchain to immutable Repopack release artifacts without changing your global Go environment. The Go proxy and checksum database continue to cache and verify the module normally. This command creates go.mod and go.sum files that manage your dependencies.

Routes

Create Go route handlers in routes using .go files. Routes are compiled to WebAssembly and run in the JavaScript runtime.

// routes/hello.go
package main

import "primate.run/go/route"

var _ = route.Get(func(request route.Request) any {
    return "Hello, world!"
})

HTTP Methods

All standard HTTP methods are supported:

package main

import "primate.run/go/route"

var _ = route.Get(func(request route.Request) any {
    return "GET request"
})

var _ = route.Post(func(request route.Request) any {
    return "POST request"
})

var _ = route.Put(func(request route.Request) any {
    return "PUT request"
})

var _ = route.Delete(func(request route.Request) any {
    return "DELETE request"
})

Request Handling

Query Parameters

Access query parameters through the Query request bag:

// routes/query.go
package main

import "primate.run/go/route"

var _ = route.Get(func(request route.Request) any {
    if request.Query.Has("foo") {
        foo, _ := request.Query.Get("foo")
        return foo
    }
    return "foo missing"
})

Request Body

Handle different body types based on content:

JSON Body

// routes/json.go
package main

import (
    "primate.run/go/route"
    "primate.run/go/types"
)

var _ = route.Post(func(request route.Request) any {
    json, err := request.Body.JSON()
    if err != nil {
        return types.Dict{"error": err.Error()}
    }
    return json
})

Form Fields

// routes/form.go
package main

import (
    "primate.run/go/route"
    "primate.run/go/types"
)

var _ = route.Post(func(request route.Request) any {
    form, err := request.Body.Form()
    if err != nil {
        return types.Dict{"error": err.Error()}
    }
    return form
})

Text Body

// routes/text.go
package main

import "primate.run/go/route"

var _ = route.Post(func(request route.Request) any {
    text, _ := request.Body.Text()
    return text
})

Binary Data

// routes/blob.go
package main

import (
    "primate.run/go/route"
    "primate.run/go/types"
)

var _ = route.Post(func(request route.Request) any {
    blob, err := request.Body.Blob()
    if err != nil {
        return types.Dict{"error": err.Error()}
    }

    head := []int{}
    for i := 0; i < len(blob.Data) && i < 4; i++ {
        head = append(head, int(blob.Data[i]))
    }

    return map[string]any{
        "type": blob.Type,
        "size": len(blob.Data),
        "head": head,
    }
})

File Uploads

Handle multipart file uploads:

// routes/upload.go
package main

import (
    "primate.run/go/route"
    "primate.run/go/types"
)

var _ = route.Post(func(request route.Request) any {
    // get form fields
    form, err := request.Body.Form()
    if err != nil {
        return types.Dict{"error": err.Error()}
    }

    // get uploaded files
    files, err := request.Body.Files()
    if err != nil {
        return types.Dict{"error": err.Error()}
    }

    // process files
    for _, file := range files {
        // file.Field - form field name
        // file.Name - original filename
        // file.Type - MIME type
        // file.Size - file size in bytes
        // file.Bytes - file content
    }

    return types.Dict{
        "form": form,
        "files":  len(files),
    }
})

Responses

Plain Data

Return any Go type that can be JSON serialized:

var _ = route.Get(func(request route.Request) any {
    return types.Dict{"name": "Donald"}
})

var _ = route.Get(func(request route.Request) any {
    return "Hello, world!"
})

var _ = route.Get(func(request route.Request) any {
    return types.Array[types.Dict]{
        {"name": "Donald"},
        {"name": "Ryan"},
    }
})

Views

Render views with props:

// routes/view.go
package main

import (
    "primate.run/go/response"
    "primate.run/go/route"
    "primate.run/go/types"
)

var _ = route.Get(func(request route.Request) any {
    return response.View("index.html", types.Dict{"hello": "world"})
})

With options:

var _ = route.Get(func(request route.Request) any {
    return response.View("index.html",
        types.Dict{"hello": "world"},
        types.Dict{"partial": true},
    )
})

Redirects

Redirect to another route:

// routes/redirect.go
package main

import (
    "primate.run/go/response"
    "primate.run/go/route"
)

var _ = route.Get(func(request route.Request) any {
    return response.Redirect("/redirected")
})

With custom status code:

var _ = route.Get(func(request route.Request) any {
    return response.Redirect("/redirected", 301) // moved permanently
})

Error Responses

Return error responses:

// routes/error.go
package main

import (
    "primate.run/go/response"
    "primate.run/go/route"
)

var _ = route.Get(func(request route.Request) any {
    return response.Error()
})

With custom error options:

var _ = route.Get(func(request route.Request) any {
    return response.Error(types.Dict{"body": "Custom error message"})
})

Sessions

Manage user sessions with the session package:

// routes/session.go
package main

import (
    "primate.run/go/route"
    "primate.run/go/session"
    "primate.run/go/types"
)

var _ = route.Get(func(request route.Request) any {
    // create a session
    s := session.From(request)
    if err := s.Create(types.Dict{"foo": "bar"}); err != nil {
        panic(err)
    }

    // get session data
    data, err := s.Get()
    if err != nil {
        panic(err)
    }
    return data
})

Session Methods

Types

Primate Go provides convenient type aliases in the core package for common data structures:

import "primate.run/go/core"

// dictionary (map[string]any)
data := core.Dict{"key": "value", "count": 42}

// generic object with typed values
user := core.Object[string]{"name": "John", "role": "admin"}

// generic array with typed elements
names := core.Array[string]{"John", "Bob", "Charlie"}

// array of dictionaries
users := core.Array[core.Dict]{
    {"name": "Donald"},
    {"name": "Ryan"},
}

Configuration

Option Type Default Description
extension string ".go" Associated file extension

Example

import go from "@primate/go";
import config from "primate/config";

export default config({
  modules: [
    go({
      // use `.golang` as associated file extension
      extension: ".golang",
    }),
  ],
});

Resources

Previous
Intro
Next
Python