A full‑stack TypeScript monorepo starter built with React on the frontend and Express on the backend. Firebase Authentication (Google Sign‑In) handles users; the server validates Firebase ID tokens on every protected route.
Additional documentation lives in the docs/ directory to explain architecture, auth flow, and testing practices for both client and server.
Documentation files are located in the docs/ directory and cover various aspects of the project:
- architecture.md – overall system architecture and design decisions
- auth.md – how Firebase authentication and token validation work across client and server
- backend-testing.md – guidance for writing and running server‑side tests
- client-testing.md – guidance for writing and running client‑side tests
- docker-dev.md – running the full stack locally with Docker Compose and hot reload (see
.env.example) - docker-prod.md – building and running production images with multi-stage Dockerfiles (includes healthcheck details)
- postgres.md – PostgreSQL configuration and usage
- architecture-ascii.md – plain‑text diagram showing container relationships
The architecture is also reproduced below for quick reference:
┌─────────────────────────────────────────────┐
│ Browser (UI) │
│ (client + Firebase JS SDK) │
└────────────────┬────────────────────────────┘
│
ID token / API calls
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Docker Host │
│ │
│ ┌─────────────────────────┐ ┌──────────────────────────────┐ │
│ │ [container] Client │ │ [container] Server │ │
│ │ (Vite / dev proxy) │◀─────────│ (Express API) │ │
│ │ port: 5173 │ │ port: 3001 │ │
│ └────────────┬────────────┘ └────────┬─────────────┬───────┘ │
│ │ │ │ │
│ serves │ queries │ │ verifies │
│ HTML/JS │ │ ID token │
│ │ │ │ │
│ │ │ │ |
│ │ ▼ ▼ │
│ │ ┌────────────────────┐ ┌─────────────┐ │
│ │ │ [container] │ │ │ │
│ │ │ PostgreSQL DB │ │ Firebase │ │
│ │ │ (postgres:5432) │ │ Admin SDK │ │
│ │ └────────────────────┘ └─────────────┘ │
│ │ │
└───────────────┼─────────────────────────────────────────────────────────┘
│
│ (browser loads app → calls server API directly)
▼
┌─────────────────┐ ┌──────────────────────────┐
│ Browser (UI) │◀────────▶│ Firebase Auth (cloud) │
│ (JS SDK) │ │ (token issuance / JWKS) │
└─────────────────┘ └──────────────────────────┘
| Layer | Technology |
|---|---|
| Monorepo | npm workspaces |
| Client | React 18, Vite, TypeScript, Tailwind CSS, axios, React Query |
| Auth | Firebase Authentication (Google Sign-In) |
| Server | Node.js, Express, TypeScript, tsx |
| Token validation | Firebase Admin SDK |
app-template/
├── package.json # Root — scripts & workspaces
├── tsconfig.base.json # Shared TypeScript config
└── packages/
├── client/ # React SPA (port 5173)
│ ├── src/
│ │ ├── firebase.ts # Firebase client init
│ │ ├── api/ # axios + react-query hooks
│ │ ├── features/ # feature-based UI code
│ │ │ ├── auth/AuthContext.tsx
│ │ │ ├── common/ProtectedRoute.tsx
│ │ │ ├── login/Login.tsx
│ │ │ └── dashboard/Dashboard.tsx
│ └── vite.config.ts # Dev proxy → :3001
└── server/ # Express API (port 3001)
└── src/
├── index.ts # App entry, routes
├── firebase.ts # Firebase Admin init
└── middleware/authMiddleware.ts
Follow these steps to boot the project locally. All commands are executed from the repository root unless otherwise noted.
- Node.js 18+ (LTS)
- npm 9+ (bundled with Node) or an equivalent package manager
- A Firebase project with Google Sign‑In enabled (Firebase Console)
npm install- Node.js ≥ 18
- A Firebase project with Google Sign-In enabled (Firebase Console)
npm installCreate packages/client/.env:
VITE_FIREBASE_API_KEY=...
VITE_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=your-project
VITE_FIREBASE_STORAGE_BUCKET=your-project.appspot.com
VITE_FIREBASE_MESSAGING_SENDER_ID=...
VITE_FIREBASE_APP_ID=...These values are found under Project Settings → Your apps in the Firebase Console.
Create packages/server/.env using the service account credentials found under Project Settings → Service accounts → Generate new private key:
PORT=3001
FIREBASE_PROJECT_ID=your-project
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxxxx@your-project.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
"If you plan to run the API against a PostgreSQL database (the default for
Docker Compose setup), also set a connection string. The compose files start
a postgres:15-alpine container with the following default credentials:
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=appThose values are wired into the server service via the
DATABASE_URL environment variable, which you can override locally like so:
DATABASE_URL=postgres://postgres:postgres@localhost:5432/appIn production the compose configuration builds the URL from
${POSTGRES_USER}, ${POSTGRES_PASSWORD}, and ${POSTGRES_DB}. If the
variable is omitted entirely the server still starts, but it skips database
initialization and repo methods will log a notice; user persistence will be
in-memory only.
npm run devThis starts both packages concurrently:
| Process | URL |
|---|---|
| Client (Vite) | http://localhost:5173 |
| Server (Express) | http://localhost:3001 |
Vite proxies all /api/* requests to the Express server, so the client never needs to hard-code the API URL.
If you prefer containerized development, we provide a docker-compose.yml at the repo root. It builds two services (client and server) and mounts
source code for hot reload.
- Ensure Docker is installed and running.
- Copy
.env.example(or use your existing root.env) and adjust ports/vars as needed. - From the repo root execute:
docker compose up --buildYou can also run the containers in the background
with -d and view logs with docker compose logs -f.
The bind mounts keep your local edits in sync; there's no need for rebuilding when editing source files. To stop, use
docker compose downPorts are mapped according to the variables in .env (defaults
5173 and 3001). The client container reads VITE_API_URL from
.env so it can target the server.
These containers are strictly for development; production image definitions are not included.
All workspace commands are defined at the root package.json and are forwarded to the appropriate package(s).
| Script | Description | Notes |
|---|---|---|
npm run dev |
Launch client and server concurrently in development mode | Starts Vite (5173) and Express (3001) with hot reload |
npm run build |
Compile both packages for production | Outputs to each package’s dist folder |
npm run lint |
Run ESLint across all packages | Configured with shared rules |
npm run test:client |
Execute client unit tests (Vitest) | Runs inside packages/client |
npm run test:server |
Execute server tests (Vitest) | Runs inside packages/server |
npm run clean |
Remove node_modules and build artifacts |
Helpful when switching branches |
Tip: use
npm run <script> --workspace=clientto run a script in a single package.
The repository includes a shared ESLint configuration at the repo root. The primary lint command is wired at the root package.json and forwards to package-level lint scripts.
Run lint across all packages:
npm run lintRun lint for a single package:
npm run lint --workspace=packages/client
npm run lint --workspace=packages/serverAuto-fix fixable issues across the repository:
npx eslint . --ext .ts,.tsx --fixNotes:
- The server
lintscript runseslintfollowed bytsc --noEmitso type errors are checked as part of linting. - See docs/eslint.md for full configuration details, rules and overrides.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/health |
Public | Server health check |
| GET | /api/me |
Bearer token | Returns the authenticated user's profile |
| PUT | /api/me |
Bearer token | Update name/picture in database |
| DELETE | /api/me |
Bearer token | Delete account and revoke tokens |
| GET | /api/users |
Bearer token | (Authenticated) list all users |
See docs/auth.md for how token‑based auth works and docs/architecture.md for a full system overview.