Primate

Backends

Primate supports multiple backend languages compiled to WebAssembly. Use one or mix them in the same app for different routes.

Overview

Backend Routes Body Query Sessions Stores WebSocket Validation
Go
Grain
Python
Ruby

Features

WebAssembly

Backends run in the JS runtime via WebAssembly or a Wasm-based runtime (e.g. Pyodide for Python).

Language Categories

Systems Languages

Go, Grain

Compiled languages with strong type systems, memory safety, and excellent WebAssembly support. Well-suited for high-performance APIs and complex business logic.

Dynamic Languages

Python, Ruby

Interpreted languages with flexible syntax and extensive ecosystems. Good for rapid prototyping, data processing, and scripting tasks.

Quickstart

Install a backend module:

npm install @primate/go

Configure it:

// config/app.ts
import config from "primate/config";
import go from "@primate/go";

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

Create a route handler:

// routes/hello.go
package main

import "github.com/primate-run/go/route"

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

Route Structure

Each backend follows a consistent pattern for route handlers:

HTTP Methods

All backends support standard HTTP methods through dedicated functions:

// Go
var _ = route.Get(func(request route.Request) any { ... })
var _ = route.Post(func(request route.Request) any { ... })
// Grain
provide let get = (request: Request) => { ... }
provide let post = (request: Request) => { ... }
# Python
@Route.get
def get(request): ...

@Route.post
def post(request): ...
# Ruby
Route.get do |request|
  ...
end

Route.post do |request|
  ...
end

Request Handling

Access request data through consistent APIs:

// Go - Query parameters
if request.Query.Has("name") {
    name, _ := request.Query.Get("name")
    return name
}

// Go - JSON body
json, err := request.Body.JSON()
// Grain - Query parameters
let query = Request.getQuery(request)
match (Map.get("name", query)) {
  Some(value) => JsonString(value),
  None => JsonString("name missing")
}

// Grain - JSON body
Body.json(request)

Response Types

Data Responses

Return structured data as JSON:

return map[string]any{"message": "Hello"}
JsonObject([("message", JsonString("Hello"))])

Views

Render frontend components:

return response.View("component.html", data)
Response.view("component.html", props = data)

Redirects

Redirect to other routes:

return response.Redirect("/other-route")
Response.redirect("/other-route")

Database Operations

Built-in store operations:

// Grain
let User = Store.store("User")
let user = Store.insert(User, userData)

Session Management

Server-side sessions with automatic cookie handling:

// Go
session.Create(map[string]any{"user": "john"})
data := session.Get()
// Grain
Session.create(JsonObject([("user", JsonString("john"))]))
let session = Session.get()

WebSocket Support

Real-time communication endpoints:

// Grain
Response.ws(
  message = Some((socket, payload) => {
    WebSocket.send(socket, payload) // Echo back
  })
)

Mixing Backends

Multiple backends can coexist; each handles its own file extensions:

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

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

Routes can be implemented in different languages:

routes/
├── auth.go          # Go authentication
├── api.py           # Python data processing
├── websocket.gr     # Grain real-time features
└── admin.rb         # Ruby admin interface

Ecosystem Integration

Package Management

Each backend uses its native package manager:

External Libraries

Import and use libraries native to each language, compiled to WebAssembly:

// Go
import "encoding/json"
import "crypto/sha256"
# Python
import numpy as np
import pandas as pd
# Ruby
require 'json'
require 'digest'
Previous
Web Components
Next
Go