Developer Guide
This guide covers the technical architecture and development patterns used in Owlat.
This guide covers the technical architecture and development patterns used in Owlat.
Quick Links
- Architecture Overview - System design and data flow
- Convex Backend - Database schema and API functions
- Authentication - BetterAuth integration
- Email System - Editor, templates, and sending
- Environment Variables - Payments, email, waitlist config
- Component Library - Reusable UI components
- Architectural Decisions - Why we made key design choices
- Contributing - How to contribute to Owlat
Development Setup
Prerequisites
- Node.js 18+
- Bun package manager
- Convex account
- Email provider (Resend or AWS SES)
Local Development
- Install dependencies:
bun install - Start the Convex backend (keeps running):
bun run dev:api - Start the Nuxt frontend:
bun run dev
Environment Variables
Both the Nuxt frontend and the Convex backend use environment variables for configuration. Feature flags follow a dual-variable pattern: one Convex env var for the backend and one NUXT_PUBLIC_* var for the frontend.
See the Environment Variables page for a complete reference with setup instructions for payments, email providers, and the waitlist.
Quick reference — essential variables:
| Variable | Where | Purpose |
|---|---|---|
BETTER_AUTH_SECRET | Convex | Session signing secret |
SITE_URL | Convex | Public site URL for redirects |
EMAIL_PROVIDER | Convex | ses (default) or resend |
UNSUBSCRIBE_SECRET | Convex | HMAC secret for unsubscribe tokens |
NUXT_PUBLIC_CONVEX_URL | Nuxt .env | Convex deployment URL |
NUXT_PUBLIC_CONVEX_SITE_URL | Nuxt .env | Convex site URL (for auth) |
NUXT_PUBLIC_SITE_URL | Nuxt .env | Public site URL |
Code Quality
bun run typecheck # TypeScript checking
bun run lint # Oxlint
bun run ox:fmt # Format with Oxfmt
Project Structure
owlat/
├── apps/
│ ├── web/ # Nuxt 4 frontend
│ │ └── app/
│ │ ├── components/ # Vue components
│ │ │ └── ui/ # App-specific UI components
│ │ ├── composables/ # Vue composables
│ │ ├── layouts/ # Page layouts
│ │ ├── pages/ # File-based routing
│ │ └── plugins/ # Nuxt plugins
│ ├── api/ # Convex backend
│ │ └── convex/
│ │ ├── _generated/ # Auto-generated (don't edit)
│ │ ├── lib/ # Shared utilities
│ │ ├── schema.ts # Database schema
│ │ └── *.ts # API functions
│ └── docs/ # VitePress documentation
└── packages/
├── email-builder/ # Vue email editor component
├── email-renderer/ # JSON blocks → HTML renderer
├── email-previewer/ # Email preview component
├── shared/ # Shared types and utilities
├── ui/ # Nuxt layer: shared UI components and composables
├── sdk-js/ # Official TypeScript SDK
└── sdk-java/ # Official Java SDK
Key Technologies
| Technology | Purpose |
|---|---|
| Nuxt 4 | Vue 3 full-stack framework |
| Convex | Real-time serverless backend |
| BetterAuth | Authentication with organization support |
| Tailwind CSS 4 | Utility-first styling |
| @owlat/email-renderer | Custom HTML email rendering |
| Lucide | Icon library |
Patterns and Conventions
File Naming
- Vue components:
PascalCase.vue - Composables:
useCamelCase.ts - Convex functions:
camelCase.ts - Pages:
kebab-case.vueor[param].vue
Imports
Use the ~/ alias for imports within the Nuxt app:
import { useAuth } from '~/composables/useAuth';
TypeScript
All code is TypeScript. Use explicit types for function parameters and return values:
function formatDate(timestamp: number): string {
return new Date(timestamp).toLocaleDateString();
}
Error Handling
- Frontend: Use
useToast()for user feedback - Backend: Throw
Errorwith descriptive messages - API: Return structured error responses
// Frontend
const { showToast } = useToast();
try {
await mutation();
showToast('Saved successfully');
} catch (e) {
showToast('Failed to save', 'error');
}
// Backend (Convex)
if (!organizationId) {
throw new Error('Organization not found');
}