Skip to content

Quickstart

Forge is one dependency. You point it at a Postgres database it can own, call init(), and you have all eight primitives on a single client. This page gets you from nothing to your first calls.

Terminal window
npm install forgelib

Forge keeps its own state in Postgres. It owns its forge_* tables and runs its migrations for you when the app starts, so give it a database (or its own schema) and keep your product tables separate.

Terminal window
# any Postgres works; create one for Forge to own
createdb myapp

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.

forge.toml
[postgres]
url = "${DATABASE_URL:-postgres://localhost/myapp}"

Everything else has a production-safe default, so this is enough to start. The full list of settings is in Configuration.

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 session
const hash = await forge.hashPassword(password);
const token = await forge.createSession(userId);
// key/value: an atomic counter
const views = await forge.kvIncr(`views:${userId}`, 1);
// queue: hand work to the background
await forge.queue<{ to: string }>("emails").enqueue({ to: email });
// rate limit: 20 attempts per minute, keyed by email
const limit = await forge.rateLimitCheck("login", email, 20, 60);
if (!limit.allowed) throw new Error("slow down");

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()). Node and Python keep the exact raw methods on the client (forge.kvSet(...), forge.kv_set(...)) for contract parity, and add native JSON handles (forge.kv<T>(key), forge.queue<T>(name), forge.kv(key), forge.queue(name)) for the app values you want typed in day-to-day code.

Forge doesn’t start any threads of its own. Enqueued jobs sit in the queue until a worker leases them, and schedules only fire when something ticks them. In a typical app you run a worker for each queue and one loop that ticks the scheduler and does housekeeping. Operations covers both, and Recipes shows them inside real handlers.