269 lines
9.1 KiB
Markdown
269 lines
9.1 KiB
Markdown
# 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
|