Source: https://primate.run/docs/application # Application Primate exposes an **application facade** through `config/app.ts`. It provides app-level utilities that are useful from server code, especially in route handlers. Use it when you need access to: * configuration values * environment variables * the project root * server-side views ## Importing the application facade The application facade is the default export of `config/app.ts`. You can import it directly: ```ts import app from "../config/app.ts"; ``` or, if you have configured a path alias in `tsconfig.json`, via the alias: ```ts import app from "@/config/app"; ``` A typical route might look like this: ```ts import app from "@/config/app"; import response from "primate/response"; import route from "primate/route"; export default route({ get() { const title = app.config("name"); return response.page({ title }); }, }); ``` !!! `@/config/app` is a path alias, not a built-in language feature. It only works if you configure it in `tsconfig.json`, for example by mapping `@/config/app` to `config/app.ts`. !!! ## `app.config(path)` Read values from your app configuration using a dot-separated path: ```ts const name = app.config("name"); const secure = app.config("server.secure"); ``` This is useful when route handlers or other server-side code need to react to application settings without importing configuration files directly. ## `app.env(key)` Read environment variables through the application facade: ```ts const token = app.env("API_TOKEN"); ``` This gives you a single app-level API for environment access instead of calling runtime-specific APIs directly. ### Typed environment variables You can make `app.env()` type-safe by declaring an environment schema in `config/app.ts`: ```ts // config/app.ts import config from "primate/config"; import p from "pema"; export default config({ env: { schema: p({ API_TOKEN: p.string, PORT: p.u16, }), }, }); ``` With a schema in place, Primate validates environment variables when the app starts serving, and `app.env()` becomes typed: ```ts const token = app.env("API_TOKEN"); // string const port = app.env("PORT"); // number ``` If a required key is missing, or a value does not satisfy the schema, Primate fails early instead of letting misconfiguration surface later at runtime. If no `env.schema` is configured, `app.env()` reads from the environment directly. !!! `app.env()` is server-only. Do not use it in frontend code. !!! ## `app.view(name)` Load a server-side view by name: ```ts const page = app.view("docs/home/index.md"); ``` This is mainly useful in advanced cases where you need the rendered or parsed view result before passing it somewhere else. For ordinary page rendering, prefer returning `response.page(...)` directly. Use `app.view(...)` only when you need to load a view manually and work with its result before returning a response. ```ts import app from "@/config/app"; import response from "primate/response"; import route from "primate/route"; export default route({ get() { const { html } = app.view("docs/home/index.md"); return response.page({ content: html }); }, }); ``` ## `app.root` `app.root` is a `FileRef` pointing at your project root. Use it to access app files relative to the root directory: ```ts const guides = await app.root.join("guides.json").json(); ``` This is useful for reading local assets, generated files, or project metadata without hard-coding absolute paths. ## Summary | API | Description | | ------------------ | -------------------------------------- | | `app.config(path)` | Read app configuration values | | `app.env(key)` | Read environment variables | | `app.view(name)` | Load a server-side view | | `app.root` | Access the project root as a `FileRef` | In most apps, you will use the application facade from route handlers when you need information or utilities that belong to the application as a whole rather than to the current request. Source: https://primate.run/docs/backend/go # Go Primate runs [Go][Documentation] with WebAssembly compilation, strongly-typed validation, sessions, and server-side routing. ## Setup ### Install Go First, install Go 1.25 or later from [go.dev](https://go.dev/dl/). ### Install Module ```bash npm install @primate/go ``` ### Configure ```ts 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: ```bash go mod init your-project-name ``` Add the Primate Go dependency: ```bash go get github.com/primate-run/go@latest ``` This creates `go.mod` and `go.sum` files that manage your Go dependencies. ## Routes Create Go route handlers in `routes` using `.go` files. Routes are compiled to WebAssembly and run in the JavaScript runtime. ```go // routes/hello.go package main import "github.com/primate-run/go/route" var _ = route.Get(func(request route.Request) any { return "Hello, world!" }) ``` ### HTTP Methods All standard HTTP methods are supported: ```go package main import "github.com/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: ```go // routes/query.go package main import "github.com/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 ```go // routes/json.go package main import ( "github.com/primate-run/go/route" "github.com/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 ```go // routes/form.go package main import ( "github.com/primate-run/go/route" "github.com/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 ```go // routes/text.go package main import "github.com/primate-run/go/route" var _ = route.Post(func(request route.Request) any { text, _ := request.Body.Text() return text }) ``` #### Binary Data ```go // routes/blob.go package main import ( "github.com/primate-run/go/route" "github.com/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: ```go // routes/upload.go package main import ( "github.com/primate-run/go/route" "github.com/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), } }) ``` ## Validation Use Primate's strongly-typed validation system with the `pema` package: ```go // routes/validate.go package main import ( "github.com/primate-run/go/route" "github.com/primate-run/go/pema" "github.com/primate-run/go/types" ) var schema = pema.Schema(types.Dict{ "baz": pema.Int(), "foo": pema.String(), }) var _ = route.Get(func(request route.Request) any { parsed, err := request.Query.Parse(schema, true) if err != nil { return err.Error() } return parsed }) ``` ### Field Types The validation system supports multiple strongly-typed field types: - `pema.String()` - validates string values - `pema.Int()` - validates integer values - `pema.Int64()` - validates 64-bit integer values - `pema.Float()` - validates float64 values - `pema.Boolean()` - validates boolean values ### Coercion Enable automatic type coercion by passing `true` as the second parameter: ```go parsed, err := r.Query.Parse(schema, true) // enables coercion ``` With coercion enabled: - Strings are converted to numbers when possible - Empty strings become `false` for booleans, `0` for numbers - Numbers are converted between types as needed ## Responses ### Plain Data Return any Go type that can be JSON serialized: ```go 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: ```go // routes/view.go package main import ( "github.com/primate-run/go/response" "github.com/primate-run/go/route" "github.com/primate-run/go/types" ) var _ = route.Get(func(request route.Request) any { return response.View("index.html", types.Dict{"hello": "world"}) }) ``` With options: ```go 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: ```go // routes/redirect.go package main import ( "github.com/primate-run/go/response" "github.com/primate-run/go/route" ) var _ = route.Get(func(request route.Request) any { return response.Redirect("/redirected") }) ``` With custom status code: ```go var _ = route.Get(func(request route.Request) any { return response.Redirect("/redirected", 301) // moved permanently }) ``` ### Error Responses Return error responses: ```go // routes/error.go package main import ( "github.com/primate-run/go/response" "github.com/primate-run/go/route" ) var _ = route.Get(func(request route.Request) any { return response.Error() }) ``` With custom error options: ```go 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: ```go // routes/session.go package main import ( "github.com/primate-run/go/route" "github.com/primate-run/go/session" "github.com/primate-run/go/types" ) var _ = route.Get(func(request route.Request) any { // create a session session.Create(types.Dict{"foo": "bar"}) // get session data return session.Get() }) ``` ### Session Methods - `session.Create(data)` - creates a new session with data - `session.Get()` - gets session data (throws if no session) - `session.Try()` - gets session data (returns empty if no session) - `session.Set(data)` - updates session data - `session.Destroy()` - destroys the session - `session.Exists()` - checks if session exists - `session.Id()` - gets the session ID ## Types Primate Go provides convenient type aliases in the `core` package for common data structures: ```go import "github.com/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 ```ts import go from "@primate/go"; import config from "primate/config"; export default config({ modules: [ go({ // use `.golang` as associated file extension extension: ".golang", }), ], }); ``` ## Resources - [Documentation] - [Go Language Specification](https://go.dev/ref/spec) - [WebAssembly Support](https://github.com/golang/go/wiki/WebAssembly) [Documentation]: https://go.dev Source: https://primate.run/docs/backend/python # Python Primate runs [Python][Documentation] with WebAssembly compilation, strongly-typed validation, sessions, and server-side routing. ## Setup ### Install Python First, install Python 3.8 or later from the [official website][Python]. ### Install Module ```bash npm install @primate/python ``` ### Configure ```ts import config from "primate/config"; import python from "@primate/python"; export default config({ modules: [python()], }); ``` ### Initialize Python Create a `requirements.txt` file in your project root to specify Python dependencies: ```txt numpy ``` Install the Primate Python dependency: ```bash pip install primate-run ``` Primate will automatically install packages listed in `requirements.txt` when compiling routes to WebAssembly. ## Routes Create Python route handlers in `routes` using `.py` files. Routes are compiled to WebAssembly and run in the JavaScript runtime. ```python # routes/hello.py from primate import Route @Route.get def get(request): return "Hello, world!" ``` ### HTTP Methods All standard HTTP methods are supported: ```python from primate import Route @Route.get def get(request): return "GET request" @Route.post def post(request): return "POST request" @Route.put def put(request): return "PUT request" @Route.delete def delete(request): return "DELETE request" ``` ## Request Handling ### Query Parameters Access query parameters through the `query` request bag: ```python # routes/query.py from primate import Route @Route.get def get(request): if request.query.has("foo"): return request.query.get("foo") else: return "foo missing" ``` ### Request Body Handle different body types based on content: #### JSON Body ```python # routes/json.py from primate import Route @Route.post def post(request): return request.body.json() ``` #### Form Fields ```python # routes/form.py from primate import Route @Route.post def post(request): return request.body.form() ``` #### Text Body ```python # routes/text.py from primate import Route @Route.post def post(request): return request.body.text() ``` #### Binary Data ```python # routes/blob.py from primate import Route @Route.post def post(request): blob = request.body.blob() return { "type": blob.type, "size": blob.size, "head": blob.head(4), } ``` ### File Uploads Handle multipart file uploads: ```python # routes/upload.py from primate import Route @Route.post def post(request): # get form fields form = request.body.form() # get uploaded files files = request.body.files() # process files file_info = [] for file in files: file_info.append({ "field": file.field, # form field name "name": file.filename, # original filename "type": file.content_type, # MIME type "size": file.size, # file size in bytes "content": file.io.read().decode("utf-8") # file content }) return { "form": form, "files": file_info } ``` ## Validation Use Primate's strongly-typed validation system with the `pema` module: ```python # routes/validate.py from primate import Route, pema schema = pema.schema({ "baz": pema.int(), "foo": pema.string() }) @Route.get def get(request): try: parsed = request.query.parse(schema, True) return parsed except pema.ValidationError as e: return str(e) ``` ### Field Types The validation system supports multiple strongly-typed field types: - `pema.string()` - validates string values - `pema.int()` - validates integer values - `pema.float()` - validates float values - `pema.boolean()` - validates boolean values ### Coercion Enable automatic type coercion by passing `True` as the second parameter: ```python parsed = request.query.parse(schema, True) # enables coercion ``` With coercion enabled: - Strings are converted to numbers when possible - Empty strings become `False` for booleans, `0` for numbers - Numbers are converted between types as needed ## Responses ### Plain Data Return any Python object that can be JSON serialized: ```python @Route.get def get(request): return {"name": "Donald"} @Route.get def get(request): return "Hello, world!" @Route.get def get(request): return [ {"name": "Donald"}, {"name": "Ryan"} ] ``` ### Views Render views with props: ```python # routes/view.py from primate import Route, Response @Route.get def get(request): return Response.view("index.html", {"hello": "world"}) ``` With options: ```python from primate import Route, Response @Route.get def get(request): return Response.view("index.html", {"hello": "world"}, {"partial": True} ) ``` ### Redirects Redirect to another route: ```python # routes/redirect.py from primate import Route, Response @Route.get def get(request): return Response.redirect("/redirected") ``` With custom status code: ```python @Route.get def get(request): return Response.redirect("/redirected", status=301) # moved permanently ``` ### Error Responses Return error responses: ```python # routes/error.py from primate import Route, Response @Route.get def get(request): return Response.error() ``` With custom error options: ```python @Route.get def get(request): return Response.error({"body": "Custom error message"}) ``` ## Sessions Manage user sessions with the Session module: ```python # routes/session.py from primate import Route, Session @Route.get def get(request): # create a session Session.create({"foo": "bar"}) # get session data return Session.get() ``` ### Session Methods - `Session.create(data)` - creates a new session with data - `Session.get()` - gets session data (raises if no session) - `Session.try_get()` - gets session data (returns empty dict if no session) - `Session.set(data)` - updates session data - `Session.destroy()` - destroys the session - `Session.exists()` - checks if session exists - `Session.id()` - gets the session ID ## Python Conventions Primate Python follows standard Python conventions: ### Function Names - Use `snake_case` for function and variable names - Use descriptive names for route handlers ### Dictionary Access Use standard Python dictionary syntax: ```python # accessing form data form = request.body.form() name = form.get("name", "") # building responses return { "status": "success", "data": processed_data } ``` ### Exception Handling Use try-except blocks for error handling: ```python @Route.post def post(request): try: data = request.body.json() return process_data(data) except Exception as e: return {"error": str(e)} ``` ## External Packages Install Python packages by adding them to `requirements.txt`: ```txt numpy pandas ``` Use them in your routes: ```python # routes/data.py from primate import Route import numpy as np import pandas as pd @Route.get def get(request): # Create sample data data = np.array([1, 2, 3, 4, 5]) df = pd.DataFrame({"values": data}) return { "mean": float(np.mean(data)), "sum": int(np.sum(data)), "dataframe_info": df.describe().to_dict() } ``` ## Configuration | Option | Type | Default | Description | | --------- | -------- | ------- | ------------------------- | | extension | `string` | `".py"` | Associated file extension | ### Example ```ts import python from "@primate/python"; import config from "primate/config"; export default config({ modules: [ python({ // use `.python` as associated file extension extension: ".python", }), ], }); ``` ## Resources - [Documentation] - [Python Language Reference](https://docs.python.org/3/) - [Pyodide Documentation](https://pyodide.org/) - [WebAssembly with Python](https://pyodide.org/en/stable/usage/wasm-constraints.html) [Documentation]: https://docs.python.org/3/ [Python]: https://www.python.org Source: https://primate.run/docs/backend/ruby # Ruby Primate runs [Ruby][Documentation] with WebAssembly compilation, strongly-typed validation, sessions, and server-side routing. ## Setup ### Install Ruby First, install Ruby 3.0 or later from the [official website][Ruby]. ### Install Module ```bash npm install @primate/ruby ``` ### Configure ```ts import config from "primate/config"; import ruby from "@primate/ruby"; export default config({ modules: [ruby()], }); ``` ### Initialize Ruby Create a `Gemfile` in your project root: ```bash bundle init ``` Add the Primate Ruby dependency: ```ruby # Gemfile source "https://rubygems.org" gem "primate-run", "~> 0.1.0" ``` Install dependencies to vendor/bundle (required for WASM): ```bash bundle config set --local path 'vendor/bundle' bundle install ``` This creates `Gemfile` and `Gemfile.lock` files that manage your Ruby dependencies. ## Routes Create Ruby route handlers in `routes` using `.rb` files. Routes are compiled to WebAssembly and run in the JavaScript runtime. ```ruby # routes/hello.rb require 'primate/route' Route.get do |request| 'Hello, world!' end ``` ### HTTP Methods All standard HTTP methods are supported: ```ruby require 'primate/route' Route.get do |request| 'GET request' end Route.post do |request| 'POST request' end Route.put do |request| 'PUT request' end Route.delete do |request| 'DELETE request' end ``` ## Request Handling ### Query Parameters Access query parameters through the `query` request bag: ```ruby # routes/query.rb require 'primate/route' Route.get do |request| if request.query.has?('foo') request.query.get('foo') else 'foo missing' end end ``` ### Request Body Handle different body types based on content: #### JSON Body ```ruby # routes/json.rb require 'primate/route' Route.post do |request| begin request.body.json rescue => e { error: e.message } end end ``` #### Form Fields ```ruby # routes/form.rb require 'primate/route' Route.post do |request| begin request.body.form rescue => e { error: e.message } end end ``` #### Text Body ```ruby # routes/text.rb require 'primate/route' Route.post do |request| request.body.text end ``` #### Binary Data ```ruby # routes/blob.rb require 'primate/route' Route.post(content_type: "application/octet-stream") do |request| blob = request.body.blob { "type" => blob.type, "size" => blob.size, "head" => blob.head(4), } end ``` ### File Uploads Handle multipart file uploads: ```ruby # routes/upload.rb require 'primate/route' Route.post(content_type: "multipart/form-data") do |request| multipart = request.body.multipart begin # Get form form form = multipart.form # Get uploaded files files = multipart.files # Process files file_info = files.map do |file| { field: file.field, # form field name name: file.filename, # original filename type: file.content_type, # MIME type size: file.size # file size in bytes } end { form: form, files: file_info } rescue => e { error: e.message } end end ``` ## Validation Use Primate's strongly-typed validation system with the `pema` module: ```ruby # routes/validate.rb require 'primate/route' require 'primate/pema' schema = Pema.schema({ 'baz' => Pema.int, 'foo' => Pema.string }) Route.get do |request| begin request.query.parse(schema, true) rescue Pema::ValidationError => e e.message end end ``` ### Field Types The validation system supports multiple strongly-typed field types: - `Pema.string` - validates string values - `Pema.int` - validates integer values - `Pema.float` - validates float values - `Pema.boolean` - validates boolean values ### Coercion Enable automatic type coercion by passing `true` as the second parameter: ```ruby request.query.parse(schema, true) # enables coercion ``` With coercion enabled: - Strings are converted to numbers when possible - Empty strings become `false` for booleans, `0` for numbers - Numbers are converted between types as needed ## Responses ### Plain Data Return any Ruby object that can be JSON serialized: ```ruby Route.get do |request| { name: 'Donald' } end Route.get do |request| 'Hello, world!' end Route.get do |request| [ { name: 'Donald' }, { name: 'Ryan' } ] end ``` ### Views Render views with props: ```ruby # routes/view.rb require 'primate/route' require 'primate/response' Route.get do |request| Response.view('index.html', hello: 'world') end ``` With options: ```ruby require 'primate/route' require 'primate/response' Route.get do |request| Response.view('index.html', { hello: 'world' }, { partial: true } ) end ``` ### Redirects Redirect to another route: ```ruby # routes/redirect.rb require 'primate/route' require 'primate/response' Route.get do |request| Response.redirect('/redirected') end ``` With custom status code: ```ruby Route.get do |request| Response.redirect('/redirected', status: 301) # moved permanently end ``` ### Error Responses Return error responses: ```ruby # routes/error.rb require 'primate/route' require 'primate/response' Route.get do |request| Response.error end ``` With custom error options: ```ruby Route.get do |request| Response.error(body: 'Custom error message') end ``` ## Sessions Manage user sessions with the session module: ```ruby # routes/session.rb require 'primate/route' require 'primate/session' Route.get do |request| # Create a session Session.create(foo: 'bar') # Get session data Session.get end ``` ### Session Methods - `Session.create(data)` - creates a new session with data - `Session.get` - gets session data (raises if no session) - `Session.try` - gets session data (returns nil if no session) - `Session.set(data)` - updates session data - `Session.destroy` - destroys the session - `Session.exists?` - checks if session exists - `Session.id` - gets the session ID ## Ruby Conventions Primate Ruby follows standard Ruby conventions: ### Method Names - Use `snake_case` for method and variable names - Use `?` suffix for boolean methods: `has?`, `exists?` - Use `!` suffix for destructive methods (when applicable) ### Hash Syntax Use modern Ruby hash syntax with symbols: ```ruby # Preferred { name: 'John', age: 30 } # Also valid { :name => 'John', :age => 30 } ``` ### String Literals Use single quotes for plain strings, double quotes for interpolation: ```ruby # Plain strings message = 'Hello, world!' # String interpolation greeting = "Hello, #{name}!" ``` ## Configuration | Option | Type | Default | Description | | --------- | -------- | ------- | ------------------------- | | extension | `string` | `".rb"` | Associated file extension | ### Example ```ts import ruby from "@primate/ruby"; import config from "primate/config"; export default config({ modules: [ ruby({ // use `.ruby` as associated file extension extension: ".ruby", }), ], }); ``` ## Resources - [Documentation] - [Ruby Language Reference](https://ruby-doc.org/) - [WebAssembly with Ruby](https://github.com/ruby/ruby.wasm) [Documentation]: https://ruby-doc.org [Ruby]: https://www.ruby-lang.org Source: https://primate.run/docs/backend # 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] | ✓ | ✓ | ✓ | ✓ | | | ✓ | | [Python]| ✓ | ✓ | ✓ | ✓ | | | ✓ | | [Ruby] | ✓ | ✓ | ✓ | ✓ | | | ✓ | [Go]: /docs/backend/go [Python]: /docs/backend/python [Ruby]: /docs/backend/ruby ## Features * **Routes** — HTTP method handlers (GET, POST, PUT, DELETE, etc.) * **Body** — handle JSON, form data, text, binary, and file uploads * **Query** — access URL query parameters and path variables * **Sessions** — server-side session management with cookies * **Stores** — built-in database operations (CRUD, queries, validation) * **WebSocket** — real-time bidirectional communication * **Validation** — strongly-typed request/response validation schemas ## WebAssembly Backends run in the JS runtime via WebAssembly or a Wasm-based runtime (e.g. Pyodide for Python). ## Language Categories ### Pre-Compiled Languages *Go* 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: ```bash npm install @primate/go ``` Configure it: ```ts // config/app.ts import config from "primate/config"; import go from "@primate/go"; export default config({ modules: [go()], }); ``` Create a route handler: ```go // 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 // Go var _ = route.Get(func(request route.Request) any { ... }) var _ = route.Post(func(request route.Request) any { ... }) ``` ```python # Python @Route.get def get(request): ... @Route.post def post(request): ... ``` ```ruby # Ruby Route.get do |request| ... end Route.post do |request| ... end ``` ### Request Handling Access request data through consistent APIs: ```go // Go - Query parameters if request.Query.Has("name") { name, _ := request.Query.Get("name") return name } // Go - JSON body json, err := request.Body.JSON() ``` ## Response Types ### Data Responses Return structured data as JSON: ```go return map[string]any{"message": "Hello"} ``` ### Views Render views: ```go return response.View("view.html", data) ``` ### Redirects Redirect to other routes: ```go return response.Redirect("/other-route") ``` ## Database Operations Built-in store operations: ## Session Management Server-side sessions with automatic cookie handling: ```go // Go session.Create(map[string]any{"user": "john"}) data := session.Get() ``` ## Mixing Backends Multiple backends can coexist; each handles its own file extensions: ```ts import config from "primate/config"; import go from "@primate/go"; import python from "@primate/python"; export default config({ modules: [go(), python()], }); ``` Routes can be implemented in different languages: ``` routes/ ├── auth.go # Go authentication ├── api.py # Python data processing └── admin.rb # Ruby admin interface ``` ## Ecosystem Integration ### Package Management Each backend uses its native package manager: - **Go** — `go.mod` and `go get` - **Python** — `requirements.txt` and `pip` - **Ruby** — `Gemfile` and `bundle` ### External Libraries Import and use libraries native to each language, compiled to WebAssembly: ```go // Go import "encoding/json" import "crypto/sha256" ``` ```python # Python import numpy as np import pandas as pd ``` ```ruby # Ruby require 'json' require 'digest' ``` Source: https://primate.run/docs/configuration # Configuration Primate works out of the box with zero configuration. In some cases, you may wish to change the defaults. The most common use case is activating additional modules. Configuration files are located in `config`. Anything you configure is merged into the defaults. ## `app.ts` %%% TypeScript:config/app.ts, JavaScript:config/app.js ```ts import config from "primate/config"; export default config({ /* options */ }); ``` ```js import config from "primate/config"; export default config({ /* options */ }); ``` %%% `config/app.ts` exports the application facade for your app. For using that value at runtime — including `app.config()`, `app.env()`, `app.view()`, and `app.root` — see the [Application] page. ### App options |Option|Default|Description| |-|-|-| |[build.external](#build-external)|`[]`|packages excluded from the server bundle| |[client.partitions](#client-partitions)|`undefined`|isolated client bundle partitions| |[client.routes](#client-partitions)|`undefined`|route-prefix to bundle-partition mapping| |[db.migrations](@/config/db-migrations)|`undefined`|database migration configuration| |[entrypoints](#entrypoints)|`{}`|named client entrypoints| |[http.csp](#http-csp)|`{}`|content security policy| |[http.headers](#http-headers)|`{}`|default HTTP response headers| |[http.host](#http-host)|`"localhost"`|server host| |[http.port](#http-port)|`6161`|server port| |[http.timeout](#http-timeout)|`undefined`|in-flight HTTP idle timeout| |[http.ssl.cert](#http-ssl-cert)|`undefined`|path to SSL certificate| |[http.ssl.key](#http-ssl-key)|`undefined`|path to SSL private key| |[http.static.root](#http-static-root)|`"/"`|web path of static assets| |[loaders](#loaders)|`{}`|esbuild file loaders by extension| |[env.schema](#env-schema)|`undefined`|schema for typed environment variables| |[modules](#modules)|`[]`|extension modules| |[i18n.defaultLocale](#i18n-defaultlocale)|`undefined`|default locale| |[i18n.locales](#i18n-locales)|`[]`|list of locales| |[i18n.currency](#i18n-currency)|`"USD"`|active currency| |[i18n.persist](#i18n-persist)|`"cookie"`|locale persistence mode| ### `build.external` Packages and import paths to exclude from the production server bundle. At runtime they are loaded from `node_modules` instead. Use this for dependencies that load adjacent WebAssembly modules, native addons, or data files that would otherwise be separated from the package by bundling. ```ts import config from "primate/config"; export default config({ build: { external: ["satori", "@resvg/resvg-js"], }, }); ``` External dependencies must be installed wherever the production server runs. Development already keeps packages external by default. ### `db.migrations` Configuration for Primate's opt-in migration system. ```ts import config from "primate/config"; import db from "@/config/db"; export default config({ db: { migrations: { table: "migration", db, }, }, }); ``` Apply migrations explicitly with `primate migrate:apply` during development or `build/migrate.js apply` before starting a production server. Automatic startup application is not supported. See the [Stores] page for the complete migration workflow and 0.41 upgrade guidance. ### `client.partitions` Client partitions isolate browser code and assets by URL route prefix. Each partition is an independent build branch, compiled into its own output file tree. Shared dependencies are duplicated rather than emitted as chunks shared across an access boundary. ```ts import config from "primate/config"; export default config({ client: { partitions: { public: "/", admin: "/admin/app/", }, routes: { "/": "public", "/admin": "admin", }, }, }); ``` `client.partitions` maps partition names to asset URL prefixes. `client.routes` maps URL path prefixes to those names; values that are not keys in `client.partitions` are rejected. The longest matching route prefix wins, and prefixes match whole path segments: `/admin` includes `/admin/users` but not `/administrator`. A `/` assignment is required as the default and is checked during startup. Mappings to unknown partitions, duplicate normalized mappings, and duplicate asset prefixes are also rejected during startup. In 0.42, `client.partitions` replaces the 0.41 `client.groups` key. Rename the key when upgrading; the old key is rejected with a migration diagnostic rather than treated as a virtual route group. `client.routes` is unchanged. Each partition receives its own bootstrap, route-page registry, CSS, dynamic imports, and file-loader assets beneath its configured asset prefix. Top-level files in `views` are shared deliberately and are duplicated into every partition; put partition-private pages and layouts beside their handlers in `routes`. Imported fonts, images, and other file-loader assets are likewise emitted under each partition's prefix, even when their contents are identical. Distinct URLs are part of the isolation guarantee. Assets that are deliberately public and shared across every authorization boundary can instead live in `static`. Navigation within a partition keeps normal client routing. Navigation across partitions stays in the same document: Primate first loads the destination assets and inactive styles and waits for its frontend to become ready. It then disposes the active frontend instance, replaces its partition styles, and mounts the destination. Links, form redirects, and browser history all use this transition. If a destination asset cannot be loaded or is rejected by the proxy, the active app, URL, and styles remain in place. Static module code may remain in the browser's module cache after disposal. This allows a reverse proxy to apply the same authentication rule to both the HTML route and its complete client file tree. For example, protect `/admin` and `/admin/app/`, with the more specific asset rule evaluated before a catch-all public rule. Production filenames are content-hashed and can be cached as immutable assets; HTML should not be cached with the same policy. Client partitions provide asset isolation, not application authorization. Continue to authorize every protected request in route hooks or handlers. Source maps are not emitted by Primate's client production build. ### `entrypoints` Named client entrypoints bundled from the `client` directory and injected into `templates/app.html` as placeholders. ```ts import config from "primate/config"; export default config({ entrypoints: { css: "master.css", colorscheme: "colorscheme.ts", }, }); ``` Each key becomes a placeholder in your HTML template. For a key `css`, add `%css%` wherever you want the asset injected: ```html
%css% %head% %body% ``` Entrypoint keys follow the placeholder naming rules: they start with a letter or underscore and may contain letters, numbers, underscores, and hyphens. Any file type supported by your active frontend modules works as an entrypoint — if you have `@primate/svelte` configured, you can point an entrypoint at a `.svelte` file and it will be bundled and injected like any other asset. The names `app`, `head`, and `body` are reserved and cannot be used as entrypoint keys. ### `http.csp` The Content Security Policy (CSP) to use. Example of a restrictive policy. ```js { // all content must come from own origin, excluding subdomains "default-src": ["'self'"], // styles must come from own origin, excluding subdomains "style-src": ["'self'"], // disallow