Quickstart
Add Forge to the backend process, point it at a PostgreSQL database it can own, and call init() to get one client for all eight primitives. Forge does not create an HTTP server, install process signal handlers, or add frontend dependencies.
Install
Section titled “Install”npm install forgelibpip install forgelibcargo add forgelibgo get github.com/isala404/forge/bindings/goGive Forge a database
Section titled “Give Forge a database”Forge keeps its own state in Postgres. It owns its forge_* tables, so give it a database (or its own schema) and keep your product tables separate. Development startup migrates automatically; production deployments call the migration API before starting the application.
# any Postgres works; create one for Forge to owncreatedb myappWrite forge.toml
Section titled “Write forge.toml”Configuration lives in a forge.toml at your project root. It’s the single source
of config: init() reads it and builds the runtime. String values can read the
environment with ${VAR} or ${VAR:-default}, so the same file works in tests and
in production.
[postgres]url = "${DATABASE_URL:-postgres://localhost/myapp}"Everything else has a convenient development default, so this is enough to start locally. Set forge.environment = "production" in production; automatic migration then defaults off. The full list of settings is in Configuration.
Make your first calls
Section titled “Make your first calls”init() reads forge.toml, migrates the system database, and returns a client.
The same calls look like this in each language. (Pick a language once and the tabs
on this site follow your choice.)
import { ForgeClient } from "forgelib";
const forge = await ForgeClient.init(); // reads ./forge.toml, runs migrations
// auth: hash a password, open a sessionconst hash = await forge.hashPassword(password);const token = await forge.createSession(userId);
// key/value: an atomic counterconst views = await forge.kvIncr(`views:${userId}`, 1);
// queue: hand work to the backgroundawait forge.queue<{ to: string }>("emails").enqueue({ to: email });
// rate limit: 20 attempts per minute, keyed by emailconst limit = await forge.rateLimitCheck("login", email, 20, 60);if (!limit.allowed) throw new Error("slow down");import forgelib
forge = await forgelib.ForgeClient.init() # reads ./forge.toml, runs migrations
# auth: hash a password, open a sessionhash = await forge.hash_password(password)token = await forge.create_session(user_id)
# key/value: an atomic counterviews = await forge.kv_incr(f"views:{user_id}", 1)
# queue: hand work to the backgroundawait forge.queue("emails").enqueue({"to": email})
# rate limit: 20 attempts per minute, keyed by emaillimit = await forge.rate_limit_check("login", email, 20, 60)if not limit.allowed: raise RuntimeError("slow down")use std::time::Duration;use forgelib::{Forge, SessionOpts, EnqueueOpts, Limit};
let forge = Forge::init().await?; // reads ./forge.toml, runs migrations
// auth: hash a password, open a sessionlet hash = forge.auth().hash_password(&password).await?;let token = forge.auth().create_session(&user_id, SessionOpts::default()).await?;
// key/value: an atomic counterlet views = forge.kv().incr(&format!("views:{user_id}"), 1).await?;
// queue: hand work to the backgroundforge.queue().enqueue("emails", payload.into(), EnqueueOpts::new()).await?;
// rate limit: 20 attempts per minute, keyed by emaillet limit = forge.ratelimit() .check("login", &email, Limit::per_duration(20, Duration::from_secs(60))) .await?;if !limit.allowed { /* return 429 */ }import ( "context" "time"
forge "github.com/isala404/forge/bindings/go")
client, err := forge.InitFrom(context.Background(), "forge.toml")if err != nil { return err }defer client.Close(context.Background())
hash, err := client.HashPassword(context.Background(), password)if err != nil { return err }token, err := client.CreateSession(context.Background(), userID, forge.SessionOptions{})if err != nil { return err }
views, err := client.KVIncr(context.Background(), "views:"+userID, 1)if err != nil { return err }_, err = client.Enqueue(context.Background(), "emails", payload, forge.EnqueueOptions{})if err != nil { return err }
limit, err := client.RateLimitCheck(context.Background(), "login", email, forge.RateLimitOptions{Max: 20, Per: time.Minute})if err != nil { return err }if !limit.Allowed { /* return 429 */ }One shape difference is worth noting. Rust reaches each primitive through an accessor and uses option structs, like forge.kv().set(key, value, SetOpts::new()). JavaScript and Python keep the exact raw methods on the client (forge.kvSet(...), forge.kv_set(...)) for contract parity and add native JSON handles for application values. Go uses one concrete client with explicit option structs and context.Context on every blocking operation.
Run the background work
Section titled “Run the background work”Enqueued jobs sit in the queue until an application-owned worker process starts a Forge worker. Schedules fire only when the application runs a scheduler loop. A normal deployment runs workers for the queues it owns, a scheduler loop, and periodic maintenance. Operations covers those roles, and Integrations shows HTTP, deployment, MCP, and observability boundaries.