Full-stack reference: NestJS API + Vite/React web. External auth via Firebase, two persistence adapters (SQLite + Firestore) in the same app, an LLM-driven code-review feature backed by the GitHub API.
sample-code-review is the "non-trivial app" reference. While sample-server and sample-server-auth are focused single-concern demos, this one exercises Rockets under real-world conditions:
- Two-adapter auth chain (Firebase ID token first, server-to-server API key second).
- Two persistence backends in one app: SQLite for OAuth tokens / user metadata, Firestore for code-review reports.
- A non-CRUD
defineModuleResourcefeature (analysisFeature) that calls OpenAI and writes reports through the dynamic-repository contract. - A separate React frontend that authenticates with Firebase client SDK and forwards the ID token to the API.
The layout intentionally mirrors rockets-starter (apps/api + apps/web), but every @bitwild/* import resolves to the local packages/* via workspace:^ — so this app is the live integration test for in-development SDK changes.
rockets/ (SDK monorepo, source of truth)
├── packages/rockets-{core,server,…}
└── examples/sample-code-review/
├── apps/
│ ├── api/ NestJS + Rockets :3001
│ └── web/ Vite + React :3000
└── packages/typescript-config (shared tsconfig)
Web (Firebase client SDK) API (@bitwild/rockets)
─────────────────────────── ───────────────────────────
1. email/password login → (no participation)
2. user.getIdToken() → Authorization: Bearer <Firebase JWT>
→ AuthServerGuard (global)
→ FirebaseAuthAdapter.authenticate()
→ request.user = AuthorizedUser
→ GET /me, /github/*, /analysis/*
Firebase is not the application backend — it only issues the ID token. The Rockets server validates that token on every request.
| Layer | Responsibility |
|---|---|
| Firebase (client) | Web login; user.getIdToken() |
@bitwild/rockets-adapter-firebase |
Verifies ID token via Firebase Admin SDK |
@bitwild/rockets |
Global AuthServerGuard + MeController + protected routes |
| Your controllers | @AuthUser(), @Ctx() — user already authenticated |
A second adapter, defineApiKeyAuth(), runs after Firebase in the chain so server-to-server callers can authenticate with X-Api-Key.
- Node 18+, Yarn 4.
- A Firebase project for real auth (or
FIREBASE_USE_FAKE=truefor in-process verification). - A GitHub OAuth App if you want the GitHub connect flow to work.
- An OpenAI API key if you want real code-review output (otherwise the analyzer falls back to a stub).
yarn install
yarn buildThe build step compiles every local @bitwild/* package — required before the example runs.
cp examples/sample-code-review/apps/api/.env.example examples/sample-code-review/apps/api/.env
cp examples/sample-code-review/apps/web/.env.example examples/sample-code-review/apps/web/.envFor local-only dev with no external services, set in apps/api/.env:
FIREBASE_USE_FAKE=trueE2E tests inject an in-memory Firestore backend via a Jest stub (test/stubs/code-review-reports.persistence.stub.ts), not an env flag.
For real Firebase, drop the service-account JSON at apps/api/secrets/firebase-service-account.json and set FIREBASE_PROJECT_ID to match the web client's VITE_FIREBASE_PROJECT_ID.
yarn workspace sample-code-review dev
# web: http://localhost:3000
# api: http://localhost:3001
# swagger: http://localhost:3001/apiyarn dev starts both apps in parallel via concurrently. Use yarn dev:api / yarn dev:web to run them separately.
yarn workspace sample-code-review test:e2e
# (runs API e2e with fakes enabled by default)- Sign in with Firebase on the web (
/login). - Connect GitHub (
/github/connect→ callbackhttp://localhost:3000/auth/github/callback). The callback URL must matchGITHUB_OAUTH_CALLBACK_URLin the API.env. - Pick a repo, click "Run code review". The API calls the GitHub REST API for the file tree, slices it into prompts, calls OpenAI (
gpt-4o-miniby default), and writes the report to Firestore. - Open the report. The web pulls it through
GET /analysis/reports/:id.
Useful for debugging without the web:
TOKEN=$(firebase login:ci) # or get a real ID token from your client
curl http://localhost:3001/me -H "Authorization: Bearer $TOKEN"FirebaseAuthAdapter validates the token; MeController returns the user.
The second adapter in the chain (defineApiKeyAuth()) reads X-Api-Key. Useful for CI / cron jobs that don't have a Firebase session.
curl http://localhost:3001/analysis/reports \
-H "X-Api-Key: $INTERNAL_API_KEY"The adapter resolves the calling identity from a static config (see apps/api/src/auth-api-key/api-key.adapter.ts).
GET /analysis/reports?github=org/repo
GET /analysis/reports?q=text # search fullName and summary
GET /analysis/reports?status=completedFilters live in the analysisFeature controller, not the framework.
Production requires Firebase Admin initialised by the app (defineFirebaseAuth or ensureFirebaseAdminApp). E2E tests swap in InMemoryFirestoreBackend through an explicit test stub — see apps/api/test/stubs/.
DATABASE_PATH=./data/app.sqliteDefault is :memory: with dropSchema: true. Set DATABASE_PATH to keep state across restarts.
examples/sample-code-review
├── apps/
│ ├── api/
│ │ └── src/
│ │ ├── app.module.ts Single composition root
│ │ ├── analysis/ defineModuleResource (Firestore, OpenAI)
│ │ ├── auth-firebase/ defineFirebaseAuth wiring
│ │ ├── auth-api-key/ Second AuthBootstrap in the chain
│ │ ├── github/ GitHub OAuth + repo browse
│ │ ├── repository/ defineTypeOrmRepository + defineFirestoreRepository
│ │ ├── entities/ Shared TypeORM entities (UserMetadata)
│ │ └── main.ts Bootstrap (helmet, validation, swagger)
│ └── web/
│ └── src/
│ ├── App.tsx Routes
│ ├── auth/ Firebase client SDK wrapper
│ ├── pages/ Login / dashboard / report views
│ ├── components/
│ └── lib/ API client (forwards ID token)
└── packages/typescript-config/ Shared tsconfig
RocketsModule.forRoot({
auth: [
defineFirebaseAuth({
forRootAsync: { useFactory: resolveFirebaseAuthModuleOptions },
}),
defineApiKeyAuth(),
],
resources: [
defineModuleResource({ entities: [UserEntity] }),
apiKeyAuthResource,
// ...
],
})Order is preserved end-to-end — Firebase tries first; API key is the fallback.
| Data | Backend | Configuration |
|---|---|---|
GitHub OAuth tokens, userMetadata |
SQLite (TypeORM) via root repository: |
DATABASE_PATH or :memory: |
| Code-review reports | Firestore via @bitwild/rockets-repository-firestore |
FIREBASE_FIRESTORE_REPORTS_COLLECTION (default code_review_reports) |
CodeReviewReportEntity uses repository: codeReviewReportsRepository from defineFirestoreRepository() with collection on the entity row — same bootstrap pattern as defineTypeOrmRepository. Services use @InjectDynamicRepository + RepositoryInterface; no Firestore types leak out of the analysis folder.
| Var | Purpose |
|---|---|
PORT |
API HTTP port (default 3001). |
DATABASE_PATH |
SQLite file path; default :memory:. |
FIREBASE_PROJECT_ID |
Required when not using the fake verifier. Must match the web's VITE_FIREBASE_PROJECT_ID. |
FIREBASE_SERVICE_ACCOUNT_PATH / GOOGLE_APPLICATION_CREDENTIALS |
Path to a Firebase service-account JSON. |
FIREBASE_USE_FAKE |
'true' switches Firebase Auth to the in-process fake verifier. |
FIREBASE_FIRESTORE_REPORTS_COLLECTION |
Firestore collection id for code-review reports. |
GITHUB_OAUTH_CLIENT_ID / GITHUB_OAUTH_CLIENT_SECRET / GITHUB_OAUTH_CALLBACK_URL |
GitHub OAuth App credentials. |
OPENAI_API_KEY (also OPEN_API_KEY) |
Enables real LLM output. Without it, the analyzer returns a stub. |
OPENAI_MODEL |
Override the default gpt-4o-mini. |
INTERNAL_API_KEY |
Static key accepted by the API-key adapter. |
VITE_API_URL, VITE_FIREBASE_* |
Web client config (see apps/web/.env.example). |
| Command | Description |
|---|---|
yarn dev |
API + Web in parallel. |
yarn dev:api / yarn dev:web |
Single app. |
yarn build |
Build both apps. |
yarn test:e2e |
API e2e with fakes. |
yarn test:e2e:ui |
Web e2e (Playwright). |
rockets-starter (GitHub) |
This example |
|---|---|
@bitwild/rockets@1.0.0-alpha.X from npm |
workspace:^ → local packages/* |
Built-in @bitwild/rockets-auth |
Firebase via defineFirebaseAuth() |
| Next.js web | Vite + React (same ports 3000 / 3001) |
| PostgreSQL | SQLite + Firestore |
BSD-3-Clause