Production-oriented NestJS monorepo with a microservices architecture. Two independently deployable services communicate over TCP: an HTTP API gateway handles all client-facing concerns, and a dedicated auth/users service owns business logic and database access.
- Two services:
api-gateway(HTTP, port 9011) andauth-service(TCP microservice, port 9012) - NestJS v11 monorepo with shared libraries (
@app/common,@app/prisma,@app/contracts) - Prisma v7 client generated to
generated/prisma, accessed only fromauth-service - PostgreSQL support with pluggable Prisma adapter (
pgorneon) - Authentication stack:
- JWT access + refresh token flow (tokens are self-contained — no per-request TCP hop)
- Email/password + MSISDN/OTP support
- Google OAuth (
/v1/auth/google) handled in gateway, account creation delegated to auth-service
- Global security layer (all enforced in
api-gateway):- API key guard (
x-api-keyheader) - JWT guard with local validation (no round-trip to auth-service)
- Role-based guard (
@Roles(...)) RpcExceptionFilter— unwrapsRpcExceptionfrom auth-service into proper HTTP responses- Throttling presets (
short,medium,long)
- API key guard (
- Swagger docs at
/api-docs(served by gateway) MicroservicesClientModuleis@Global()—AUTH_SERVICEClientProxyis available across all gateway modules without per-module imports- Shared pagination service and DTO patterns via
@app/common
| Concern | Technology |
|---|---|
| Framework | NestJS 11 |
| Transport | TCP (@nestjs/microservices) |
| Database | PostgreSQL + Prisma 7 |
| Auth | Passport (jwt, google-oauth20) |
| Validation | class-validator / class-transformer |
| Testing | Jest + Supertest |
| Package manager | pnpm |
nestjs-starter/
├── apps/
│ ├── api-gateway/ # HTTP server (port 9011)
│ │ └── src/
│ │ ├── main.ts
│ │ ├── app.module.ts
│ │ ├── auth/ # Gateway auth controller + Google strategy
│ │ ├── users/ # Gateway users controller
│ │ └── common/ # Gateway-specific guards, filters, strategies
│ └── auth-service/ # TCP microservice (port 9012)
│ └── src/
│ ├── main.ts
│ ├── app.module.ts
│ ├── auth/ # @MessagePattern handlers + auth business logic
│ └── users/ # @MessagePattern handlers + users business logic
├── libs/
│ ├── common/ # @app/common — pipes, filters, decorators, utils
│ ├── prisma/ # @app/prisma — PrismaModule, PrismaService
│ └── contracts/ # @app/contracts — patterns, payloads, JwtPayload
├── prisma/ # Shared Prisma schema
├── generated/prisma/ # Generated Prisma client (git-ignored)
└── test/ # E2E tests
Client → HTTP (port 9011) → api-gateway → TCP (port 9012) → auth-service → PostgreSQL
JWT validation happens locally in the gateway — tokens embed name, roleId, roleName, and organizationId, so no TCP hop is needed per authenticated request.
pnpm installcp .env.example .envFill in all required values (see Environment Variables below).
pnpm prisma generatepnpm prisma migrate devTerminal 1 — auth-service (TCP :9012):
pnpm start:authTerminal 2 — api-gateway (HTTP :9011):
pnpm start:gatewayGET http://localhost:9011/returns{ "message": "OK!" }GET http://localhost:9011/healthreturns runtime health info- Swagger UI:
http://localhost:9011/api-docs
| Command | Description |
|---|---|
pnpm start:auth |
Start auth-service TCP microservice (port 9012) |
pnpm start:gateway |
Start api-gateway HTTP server (port 9011) |
pnpm build:auth |
Compile auth-service |
pnpm build:gateway |
Compile api-gateway |
pnpm start:prod:auth |
Run compiled auth-service |
pnpm start:prod:gateway |
Run compiled api-gateway |
| Command | Description |
|---|---|
pnpm test |
Unit tests (apps/**/*.spec.ts, libs/**/*.spec.ts) |
pnpm test:watch |
Watch mode |
pnpm test:cov |
Coverage report |
pnpm test:e2e |
E2E tests (test/**/*.e2e-spec.ts) |
| Command | Description |
|---|---|
pnpm lint |
ESLint with auto-fix |
pnpm format |
Prettier format |
| Command | Description |
|---|---|
pnpm prisma generate |
Regenerate Prisma client |
pnpm prisma migrate dev |
Create and apply migration |
pnpm prisma studio |
Database GUI |
# Service ports
PORT=9011 # api-gateway HTTP port
AUTH_SERVICE_HOST=localhost # auth-service host
AUTH_SERVICE_PORT=9012 # auth-service TCP port
# Database (used only by auth-service)
DATABASE_URL=postgresql://...
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=secret
DB_DATABASE=mydb
DB_ADAPTER=pg # pg | neon
# Security
X_API_KEY=your-api-key
JWT_SECRET=your-jwt-secret
# Google OAuth (gateway-level)
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GOOGLE_CALLBACK_URL=http://localhost:9011/v1/auth/google/callback
FRONTEND_AUTH_CALLBACK_URL=http://localhost:3000/auth/callbackVersioning is URI-based with default version v1. All routes are served through api-gateway.
| Method | Path | Guard | Description |
|---|---|---|---|
GET |
/ |
None | Root status |
GET |
/health |
None | Runtime health details |
| Method | Path | Guard | TCP Pattern |
|---|---|---|---|
POST |
/signup |
@Public() |
auth.signup |
POST |
/login |
@Public() |
auth.login |
POST |
/refresh |
@Public() |
auth.refresh |
GET |
/me |
JWT | auth.me |
PATCH |
/password/update |
JWT | auth.password.update |
POST |
/password/reset/request |
@Public() |
auth.password.reset.request |
PATCH |
/password/reset |
@Public() |
auth.password.reset |
POST |
/otp/request |
@Public() |
auth.otp.request |
POST |
/otp/resend |
@Public() |
auth.otp.resend |
POST |
/otp/verify |
@Public() |
auth.otp.verify |
GET |
/google |
@Open() |
Google OAuth initiation |
GET |
/google/callback |
@Open() |
Google OAuth callback → auth.google.callback |
| Method | Path | Guard | TCP Pattern |
|---|---|---|---|
GET |
/ |
JWT | users.findAll |
GET |
/user/:user_id |
JWT | users.findById |
GET |
/user/:user_id/profile-status |
JWT | users.profileStatus |
POST |
/ |
JWT | users.create |
PATCH |
/user/:user_id |
JWT | users.update |
DELETE |
/user/:user_id |
JWT | users.delete |
Global guards applied in this order on every request through api-gateway:
ApiKeyGuard— checksx-api-keyheader; passes if Bearer token already presentJwtAuthGuard— skips@Open()and@Public()routes; validates JWT locallyRolesGuard— checks@Roles(...)metadata againstrequest.user.roleName
Decorator summary:
@Open()— skips all guards (used for OAuth endpoints)@Public()— skips JWT validation but not API key check
- UI:
http://localhost:9011/api-docs - JSON:
http://localhost:9011/api-docs/json
Swagger is configured with Bearer auth definition named access-token.
Additional docs live in docs/:
docs/getting-started.md— install, env setup, two-service startupdocs/project-architecture.md— monorepo layout, request flow, module designdocs/api-reference.md— full route catalog with TCP patternsdocs/auth-and-security.md— guards, JWT model, Google OAuth, RpcExceptionFilterdocs/database-and-prisma.md— Prisma setup, shared-DB pattern, migration workflowdocs/development-workflow.md— adding endpoints, new services, testing patterns
MIT