# 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 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: - `//-frontend:` (from `package.json`) - `//-frontend:latest` - `//-backend:` (from `package.json`) - `//-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