This guide walks you through setting up a complete development environment for building AT Protocol applications, from initial installation to running tests.
Prerequisites
Node.js
Install Node.js 18 or later. We recommend using nvm for managing Node.js versions. # Install nvm (macOS/Linux)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# Install Node.js 18
nvm install 18
nvm use 18
Package manager
Choose your preferred package manager: # npm comes with Node.js
npm --version
Project Setup
Creating a New Project
Initialize your project
mkdir my-atproto-app
cd my-atproto-app
npm init -y
Install dependencies
npm install @atproto/api
npm install --save-dev typescript @types/node
Configure TypeScript
Create a tsconfig.json file: {
"compilerOptions" : {
"target" : "ES2021" ,
"module" : "commonjs" ,
"lib" : [ "ES2021" ],
"outDir" : "./dist" ,
"rootDir" : "./src" ,
"strict" : true ,
"esModuleInterop" : true ,
"skipLibCheck" : true ,
"forceConsistentCasingInFileNames" : true ,
"resolveJsonModule" : true ,
"declaration" : true ,
"declarationMap" : true ,
"sourceMap" : true
},
"include" : [ "src/**/*" ],
"exclude" : [ "node_modules" , "dist" ]
}
Add scripts to package.json
{
"scripts" : {
"build" : "tsc" ,
"dev" : "tsc --watch" ,
"start" : "node dist/index.js" ,
"test" : "jest"
}
}
Directory Structure
Organize your project:
my-atproto-app/
├── src/
│ ├── index.ts # Entry point
│ ├── auth/
│ │ └── oauth.ts # OAuth configuration
│ ├── api/
│ │ └── client.ts # API client setup
│ └── utils/
│ └── richtext.ts # Rich text helpers
├── tests/
│ └── api.test.ts
├── .env # Environment variables (don't commit!)
├── .gitignore
├── package.json
└── tsconfig.json
Environment Configuration
Create .env File
# AT Protocol Configuration
ATPROTO_SERVICE = https://bsky.social
ATPROTO_IDENTIFIER = your.handle.bsky.social
ATPROTO_PASSWORD = your-app-password
# OAuth Configuration (for production)
OAUTH_CLIENT_ID = https://your-app.com/client-metadata.json
OAUTH_REDIRECT_URI = https://your-app.com/callback
# Development
NODE_ENV = development
PORT = 3000
Add .gitignore
# Dependencies
node_modules/
# Build output
dist/
*.js
*.js.map
*.d.ts
# Environment
.env
.env.local
.env.*.local
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
logs/
*.log
npm-debug.log*
# Testing
coverage/
.nyc_output/
Load Environment Variables
Install and configure dotenv:
import dotenv from 'dotenv'
// Load environment variables
dotenv . config ()
// Validate required variables
if ( ! process . env . ATPROTO_SERVICE ) {
throw new Error ( 'ATPROTO_SERVICE is required' )
}
Basic Application Setup
Create API Client
import { Agent , CredentialSession } from '@atproto/api'
export async function createAgent () : Promise < Agent > {
const serviceUrl = process . env . ATPROTO_SERVICE !
const identifier = process . env . ATPROTO_IDENTIFIER !
const password = process . env . ATPROTO_PASSWORD !
const session = new CredentialSession ( new URL ( serviceUrl ))
await session . login ({ identifier , password })
return new Agent ( session )
}
Main Application Entry
import dotenv from 'dotenv'
import { createAgent } from './api/client'
dotenv . config ()
async function main () {
try {
const agent = await createAgent ()
console . log ( 'Connected as:' , agent . accountDid )
// Your application logic here
const profile = await agent . getProfile ({ actor: agent . accountDid })
console . log ( 'Profile:' , profile . data . displayName )
} catch ( error ) {
console . error ( 'Error:' , error )
process . exit ( 1 )
}
}
main ()
Testing Setup
Install Testing Dependencies
npm install --save-dev jest @types/jest ts-jest
module . exports = {
preset: 'ts-jest' ,
testEnvironment: 'node' ,
roots: [ '<rootDir>/tests' ],
testMatch: [ '**/*.test.ts' ],
collectCoverageFrom: [
'src/**/*.ts' ,
'!src/**/*.d.ts'
],
moduleNameMapper: {
'^@/(.*)$' : '<rootDir>/src/$1'
}
}
Write Tests
import { Agent } from '@atproto/api'
import { createAgent } from '../src/api/client'
describe ( 'API Client' , () => {
let agent : Agent
beforeAll ( async () => {
agent = await createAgent ()
})
test ( 'should authenticate successfully' , () => {
expect ( agent . did ). toBeDefined ()
expect ( agent . did ). toMatch ( / ^ did:/ )
})
test ( 'should fetch user profile' , async () => {
const { data } = await agent . getProfile ({ actor: agent . accountDid })
expect ( data ). toBeDefined ()
expect ( data . did ). toBe ( agent . accountDid )
})
test ( 'should create and delete a post' , async () => {
// Create post
const { uri } = await agent . post ({
text: 'Test post from automated tests' ,
createdAt: new Date (). toISOString ()
})
expect ( uri ). toBeDefined ()
// Delete post
await agent . deletePost ( uri )
// Verify deletion
await expect (
agent . getPost ({ uri })
). rejects . toThrow ()
})
})
Development Workflow
Running in Development
# Build and run
npm run build
npm start
# Watch mode
npm run dev
Running Tests
# Run all tests
npm test
# Run tests in watch mode
npm test -- --watch
# Run tests with coverage
npm test -- --coverage
Web Application Setup
For building web applications:
Next.js Setup
npx create-next-app@latest my-atproto-app --typescript
cd my-atproto-app
npm install @atproto/api @atproto/oauth-client-browser
Vite Setup
npm create vite@latest my-atproto-app -- --template react-ts
cd my-atproto-app
npm install
npm install @atproto/api @atproto/oauth-client-browser
Debugging
VS Code Configuration
Create .vscode/launch.json:
{
"version" : "0.2.0" ,
"configurations" : [
{
"type" : "node" ,
"request" : "launch" ,
"name" : "Debug Application" ,
"skipFiles" : [ "<node_internals>/**" ],
"program" : "${workspaceFolder}/src/index.ts" ,
"preLaunchTask" : "tsc: build - tsconfig.json" ,
"outFiles" : [ "${workspaceFolder}/dist/**/*.js" ],
"envFile" : "${workspaceFolder}/.env"
},
{
"type" : "node" ,
"request" : "launch" ,
"name" : "Debug Tests" ,
"program" : "${workspaceFolder}/node_modules/.bin/jest" ,
"args" : [ "--runInBand" , "--no-cache" ],
"console" : "integratedTerminal" ,
"internalConsoleOptions" : "neverOpen" ,
"envFile" : "${workspaceFolder}/.env"
}
]
}
Enable Debug Logging
// Enable verbose logging
if ( process . env . NODE_ENV === 'development' ) {
process . env . DEBUG = '@atproto/*'
}
ESLint Setup
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
npx eslint --init
module . exports = {
parser: '@typescript-eslint/parser' ,
extends: [
'eslint:recommended' ,
'plugin:@typescript-eslint/recommended'
],
parserOptions: {
ecmaVersion: 2021 ,
sourceType: 'module'
},
rules: {
'@typescript-eslint/no-unused-vars' : 'error' ,
'@typescript-eslint/no-explicit-any' : 'warn'
}
}
Prettier Setup
npm install --save-dev prettier
{
"semi" : false ,
"singleQuote" : true ,
"tabWidth" : 2 ,
"trailingComma" : "es5"
}
Add scripts to package.json:
{
"scripts" : {
"lint" : "eslint src/**/*.ts" ,
"format" : "prettier --write src/**/*.ts"
}
}
Common Development Tasks
Creating App Passwords
For development, create an app password at:
Never commit app passwords to version control. Always use environment variables.
Testing with Local PDS
Run a local PDS for development:
# Clone the atproto repository
git clone https://github.com/bluesky-social/atproto.git
cd atproto
# Install dependencies
pnpm install
# Run dev environment
make run-dev-env
Update your .env:
ATPROTO_SERVICE = http://localhost:2583
Best Practices
Use TypeScript
TypeScript provides type safety and better IDE support.
Version control your code
Always use Git and commit regularly. git init
git add .
git commit -m "Initial commit"
Never commit secrets
Use .gitignore to exclude .env files and other sensitive data.
Write tests
Test your code to catch bugs early and ensure reliability.
Use environment-specific configs
Create separate configs for development, staging, and production. .env.development
.env.production
Troubleshooting
Common Issues
Ensure dependencies are installed: rm -rf node_modules package-lock.json
npm install
TypeScript compilation errors
Check your tsconfig.json and ensure all dependencies are properly typed: npm install --save-dev @types/node
Verify your credentials in .env:
Check that your handle is correct
Ensure you’re using an app password, not your main password
Verify the service URL is correct
For browser applications, ensure you’re using OAuth, not credential-based auth.
Next Steps
Using the API Learn how to use the Agent API
OAuth Authentication Set up OAuth for production
Rich Text Work with mentions and links
Quickstart Build your first app quickly