# Nuxt PocketBase Starter

A full-stack starter template for rapidly building and shipping apps with **Nuxt 4**, **PocketBase**, and **Capacitor** (Android/iOS).

## What's Included

- **Frontend** — Nuxt 4 SPA with Vue 3, Nuxt UI, Tailwind CSS v4, Pinia state management
- **Backend** — PocketBase (SQLite, REST API, Realtime WebSocket), Dockerized with migrations and hooks
- **Authentication** — Email OTP, Google OAuth, Apple OAuth (auto-creates users on first login)
- **Push Notifications** — FCM via Deno sidecar service, works on Android and web
- **Mobile** — Capacitor for Android and iOS with deep linking support
- **i18n** — English and German, frontend and backend (localized email templates)
- **Dev Tooling** — ESLint, TypeScript, Vitest, Playwright, Mailpit (dev email viewer)

## Prerequisites

- [Node.js](https://nodejs.org/) 22+
- [pnpm](https://pnpm.io/) 10+ (`corepack enable`)
- [Docker](https://www.docker.com/) and Docker Compose
- [Deno](https://deno.com/) (for PocketBase sidecar)

## Quick Start

### 1. Clone and install

```bash
git clone <repository-url>
cd nuxt-pocketbase-starter
pnpm install
```

### 2. Configure environment

```bash
cp .env.example .env
```

Edit `.env` to set your values. The defaults work for local development — you only need to configure OAuth credentials and Firebase if you want those features.

### 3. Start the backend

```bash
pnpm pocketbase:start
```

This starts three Docker containers:

| Service | URL | Purpose |
|---------|-----|---------|
| PocketBase | http://localhost:8090 | API + Admin UI (`/_/`) |
| Sidecar | http://localhost:8091 | FCM push notification delivery |
| Mailpit | http://localhost:8025 | Dev email viewer (catches all outgoing mail) |

### 4. Start the frontend

In a second terminal:

```bash
pnpm dev
```

The app is available at http://localhost:3000.

## Available Commands

| Command | Description |
|---------|-------------|
| `pnpm dev` | Start Nuxt dev server |
| `pnpm build` | Production build |
| `pnpm preview` | Preview production build locally |
| `pnpm lint` | Run ESLint |
| `pnpm typecheck` | Run TypeScript type checking |
| `pnpm test` | Run tests (Vitest) |
| `pnpm pocketbase:start` | Start PocketBase + sidecar + Mailpit (Docker) |
| `pnpm pocketbase:stop` | Stop all backend containers |
| `pnpm pocketbase:types` | Regenerate TypeScript types from PocketBase schema |
| `pnpm assets` | Generate mobile app icons and splash screens |

## Environment Variables

See `.env.example` for all variables. Key ones:

| Variable | Description |
|----------|-------------|
| `APP_NAME` | Application display name |
| `APP_ID` | App identifier (e.g., `com.example.myapp`) |
| `NUXT_PUBLIC_APP_URL` | Frontend URL (default: `http://localhost:3000`) |
| `NUXT_PUBLIC_POCKETBASE_URL` | PocketBase API URL (default: `http://127.0.0.1:8090`) |
| `SUPERUSER_EMAIL` / `SUPERUSER_PW` | PocketBase admin credentials |
| `AUTH_GOOGLE_CLIENT_ID` / `SECRET` | Google OAuth credentials |
| `AUTH_APPLE_CLIENT_ID` / `SECRET` | Apple OAuth credentials |
| `SIDECAR_SECRET` | Shared secret between PocketBase and sidecar |
| `GOOGLE_CREDENTIALS_JSON` | Firebase service account JSON (for FCM push) |

## Project Structure

```
├── app/                        # Nuxt frontend
│   ├── pages/                  # File-based routes
│   ├── stores/                 # Pinia stores (user, counter, notifications, avatar)
│   ├── components/             # Vue components (App/, Counter/, Profile/)
│   ├── composables/            # usePocketBase()
│   ├── plugins/                # PocketBase client init
│   ├── middleware/             # Auth guard, user fetch
│   ├── i18n/locales/           # Frontend translations (en, de)
│   └── types/                  # TypeScript types
├── pocketbase/
│   ├── pb_migrations/          # Database migrations (run on startup)
│   ├── pb_hooks/               # Server-side hooks and custom endpoints
│   │   ├── templates/          # Localized HTML email templates
│   │   └── locales/            # Backend translations
│   ├── sidecar/                # Deno service for FCM push delivery
│   ├── docker-compose.yml
│   └── Dockerfile
├── android/                    # Capacitor Android project
├── ios/                        # Capacitor iOS project
├── Dockerfile                  # Nuxt production image
└── capacitor.config.ts         # Mobile app configuration
```

## Customizing the Template

### Rename the app

1. Update `APP_NAME` and `APP_ID` in `.env`
2. Update `package.json` name and version
3. For mobile: regenerate assets with `pnpm assets`

### Add a new backend collection

1. Create a migration file in `pocketbase/pb_migrations/` (next number in sequence, e.g., `5_my_table.js`)
2. Restart PocketBase: `pnpm pocketbase:stop && pnpm pocketbase:start`
3. Regenerate types: `pnpm pocketbase:types`
4. Create a Pinia store in `app/stores/` using the generated types

### Add a new language

1. Add translation file to `app/i18n/locales/` (e.g., `fr.json`)
2. Register the locale in `nuxt.config.ts` under `i18n.locales`
3. Add backend translations in `pocketbase/pb_hooks/locales/` (e.g., `fr.json`)
4. Add email templates in `pocketbase/pb_hooks/templates/fr/`
5. Update `LanguageCode` type in `app/types/i18n.types.ts`

### Add a custom API endpoint

Create a hook file in `pocketbase/pb_hooks/` (e.g., `myEndpoint.pb.js`) using `routerAdd()`. See `resetCounter.pb.js` for an example. Hooks use PocketBase's JSVM — not Node.js.

## Mobile Development

### Android

```bash
pnpm build
npx cap sync android
npx cap open android     # Opens Android Studio
```

For push notifications, place your `google-services.json` at `creds/fcm-google-services.json` — it gets copied to the Android project automatically.

### iOS

```bash
pnpm build
npx cap sync ios
npx cap open ios         # Opens Xcode
```

Requires a Mac with Xcode and an Apple Developer account.

## CI/CD

Gitea Actions workflows in `.gitea/workflows/` trigger on pushes to the `release` branch (and manual `workflow_dispatch`):

| Workflow | What it does |
|----------|--------------|
| `frontend.yaml` | Builds the Nuxt Docker image and pushes it to the Gitea Container Registry |
| `backend.yaml` | Builds the PocketBase Docker image and pushes it to the Gitea Container Registry |
| `android.yaml` | Builds the Android app via Fastlane and releases to Google Play |
| `ios.yaml` | Builds the iOS app via Fastlane and uploads to App Store Connect |

### Required Gitea configuration

**Repository variables** (Settings → Actions → Variables):

| Variable | Example |
|----------|---------|
| `NUXT_PUBLIC_APP_URL` | `https://your-domain.com` |
| `NUXT_PUBLIC_POCKETBASE_URL` | `https://pb.your-domain.com` |

**Repository secrets** (Settings → Actions → Secrets):

| Secret | Purpose |
|--------|---------|
| `REGISTRY_TOKEN` | Gitea personal access token with `package:write` scope |
| `PLAY_STORE_JSON_KEY_DATA` | Google Play service account JSON (Android release) |
| `APP_STORE_CONNECT_API_KEY_ID` | App Store Connect API key ID (iOS release) |
| `APP_STORE_CONNECT_API_ISSUER_ID` | App Store Connect issuer ID |
| `APP_STORE_CONNECT_API_KEY_CONTENT` | Base64-encoded App Store Connect `.p8` key |
| `IOS_CERTIFICATE_BASE64` | Base64-encoded `.p12` signing certificate |
| `IOS_CERTIFICATE_PASSWORD` | Password for the signing certificate |
| `IOS_PROVISIONING_PROFILE_BASE64` | Base64-encoded provisioning profile |

### Image tagging

Images are pushed to the Gitea Container Registry with two tags:
- `<registry>/<owner>/<repo>-frontend:<version>` (from `package.json`)
- `<registry>/<owner>/<repo>-frontend:latest`
- `<registry>/<owner>/<repo>-backend:<version>` (from `package.json`)
- `<registry>/<owner>/<repo>-backend:latest`

## Deployment

Both the frontend and backend are published as Docker images via CI. Deploy them together with Docker Compose on any server.

### Production Docker Compose

Create a `docker-compose.yml` on your server:

```yaml
services:
  frontend:
    image: git.your-domain.com/owner/repo-frontend:latest  # Nuxt image from CI
    ports:
      - 3000:3000
    restart: unless-stopped

  pocketbase:
    image: git.your-domain.com/owner/repo-backend:latest  # PocketBase image from CI
    ports:
      - 8090:8090
      - 8091:8091
    volumes:
      - pb_data:/pb/pb_data
    env_file:
      - .env
    restart: unless-stopped

volumes:
  pb_data:
```

Place a production `.env` next to it with your secrets (`SUPERUSER_EMAIL`, `SUPERUSER_PW`, `SIDECAR_SECRET`, OAuth credentials, `GOOGLE_CREDENTIALS_JSON`, etc.).

```bash
docker compose pull && docker compose up -d
```

Put a reverse proxy (Caddy, Traefik, nginx) in front to handle TLS and route traffic to port 3000 (frontend) and 8090 (PocketBase API).

### Building images manually

If you're not using CI, you can build locally:

```bash
# Frontend
docker build \
  --build-arg NUXT_PUBLIC_APP_URL=https://your-domain.com \
  --build-arg NUXT_PUBLIC_POCKETBASE_URL=https://pb.your-domain.com \
  -t nuxt-app .

# Backend
cd pocketbase
docker build --target prod -t pocketbase-app .
```

## License

MIT
