Writing an appview. How hard can it be? It’s just indexing records from the firehose/jetstream and then returning those records via xrpc endpoints…right?

Back in April 2026 there was quite a major Bluesky appview outage. It meant most people could log into the Bluesky app, but saw no posts at all. That sucked because that’s why we use Bluesky. Indicators that it was an appview issue were confirmed by logging into Blacksky and being able to see posts there. What a win for decentralised social media that is!

It got me thinking though. What if I had my own appview that I could use for situations like this. There’s actually been a few outages this past week, although I didn’t notice them as much as I was enjoying a holiday in Wales.

If I can host a PDS on my own hardware (well a VPS at the moment) and I can host a Tangled Knot and Spindle, my own Go implementation of Statusphere and many other ATProto projects I have on a Pi in my kitchen, then why not a small Appview as well.

I don’t need to because of others such as Blacksky, but this got me excited to at least try it out, and also get a better understanding of how an Appview actually works for things like Timelines and Feeds.

My Appview was going to be different though. I didn’t need to index posts for every single person. I was this Appview to be for me (or someone else if they self host it too). So I only really need to index records for the users I follow. Or so I thought. 3rd party feeds that I load in the Bluesky app will also go through my Appview, so it will need to return records for those too. I have a plan for that, but it’s for much further down the line!

Turns out that already implemented on in Go (my language of choice and what I use day to day) called Konbini. To prevent myself from being tempted to copy/paste his when I got stuck I decided to try it in Rust (a language I’ve been trying to learn for a while). And so My-Appview was born.

Konbini was actually really useful to read through first because it gave me the template of which xrpc endpoints I needed to implement. So I grabbed all of the ones I could find and documented them ready for implementation later. The first job though was indexing. No point having xrpc endpoints return data if there’s no data to return.

I started to work on consuming posts to index and initially decided to try Tap. I implemented a basic version of Tap, that would subscribe to repos I followed and then if I followed/unfollowed users it would subscribe/unsubscribe automatically. That was actually pretty straight forward (once I had got my head around a lot of Rust shenanigans).However it then occurred to me, that I had to host Tap on my Pi as well, which actually consumes the entire Firehose which takes up a lot of my home internet bandwidth. Opps!

Maybe Jetstream is the better option here. I ripped out the Tap code and implemented Jetstream instead. This time I subscribe to app.bsky.feed.post, app.bsky.feed.repost and app.bsky.graph.follow events. For the follow events, if the DID on the event wasn’t my own DID, the event is discarded. I don’t care about other peoples follows. For my follows though I then add it to an array that I then use to filter out the posts/repost events. If the post/repost event if for a user I follow, then I index it, otherwise I don’t care.

That seemed to work however I needed to backfill my follows on app startup. I only really want to index posts newer than 24 hours. I don’t really look further back than that in the Bluesky app. That means I can set the start point cursor for Jetstream to be now - 24 hours and by doing that means I will only get my follows from less than 24 hours ago.

Well that’s a nice and easy problem to solve. On app startup I just pull all of my follow records from my repo using an xrpc request.Once I have them, I can then pass that into my consumer logic and update it as I follow/unfollow while the Appview is running.

That is where I’m at today. That in itself, to me as someone learning Rust, has actually been a lot of work so I’m pretty damn proud that I’ve done it because I’ve given up on Rust so many times before.

Next up is to implement some of the xrpc endpoints that an Appview requires, starting with an absolute beast of an endpoint…xrpc/app.bsky.actor.getProfile This has got a lot of information that I’ll need to pull from my PDS and possible from other places too. It’s going to be a challenge and I’m really excited to give it a go.

This is why I’m writing this post actually. I find that I become more motivated on my projects and follow through with them if I write about them as I go. There was a 4 month gap between the Tap and Jetstream implementation because I just didn’t feel motivated. However I’ve put all my projects to one side (including trying out the new atproto spaces stuff)and really want to focus on this one. So hopefully I’ll see this one through to the end!

If you want to follow along with the code, then take a look here :)