Skip to content

AppView

Colibri’s AppView is what you would call the “Backend” in a normal full-stack application. It handles HTTP requests and WebSocket connections from clients and stores AT protocol records for caching.

The reference AppView is written in Rust using the framework Rocket and a few other libraries. Since backfilling is very difficult to implement, we have opted to use Tap, an ATproto firehose consumer, to handle backfill. The AppView connects to Tap for live events via a WebSocket connection. They both connect to a single PostgreSQL database.

Colibri AppView architectureThe client sends requests through the user's PDS to the AppView and opens a direct WebSocket to it for live events. Tap consumes the ATproto firehose and relays events to the AppView. Both Tap and the AppView read from and write to a single PostgreSQL database.requestsservice authsubscribeEvents (direct WS connection)Firehoselive events (WS)read / writewritesClientSolidJS UIYour PDSATproto proxyFirehoseATproto networkTapBackfill + RelayPostgreSQLshared storeAppViewRocket

All three services can be deployed using a single Docker compose file. The entire setup is meant to be easily self-hostable, so it’s designed to use very little RAM.

Service Average RAM usage
Tap ~60 MiB - 1 GiB
Postgres ~70 MiB - 120 MiB
AppView ~40 MiB - 1 GiB

These are baseline figures.

A small VPS with 2+ CPU cores and 4+ GB of RAM should be able to handle basic usage. A system with 4+ CPU cores and 6+ GB of RAM is recommended.

Because Colibri is self-hostable, a single community’s members can be spread across several independent AppViews. On-protocol data (messages, memberships, roles, and so on) lives in repositories, so it reaches every AppView through the tap firehose regardless of where it originated. Off-protocol signals like online status, typing indicators, and voice presence never touch a repo. Without help they stay trapped inside the AppView that produced them, so a member on one instance would appear permanently offline to members on another.

Humming bridges that gap. A Hum is a single off-protocol event that one AppView relays to another, where it is delivered to that instance’s clients over their ordinary subscribeEvents stream. Humming carries only these ephemeral signals, never data about messages, memberships, or any other on-protocol record, all of which continue to flow through the firehose.

Every community names a single AppView as its hub through the appview field on its community record, the same instance that administers the community and hosts its voice SFU. Presence then flows through that hub in a star topology:

  1. A leaf (any AppView with a local, online member of the community) forwards that member’s Hums to the hub via sendHum.
  2. The hub delivers the Hum to its own local clients and fans it out to every leaf subscribed over subscribeHums.
  3. Leaves never re-forward what they receive.
Humming star topologyA leaf AppView forwards a local member's off-protocol event to the community's hub via sendHum. The hub delivers it to its own clients and fans it out to every other subscribed leaf via subscribeHums. Leaves never re-forward, so a Hum makes at most two hops.sendHumsubscribeHumsLeaf AppViewmember onlineHubcommunity appviewLeaf AppViewother membersLeaf AppViewother membersLeaf AppViewother members

A Hum therefore makes at most two hops, and an instance only ever sees traffic for communities its own users belong to. There is no whole-network flood, and propagation is bounded structurally as well as by a TTL, per-peer rate limits, and connection caps.

Cross-instance presence is opt-in per user. A user opts in by publishing a presenceService DID on their profile record, naming the one AppView allowed to speak for their presence. The reference client keeps this in sync with the AppView you actually connect to and exposes a “Share presence across AppViews” toggle (on by default). Removing the field opts out, meaning every AppView then drops Hums about that user, while their local, same-instance presence is unaffected.

The full wire format, endpoint behaviour, and the complete trust-model checklist are documented on the specification page.

The same per-community hub that carries presence also owns the community’s data. Only the AppView holding a community’s credentials can write to its repo, so moderating or configuring a community means reaching that AppView, which may not be the one you signed in to.

Clients never address a foreign AppView. Your own AppView notices that a community belongs to another one and relays the request there instead, returning that AppView’s answer as if it had handled it. Reads need none of this. Bans and hidden messages are ordinary social.colibri.moderation records on the community’s own repo, so every AppView rebuilds identical moderation state from the firehose. Credentials gate writes only.

A relayed request is a claim by one AppView that a user wants something, which the receiving AppView cannot take on faith. The acting user’s DID therefore rides inside the signed token as an act claim, and the hub honours it only when that user’s own profile names the sending AppView in presenceService, the same field Humming uses. Identity is the only thing delegated: the hub still derives that user’s roles and permissions from its own mirror of the community.

This shares the presence opt-in deliberately. A user who has not published a presenceService has not told anyone which AppView may speak for them, so a community hosted elsewhere refuses to act on their behalf. The reference client explains the coupling next to the “Share presence across AppViews” toggle, warns before you switch it off, and warns again on the moderation screens of a community hosted elsewhere.

Relays never chain. A request that arrives already carrying an act and still is not for a community this AppView administers is refused, naming the correct hub, rather than forwarded onward.

For the wire format, the full check list, and the outbound transport rules, see the specification page.