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#
- Bun v1.0+
Installation#
-
Clone the repository:
git clone <repository-url> cd watproto -
Install dependencies:
bun install -
Create files for environment variables:
Server (
packages/server/.env.local):PORT=3000 DATABASE_URL=./data/dev.db SERVICE_DID=did:web:localhost:3000Client (
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.hostGenerate
AUTH_SECRETwith:openssl rand -base64 32To use OAuth in
localhostdo not define aCLIENT_URLhereMore 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 pathSERVICE_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#
- Navigate to
packages/server - Create/modify XRPC procedures in
src/ - Add database migrations if needed
- Test with
bun run dev - See
packages/server/CLAUDE.mdfor patterns
Adding a Frontend Feature#
- Navigate to
packages/client - Create route file in
app/routes/ - Create components as needed
- Style with Tailwind CSS + DaisyUI
- Test with
bun run dev - See
packages/client/CLAUDE.mdfor 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
PORTinpackages/server/.env.local - Client will auto-increment port if 5173 is taken
CORS errors:
- Ensure server CORS allows client origin
- Check
API_URLin client environment variables matches server URL
Type errors:
- Run
bun installat root to sync workspace - Run
bun run typecheckto 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.