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.

Development Setup

Prerequisites

  • Node.js 18+
  • Bun package manager
  • Convex account
  • Email provider (Resend or AWS SES)

Local Development

  1. Install dependencies:
    bun install
    
  2. Start the Convex backend (keeps running):
    bun run dev:api
    
  3. 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:

VariableWherePurpose
BETTER_AUTH_SECRETConvexSession signing secret
SITE_URLConvexPublic site URL for redirects
EMAIL_PROVIDERConvexses (default) or resend
UNSUBSCRIBE_SECRETConvexHMAC secret for unsubscribe tokens
NUXT_PUBLIC_CONVEX_URLNuxt .envConvex deployment URL
NUXT_PUBLIC_CONVEX_SITE_URLNuxt .envConvex site URL (for auth)
NUXT_PUBLIC_SITE_URLNuxt .envPublic 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

TechnologyPurpose
Nuxt 4Vue 3 full-stack framework
ConvexReal-time serverless backend
BetterAuthAuthentication with organization support
Tailwind CSS 4Utility-first styling
@owlat/email-rendererCustom HTML email rendering
LucideIcon library

Patterns and Conventions

File Naming

  • Vue components: PascalCase.vue
  • Composables: useCamelCase.ts
  • Convex functions: camelCase.ts
  • Pages: kebab-case.vue or [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 Error with 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');
}