Redis and sessions
The embedded RESP driver, the session module, cookie settings, flash data and conflict handling.
use redis
$redis = new redis({
host: "localhost",
port: 6379,
db: 0,
password: "1234"
}).connect()The driver is built into the core: it speaks the RESP protocol directly, with no external Go package. There is no pooling — the same model as postgres.
| Call | Returns |
|---|---|
$redis.connect() | $redis (chainable) |
$redis.ping() | "PONG" |
$redis.get($key) | the value, or null |
$redis.set($key, $value) | $redis |
$redis.set($key, $value, {lifetime: 60}) | $redis — lifetime in minutes |
$redis.has($key) | true / false |
$redis.delete($k1, $k2, …) | number deleted |
$redis.expire($key, $minutes) | true / false |
$redis.ttl($key) | remaining seconds, or null |
$redis.increment($key) | the new value |
$redis.command("LPUSH", "queue", "job") | the raw reply |
$redis.close() / $redis.is_closed() | — |
Objects and arrays are stored as JSON:
$redis.set("user", {id: 42, name: "Ali"}) # {"id":42,"name":"Ali"}command is the escape hatch for commands the module does not expose.
Sessions#
use redis, session
$redis = new redis({host: "localhost", port: 6379, db: 0}).connect()
session.redis = $redis
session.start()
$user = session.get("user")
if $user == null then
out("Not signed in")
end ifsession.redis is the only assignable module field; modules are otherwise read-only.
Session state is specific to each request — the module is recreated for every use session and does not leak between requests.
Order matters: session.redis and session.config.set must come before start(), because start() reads the cookie with those settings.
The storage model#
| Layer | What it holds |
|---|---|
| Cookie | the session_id only |
| Redis key | lexer:session:{id} |
| Redis value | {id, version, created_at, last_activity, data, flash} |
session.get/set | the data part only |
An empty session is never written to Redis — no record is created until the first value is stored, so empty records do not pile up for visitors and bots that do nothing.
There is no secret setting: only a 128-bit random id goes into the cookie and the data stays in Redis. Signing is needed by designs that carry the data in the cookie.
Methods#
| Call | Description |
|---|---|
session.start() | starts or resumes, returns the id |
session.id() | the current session_id |
session.get($key[, $default]) | the value, else null / $default |
session.set($key, $value) | writes with a version check |
session.has($key) · session.delete($key) | |
session.clear() | clears the data, keeps the id |
session.destroy() | destroys the record, data and cookie |
session.regenerate() | new id, data carried over |
session.all() · session.meta() |
Cookie settings#
session.config.set({
cookie_name: "lexer-id", # default LEXER_SESSION_ID
cookie_lifetime: 1440, # minutes; default 120, 0 = session cookie
cookie_path: "/",
cookie_domain: "", # empty = only the domain serving the request
cookie_secure: true, # default false
cookie_httponly: true, # default true
cookie_samesite: "Lax" # Lax | Strict | None
})When SameSite: None is given, Secure is turned on automatically — browsers would otherwise ignore the cookie entirely.
Keys are validated. A misspelled setting is not silently ignored:
session.config.set: bilinmeyen ayar "cookie_http_only";
"cookie_httponly" mi demek istediniz?If it stayed quiet, a typo in cookie_secure would leave the cookie unprotected.
Flash data#
Data that lives until the next request.
session.flash("success", "Your profile was updated") # writes
$message = session.flash("success") # reads and removesIf it is not read it survives one more request and is removed after that. Also: session.flash_has, session.flash_all, session.reflash, session.keep_flash.
Conflict handling#
The default is optimistic locking: each write checks the version, and on a conflict re-reads the record, replays the change onto the refreshed data and retries (three times by default). When those run out, SessionConflictError is raised.
To make several writes in one go:
session.with_lock(func()
$n = session.get("counter", 0)
session.set("counter", $n + 1)
end func)try
session.set("cart", $cart)
catch($e)
if $e.type == "SessionConflictError" then
out("session conflict")
end if
end tryAfter signing in#
session.regenerate()
session.set("user_id", $user.id)Regenerating the id after a successful sign-in defeats session fixation — an attacker planting a known id in the victim's browser beforehand.