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.
# 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 }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.
A small API
Create a topic, write to it, read it back by sequence, follow it live, delete what you don’t need. A handful of HTTP calls, JSON in and out, IDs assigned for you. curl is a complete client — there is nothing to install.
One node, not a fleet
It runs on a single machine, so you skip the distributed system entirely — no cluster, no quorum, no replication hops — the way SQLite is a file, not a database fleet. Growing means a bigger machine, not more of them to keep alive.
Why one node →Durability you choose
Decide per topic how much crash-safety a write buys: resident-only in RAM, best-effort on the WAL path, crash-survivable on disk, or fsync’d against power loss. And you are never lied to — lost data always comes with a signal.
Durability classes →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.
A topic appears on first write — or PUT /v0/topics/:topic to set config up front. Nothing to provision.
POST /v0/topics/:topic with a batch of records. The server numbers each one and returns the assigned seqs.
POST …/diff with from_seq. Get the records after it plus the next cursor. The seq is the cursor — advancing it is the ack.
POST /v0/watch, then open the SSE stream. Follow many topics live over one resumable connection, woken on append.
POST …/delete by seq range and/or tag. Permanent, immediate, point-in-time — it never touches records written later.
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.
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.
Millions of events a second
The engine appends and projects records at millions a second in-process, and a non-durable write acks in well under a millisecond — because the log lives on one local sequential disk it fully owns, with nothing to replicate or agree on first.
Nothing to coordinate
One process, one data directory — no ZooKeeper, no quorum, no partitions, no rebalancing, no JVM. Like SQLite, the hard part (a correct, durable log) is on the inside; the outside is just a thing you run.
Restarts, not orchestration
The cost of one node is brief downtime on deploys, patches, and the rare crash. topics makes it cheap: it replays its write-ahead log and gates readiness until recovery finishes. You trade seconds of occasional downtime for a system you can hold in your head.
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.
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.
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.
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.
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.
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.
A named, append-only log of records ordered by a monotonic seq. Created lazily on first write.
One immutable event. The server assigns $seq and $ts; you supply opaque data, an optional tag and node.
The cursor. Reads are “give me everything after from_seq.” The client owns its position; advancing it is the ack.
A server-side source → dest forwarding rule. Fan-out that preserves origin node, so N nodes mirror without echo.
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.
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.
vs Redis Streams
Per-write durability classes instead of one global appendfsync; gap tombstones where Redis trims silently.
vs Apache Kafka
One process, no JVM, no cluster — and an in-band “you missed X..Y”, which Kafka leaves to lag metrics.
Compare →vs NATS JetStream
The closest single-machine peer. topics adds delivery-time gap signals and built-in dead-letter moves.
Compare →vs SQS / Kinesis / Pub/Sub
Log + queue + fan-out in one self-hosted API, with fixed cost and full data locality.
Compare →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.