Files
shiftcraft/README.md
2026-04-17 23:26:01 +00:00

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