Skip to Content
Single-node · millions of events/sec · JSON over HTTP

Millions of events a second, with the simplicity of SQLite

topics is a persistent event log that runs as one process on one machine — no cluster, no coordination, the way SQLite is one file instead of a database fleet. Yet the engine moves millions of events a second over a small HTTP/JSON API. Write to a topic, read it back by sequence number, follow it live, delete what you’re done with — and choose, per topic, how durable each write should be. Nothing is ever lost without telling you.

bash — record an order, read it back
# Point $TOPICS at your server, then append an event. The topic is created # on first write, and the server assigns the sequence number. export TOPICS=http://localhost:4000 curl -X POST $TOPICS/v0/topics/orders \ -H 'content-type: application/json' \ -d '{ "records": [ { "data": { "sku": "AEROPRESS-GO", "qty": 1, "total": 3499 } } ] }' # → { "topic": "orders", "seqs": [1], "head_seq": 1, ... } # Read everything after sequence 0. The sequence number is the cursor. curl -X POST $TOPICS/v0/topics/orders/diff -d '{ "from_seq": 0 }' # → { "records": [ { "$seq": 1, "$ts": 1748470000123, "data": { "sku": "AEROPRESS-GO", ... } } ], # "next_from_seq": 1, "caught_up": true }
Why topics

Three ideas. That’s the whole pitch.

A persistent event log doesn’t have to mean a distributed system to operate or an SDK to learn. topics makes three deliberate bets, and everything else follows from them.

The API

The whole thing is a handful of HTTP calls

topics is a log you POST events to and read back by number. The server stamps each event with a monotonic seq; reading is just “give me everything after this number.” That sentence is the entire model — here is every verb it expands into.

create

A topic appears on first write — or PUT /v0/topics/:topic to set config up front. Nothing to provision.

write

POST /v0/topics/:topic with a batch of records. The server numbers each one and returns the assigned seqs.

read

POST …/diff with from_seq. Get the records after it plus the next cursor. The seq is the cursor — advancing it is the ack.

watch

POST /v0/watch, then open the SSE stream. Follow many topics live over one resumable connection, woken on append.

delete

POST …/delete by seq range and/or tag. Permanent, immediate, point-in-time — it never touches records written later.

fan out

PUT /v0/routers/:r to copy every record from one topic into another, server-side, preserving origin.

No connection modes, no binary framing, no consumer-group bookkeeping on the server. Read it at three levels of depth on the API reference.

Run it on one machine

Like SQLite: huge capability, almost nothing to operate

SQLite became the most-deployed database on earth by refusing to be a server you run — it’s a library and a file. topics brings that operating model to event streaming: a Kafka-shaped job done with an SQLite-shaped footprint. It is a networked server, so every service can share it — but running it is one process and one directory, not a cluster to coordinate.

Durability you choose

Decide what an “ok” means — one topic at a time

Every write either stays resident-only in RAM or travels the disk path: from your process, into the kernel, onto the physical disk. You decide per topic, so a live fan-out feed, a throwaway cache, and a financial ledger can live side by side in one process without paying for each other’s guarantees.

ephemeral

RAM-only live feed

Records stay resident and skip the WAL/segment path. The topic config persists and seqs stay monotonic, but records are intentionally gone after restart.

memory

Fastest disk-like

Uses the WAL path with no durability guarantee: after a restart its records may survive or may be gone. For caches and scratch state where losing a little is fine.

disk

Survives a crash

Written to a group-committed write-ahead log before the ack. Survives a process crash, losing only the most recent not-yet-flushed tail. The everyday default.

fsync

Survives power loss

The ack waits until the data is fsynced to the platter. An acknowledged write survives any crash, power loss included, and is replayed from the log on restart. For ledgers and queues.

Whatever the class, an acked write is published under the contract you picked. Read the full contract on Durability Classes.

Mental model

Five nouns and you know the system

topics is deliberately small. Learn five concepts and every endpoint is just one of them doing its job.

Topic

A named, append-only log of records ordered by a monotonic seq. Created lazily on first write.

Record

One immutable event. The server assigns $seq and $ts; you supply opaque data, an optional tag and node.

seq

The cursor. Reads are “give me everything after from_seq.” The client owns its position; advancing it is the ack.

Router

A server-side source → dest forwarding rule. Fan-out that preserves origin node, so N nodes mirror without echo.

Tombstone

The explicit “you missed data” signal. Cap/TTL loss returns an in-band gap range — at HTTP 200, never silently.

The load-bearing rule connecting them: loss you didn’t ask for is always announced; removal you did ask for is silent. Read it in full in the mental model.

How it compares

SQLite is to a database cluster what topics is to Kafka

topics is a single-machine peer to Redis Streams, Kafka, NATS JetStream, and the cloud queues — trading horizontal scale for operational simplicity and an explicit, in-band loss signal none of them provide.

A durable event log, up and running in a minute

Start topics on one machine and write your first record before your coffee’s cold. Turn the durability dial up only where you need it.