Testing implementation for private data in ATProto with ATPKeyserver and ATCute tools
TypeScript 98.9%
JavaScript 0.5%
Dockerfile 0.4%
CSS 0.3%
Other 0.1%
76 1 0

Clone this repository

https://tangled.org/djara.dev/watproto
git@tangled.org:djara.dev/watproto

For self-hosted knots, clone URLs may differ based on your setup.

README.md

ATProto OAuth Application#

A full-stack ATProto microblogging application with OAuth authentication. Built with Elysia (backend) and React Router v7 (frontend) in a Bun workspace monorepo.

Project Structure#

watproto/
├── packages/
│   ├── server/              # XRPC API server
│   │   ├── src/             # Server source code
│   │   ├── data/            # SQLite database
│   │   └── CLAUDE.md        # Server documentation
│   ├── client/              # React Router v7 frontend (SSR)
│   │   ├── app/             # React Router app
│   │   ├── data/            # OAuth state database
│   │   └── CLAUDE.md        # Client documentation
│   └── lexicon/             # ATProto lexicon types
│       ├── types/           # Generated types
│       └── index.ts         # Type exports
├── lexicons/                # Lexicon schemas
├── package.json             # Workspace configuration
└── CLAUDE.md                # Project-wide documentation

Tech Stack#

Backend (Server):

  • Bun runtime
  • Elysia web framework
  • XRPC protocol (@atcute/xrpc-server)
  • SQLite + Kysely ORM

Frontend (Client):

  • React Router v7 (SSR)
  • ATProto OAuth (@atproto/oauth-client-node)
  • XRPC client (@atcute/client)
  • React 19
  • Tailwind CSS v4 + DaisyUI
  • Vite

Shared:

  • @watproto/lexicon - Lexicon type definitions and utils generated from lexicon schemas

Quick Start#

Prerequisites#

Installation#

  1. Clone the repository:

    git clone <repository-url>
    cd watproto
    
  2. Install dependencies:

    bun install
    
  3. Create files for environment variables:

    Server (packages/server/.env.local):

    PORT=3000
    DATABASE_URL=./data/dev.db
    SERVICE_DID=did:web:localhost:3000
    

    Client (packages/client/.env.local):

    CLIENT_URL=http://your.client.host
    API_URL=http://localhost:3000
    DEFAULT_PDS_URL=https://your.pds.host
    DB_PATH=./data/state.db
    PORT=5173
    AUTH_SECRET=your-random-secret-key-here
    KEYSERVER_DID=did:web:your.keyserver.host
    

    Generate AUTH_SECRET with: openssl rand -base64 32

    To use OAuth in localhost do not define a CLIENT_URL here

    More on environment variables below

Development#

Run both server and client in separate terminals:

Terminal 1 - Server:

cd packages/server
bun run dev

Terminal 2 - Client:

cd packages/client
bun run dev

Then you can open:

  • Frontend: http://127.0.0.1:5173
  • XRPC Server: http://localhost:3000

Alternative: Run from Root#

You can also use workspace scripts from the root:

# Start server only
bun run dev

# Start client only
bun run dev:client

# Build both packages
bun run build

# Type check all packages
bun run typecheck

This has the advantage of being slightly easier to run, but logs will be slightly more cluttered.

Project Commands#

Root Commands#

bun install                 # Install all workspace dependencies
bun run dev                 # Start both server and client
bun run dev:server          # Start server only
bun run dev:client          # Start client only
bun run build               # Build client for production
bun run typecheck           # Type check all packages
bun run lex                 # Generate TypeScript types from lexicon schemas

Server Commands (packages/server)#

bun run dev                 # Start development server (port 3000)
bun run start               # Start production server
bun run debug               # Start with debugger
bun run db:codegen          # Regenerate database types
bun run db:migrate          # Run database migrations
bun run typecheck           # Type check

Client Commands (packages/client)#

bun run dev                 # Start development server (port 5173)
bun run build               # Build for production
bun run start               # Start production server
bun run lint                # Run ESLint
bun run typecheck           # Type check

Features#

Current#

  • ✅ ATProto OAuth authentication (client-side SSR)
  • ✅ Session management with SQLite
  • ✅ XRPC server and client communication
  • ✅ Post creation and storage (public and private)
  • ✅ Tag system for posts
  • ✅ User authentication UI (login flow)
  • ✅ Basic UI components (Header, PostFeed, UserMenu)
  • ✅ Lexicon type generation
  • ✅ React Router v7 frontend with SSR
  • ✅ Tailwind CSS + DaisyUI styling
  • ✅ TypeScript throughout

Planned#

  • 🚧 ATProto feed aggregation and timeline
  • 🚧 User profile pages
  • 🚧 Media upload (images/videos)
  • 🚧 Real-time updates via WebSockets
  • 🚧 Background job processing
  • 🚧 Full-text search
  • 🚧 Multi-account support

Architecture#

Backend (packages/server)#

  • Framework: Elysia (fast, type-safe web framework)
  • Protocol: XRPC (ATProto RPC protocol)
  • Database: SQLite with Kysely query builder
  • Features: Post storage, account management, ID resolution

Frontend (packages/client)#

  • Framework: React Router v7 (with SSR)
  • OAuth: ATProto OAuth Client (server-side)
  • Database: SQLite for OAuth state
  • Routing: File-based routes in app/routes/
  • Styling: Tailwind CSS v4 + DaisyUI
  • Build: Vite

Shared (packages/lexicon)#

  • Purpose: ATProto lexicon type definitions
  • Generator: @atcute/lex-cli
  • Usage: Shared types across server and client

Communication#

  • Client ↔ Server: XRPC protocol over HTTP
  • Authentication: OAuth managed by client SSR
  • Session: SQLite database with httpOnly cookies

Deployment#

Server#

Deploy to Node/Bun compatible hosting:

Client#

Deploy to static hosting:

Environment Variables#

Configure production environment variables in your hosting provider:

Server:

  • PORT - Server port (default: 3000)
  • DATABASE_URL - SQLite database path
  • SERVICE_DID - DID that points to this service (required)

Client:

  • API_URL - XRPC server URL (required)
  • DEFAULT_PDS_URL - Default PDS URL for OAuth (required)
  • AUTH_SECRET - Session encryption secret (required)
  • KEYSERVER_DID - DID that poins to a Keyserver for encryption / decryption (required)
  • Optional: CLIENT_URL, DB_PATH, PORT

Important: Client requires SSR-capable hosting with writable filesystem for OAuth database

Documentation#

Detailed documentation is available in CLAUDE.md files:

  • Root: CLAUDE.md - Monorepo overview and workflow
  • Server: packages/server/CLAUDE.md - Backend architecture and patterns
  • Client: packages/client/CLAUDE.md - Frontend architecture and patterns

Development Workflow#

Adding a Backend Feature#

  1. Navigate to packages/server
  2. Create/modify XRPC procedures in src/
  3. Add database migrations if needed
  4. Test with bun run dev
  5. See packages/server/CLAUDE.md for patterns

Adding a Frontend Feature#

  1. Navigate to packages/client
  2. Create route file in app/routes/
  3. Create components as needed
  4. Style with Tailwind CSS + DaisyUI
  5. Test with bun run dev
  6. See packages/client/CLAUDE.md for patterns

Working with Lexicons#

# Add or modify lexicon schemas in lexicons/ directory
# Then regenerate TypeScript types:
bun run lex

# Types are generated in packages/lexicon/types/
# Available via: import { Watproto } from '@watproto/lexicon'

Database Migrations#

cd packages/server

# Create migration file manually in src/db/migrations/
# Then regenerate types:
bun run db:codegen

Troubleshooting#

Port already in use:

  • Change PORT in packages/server/.env.local
  • Client will auto-increment port if 5173 is taken

CORS errors:

  • Ensure server CORS allows client origin
  • Check API_URL in client environment variables matches server URL

Type errors:

  • Run bun install at root to sync workspace
  • Run bun run typecheck to find issues

Database issues:

  • Delete packages/server/data/*.db* files
  • Restart server to recreate database

License#

MIT

Contributing#

See CLAUDE.md for development guidelines and architecture patterns.