+14
-8
README.md
···
···
-644
TUTORIAL.md
···-In this guide, we're going to build a simple multi-user app that publishes your current "status" as an emoji.-We're going to keep this light so you can quickly wrap your head around ATProto. There will be links with more information about each step.-Data in the Atmosphere is stored on users' personal repos. It's almost like each user has their own website. Our goal is to aggregate data from the users into our SQLite DB.-Think of our app like a Google. If Google's job was to say which emoji each website had under `/status.json`, then it would show something like:-The Atmosphere works the same way, except we're going to check `at://` instead of `https://`. Each user has a data repo under an `at://` URL. We'll crawl all the `at://`s in the Atmosphere for all the "status.json" records and aggregate them into our SQLite database.-> `at://` is the URL scheme of the AT Protocol. Under the hood it uses common tech like HTTP and DNS, but it adds all of the features we'll be using in this tutorial.-Our repo is a regular Web app. We're rendering our HTML server-side like it's 1999. We also have a SQLite database that we're managing with [Kysley](https://kysely.dev/).-With each step we'll explain how our Web app taps into the Atmosphere. Refer to the codebase for more detailed code — again, this tutorial is going to keep it light and quick to digest.-When somebody logs into our app, they'll give us read & write access to their personal `at://` repo. We'll use that to write the `status.json` record.-We're going to accomplish this using OAuth ([spec](https://github.com/bluesky-social/proposals/tree/main/0004-oauth)). Most of the OAuth flows are going to be handled for us using the [@atproto/oauth-client-node](https://github.com/bluesky-social/atproto/tree/main/packages/oauth/oauth-client-node) library. This is the arrangement we're aiming toward:-When the user logs in, the OAuth client will create a new session with their repo server and give us read/write access along with basic user info.-Our login page just asks the user for their "handle," which is the domain name associated with their account. For [Bluesky](https://bsky.app) users, these tend to look like `alice.bsky.social`, but they can be any kind of domain (eg `alice.com`).-When they submit the form, we tell our OAuth client to initiate the authorization flow and then redirect the user to their server to complete the process.-This is the same kind of SSO flow that Google or GitHub uses. The user will be asked for their password, then asked to confirm the session with your application.-When that finishes, they'll be sent back to `/oauth/callback` on our Web app. The OAuth client stores the access tokens for the server, and then we attach their account's [DID](https://atproto.com/specs/did) to their cookie-session.-With that, we're in business! We now have a session with the user's `at://` repo server and can use that to access their data.-Why don't we learn something about our user? In [Bluesky](https://bsky.app), users publish a "profile" record which looks like this:-You can examine this record directly using [atproto-browser.vercel.app](https://atproto-browser.vercel.app). For instance, [this is the profile record for @bsky.app](https://atproto-browser.vercel.app/at?u=at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.actor.profile/self).-We're going to use the [Agent](https://github.com/bluesky-social/atproto/tree/main/packages/api) associated with the user's OAuth session to fetch this record.-We'll explain the collection name shortly. Record keys are strings with [some restrictions](https://atproto.com/specs/record-key#record-key-syntax) and a couple of common patterns. The `"self"` pattern is used when a collection is expected to only contain one record which describes the user.-We write records using a similar API. Since our goal is to write "status" records, let's look at how that will happen:-Repo collections are typed, meaning that they have a defined schema. The `app.bsky.actor.profile` type definition [can be found here](https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/actor/profile.json).-Anybody can create a new schema using the [Lexicon](https://atproto.com/specs/lexicon) language, which is very similar to [JSON-Schema](http://json-schema.org/). The schemas use [reverse-DNS IDs](https://atproto.com/specs/nsid) which indicate ownership, but for this demo app we're going to use `com.example` which is safe for non-production software.-> Schemas help other applications understand the data your app is creating. By publishing your schemas, you make it easier for other application authors to publish data in a format your app will recognize and handle.-Let's create our schema in the `/lexicons` folder of our codebase. You can [read more about how to define schemas here](https://atproto.com/guides/lexicon).-This will produce Typescript interfaces as well as runtime validation functions that we can use in our app. Here's what that generated code looks like:-Remember how we referred to our app as being like a Google, crawling around the repos to get their records? One advantage we have in the AT Protocol is that each repo publishes an event log of their updates.-Using a [Relay service](https://docs.bsky.app/docs/advanced-guides/federation-architecture#relay) we can listen to an aggregated firehose of these events across all users in the network. In our case what we're looking for are valid `com.example.status` records.-Applications write to the repo. The write events are then emitted on the firehose where they're caught by the apps and ingested into their databases.-Why sync from the event log like this? Because there are other apps in the network that will write the records we're interested in. By subscribing to the event log, we ensure that we catch all the data we're interested in — including data published by other apps!-Now that we have statuses populating our SQLite, we can produce a timeline of status updates by users. We also use a [DID](https://atproto.com/specs/did)-to-handle resolver so we can show a nice username with the statuses:-Since we're updating our users' repos locally, we can short-circuit that flow to our own database:-This is an important optimization to make, because it ensures that the user sees their own changes while using your app. When the event eventually arrives from the firehose, we just discard it since we already have it saved locally.-In this tutorial we've covered the key steps to building an atproto app. Data is published in its canonical form on users' `at://` repos and then aggregated into apps' databases to produce views of the network.-This is how every app in the Atmosphere works, including the [Bluesky social app](https://bsky.app).-If you want to practice what you've learned, here are some additional challenges you could try:-- Sync the profile records of all users so that you can show their display names instead of their handles.-- Create a different kind of schema, like a way to post links to websites and rate them 1 through 4 stars.-|[📦 ATProto monorepo](https://github.com/bluesky-social/atproto)|See the source code first-hand.|-|[💬 ATProto discussions board](https://github.com/bluesky-social/atproto/discussions)|Ask any questions you have!|
···
docs/app-banner.png
This is a binary file and will not be displayed.
docs/app-login.png
This is a binary file and will not be displayed.
docs/app-screenshot.png
This is a binary file and will not be displayed.
docs/app-status-history.png
This is a binary file and will not be displayed.
docs/app-status-options.png
This is a binary file and will not be displayed.
docs/diagram-event-stream.png
This is a binary file and will not be displayed.
docs/diagram-info-flow.png
This is a binary file and will not be displayed.
docs/diagram-oauth.png
This is a binary file and will not be displayed.
docs/diagram-optimistic-update.png
This is a binary file and will not be displayed.
docs/diagram-repo.png
This is a binary file and will not be displayed.