A production-ready NestJS backend template with modular architecture - use only the services you need. Designed for startups and side projects that need to scale.
π¦ Using this template? Run
npm run setupafter cloning to rename the project to your app name.
This template supports multiple configurations - from a minimal API with no external dependencies to a full-featured backend with all services:
| Stack | Services | Setup Time | Best For |
|---|---|---|---|
| Minimal | Just NestJS + Admin | 2 min | Simple APIs, microservices |
| Standard β | + Database + Auth | 15 min | Most web apps |
| Full | + Storage + Redis + OAuth | 30 min | Production apps |
See STACK_PROFILES.md for detailed comparison and setup guides.
| Service | Provider | Free Tier | Auto-Enabled When |
|---|---|---|---|
| Backend | NestJS + Fastify | - | Always |
| Database | Supabase Postgres | 500MB, 2 projects | SUPABASE_URL set |
| Auth | Firebase Auth | 50K MAU | FIREBASE_PROJECT_ID set |
| OAuth | Self-hosted JWT | - | OAUTH_JWT_SECRET set |
| Storage | Cloudflare R2 | 10GB, 1M requests | R2_ACCOUNT_ID set |
| Cache/Queue | Upstash Redis | 10K commands/day | REDIS_URL set |
| Deployment | Cloud Run / Fly.io | Generous free tier | - |
- β Modular by Design: Load only the services you need via environment variables
- β Fast & Lightweight: Fastify adapter (2x faster than Express)
- β Type-Safe: Full TypeScript with strict mode
- β Three Auth Systems: Firebase, OAuth, or Admin (mix and match)
- β Database: Supabase Postgres with connection pooling (port 6543)
- β Object Storage: Cloudflare R2 with pre-signed URLs (never stream through server)
- β Caching: Redis-based caching layer with TTL support
- β Rate Limiting: Built-in throttling (in-memory or Redis-backed)
- β Background Jobs: BullMQ queues for async processing
- β
API Documentation: Swagger UI at
/api/docs - β Resource Generator: Scaffold CRUD APIs with one command
- β Production Ready: Docker + Cloud Run / Fly.io deployment
git clone <repo>
cd be-template
npm install
cp .env.minimal .env
npm run start:devβ Running in 2 minutes - No external services required!
git clone <repo>
cd be-template
npm install
cp .env.standard .env
# Configure Supabase + Firebase (see STACK_PROFILES.md)
npm run start:devβ Running in 15 minutes - Database + Authentication
git clone <repo>
cd be-template
npm install
cp .env.full .env
# Configure all services (see STACK_PROFILES.md)
npm run start:devβ Running in 30 minutes - All features enabled
For detailed setup instructions, see STACK_PROFILES.md
If you cloned from the template, rename all references to your project name:
npm run setup
# Follow the prompts to enter your project nameThis will:
- Update package.json name and description
- Update all config files (fly.toml, Dockerfile, etc.)
- Update documentation references
- Clean up the setup files
Note: The guides below are for the Full Stack configuration. For Minimal or Standard stacks, you only need to configure the services you're using. See STACK_PROFILES.md for stack-specific setup.
Firebase handles user authentication. Your frontend will use Firebase SDK for login/register, and this backend verifies the JWT tokens.
Required for: Standard Stack, Full Stack
Skip if: Using OAuth-only authentication or no authentication
- Go to Firebase Console
- Click "Create a project" (or select existing)
- Enter project name (e.g.,
my-app-prod) - Disable Google Analytics (optional for dev)
- Click Create project
- In Firebase Console, go to Build β Authentication
- Click "Get started"
- Go to Sign-in method tab
- Enable your preferred providers:
- Email/Password - Click, toggle Enable, Save
- Google - Click, toggle Enable, add support email, Save
- (Optional) Add other providers as needed
- Click the βοΈ gear icon β Project settings
- Go to Service accounts tab
- Click "Generate new private key"
- Save the downloaded JSON file securely (NEVER commit this file)
- Open the JSON and copy these values to your
.env:
FIREBASE_PROJECT_ID=your-project-id
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxxxx@your-project-id.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvQ...(full key)...\n-----END PRIVATE KEY-----\n"
β οΈ Important: The private key must include the\ncharacters and be wrapped in quotes.
Your frontend app uses Firebase SDK to authenticate users:
// Frontend: Login and get token
import { signInWithEmailAndPassword, getIdToken } from 'firebase/auth';
const userCredential = await signInWithEmailAndPassword(auth, email, password);
const token = await getIdToken(userCredential.user);
// Use token in API calls
fetch('https://your-api.com/auth/me', {
headers: { 'Authorization': `Bearer ${token}` }
});Supabase provides a managed PostgreSQL database with automatic APIs and Row Level Security.
Required for: Standard Stack, Full Stack
Skip if: Not using a database (e.g., proxy API, static content server)
- Go to Supabase Dashboard
- Click "New project"
- Select your organization (or create one)
- Enter:
- Name:
my-app-db - Database Password: Generate a strong password (save this!)
- Region: Choose closest to your users
- Name:
- Click "Create new project" (takes ~2 minutes)
- Go to Project Settings (gear icon) β Database
- Scroll to Connection string section
- Select URI tab
- Copy the connection string
β οΈ Critical: Change the port from5432to6543for pooled connections (required for serverless)
DATABASE_URL=postgresql://postgres.[project-ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres- Go to Project Settings β API
- Copy these values to your
.env:
SUPABASE_URL=https://[project-ref].supabase.co
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
β οΈ Security: The service role key bypasses RLS. Never expose it to the frontend.
R2 provides S3-compatible object storage with no egress fees. Files are uploaded directly from the client using pre-signed URLs.
- Go to Cloudflare Dashboard
- Select your account (or create one)
- In the sidebar, click R2 Object Storage
- Click "Create bucket"
- Enter bucket name (e.g.,
my-app-storage) - Click Create bucket
- In R2, click "Manage R2 API Tokens" (top right)
- Click "Create API token"
- Configure:
- Token name:
backend-api - Permissions: Object Read & Write
- Specify bucket(s): Select your bucket
- Token name:
- Click Create API Token
- Copy the credentials immediately (shown only once!)
R2_ACCOUNT_ID=your-cloudflare-account-id
R2_ACCESS_KEY_ID=your-access-key-id
R2_SECRET_ACCESS_KEY=your-secret-access-key
R2_BUCKET_NAME=my-app-storage- Your Account ID is in the Cloudflare dashboard URL:
dash.cloudflare.com/[ACCOUNT_ID]/... - Or find it in R2 β Overview β right sidebar
If you need public URLs for files:
- Go to your bucket β Settings
- Under Public access, click "Allow Access"
- Add a custom domain or use the R2.dev subdomain
- Add to
.env:
R2_PUBLIC_URL=https://pub-xxxx.r2.devUpstash provides serverless Redis for caching, rate limiting, and background job queues.
- Go to Upstash Console
- Click "Create Database"
- Configure:
- Name:
my-app-cache - Type: Regional
- Region: Choose closest to your deployment
- Name:
- Click Create
- In your database, go to Details tab
- Find REST URL or Redis URL
- Copy the Redis URL (starts with
redis://orrediss://)
REDIS_URL=rediss://default:xxxxxxxxxxxx@usw1-xxxx.upstash.io:6379π‘ Tip: Upstash also shows a
REDIS_TOKENfor REST API access. You can ignore this for now.
Add these final settings to your .env:
# Server
PORT=3000
HOST=0.0.0.0
NODE_ENV=development
# Rate Limiting
THROTTLE_TTL=60
THROTTLE_LIMIT=100
# CORS (update for production)
CORS_ORIGIN=*npm run start:devYou should see:
Application is running on: http://0.0.0.0:3000
Swagger UI available at: http://0.0.0.0:3000/api/docs
# Health check
curl http://localhost:3000/health
# Swagger UI
open http://localhost:3000/api/docs| Error | Solution |
|---|---|
Firebase credentials not configured |
Check FIREBASE_* env vars are set correctly |
R2 configuration is incomplete |
Check all R2_* env vars |
ECONNREFUSED (Redis) |
Verify REDIS_URL is correct |
Connection refused (Database) |
Check DATABASE_URL, ensure port is 6543 |
npm run start:dev # Start with hot reload
npm run build # Production build
npm run lint # Fix linting issues
npm run format # Format with Prettier
npm run generate # Generate CRUD resourceQuickly scaffold a complete CRUD API for a database table:
npm run generate Product name:string price:number description:string? inStock:booleanThis creates:
src/modules/product/- Module, Controller, Servicesrc/modules/product/dto/- Create & Update DTOs with validationsrc/modules/product/entities/- TypeScript interfacemigrations/- SQL migration with RLS policies
Then:
- Add
ProductModuletoapp.module.tsimports - Run the SQL migration in Supabase SQL Editor
This backend only verifies Firebase tokens. Users authenticate on the frontend:
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β Frontend ββββββΆβ Firebase ββββββΆβ Backend β
β (React) β β Auth β β (NestJS) β
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β β β
β 1. Login/Register β β
βββββββββββββββββββββΆβ β
β β β
β 2. Get JWT Token β β
ββββββββββββββββββββββ β
β β β
β 3. API Request with Bearer Token β
ββββββββββββββββββββββββββββββββββββββββββΆβ
β β β
β β 4. Verify JWT β
β ββββββββββββββββββββββ
β β β
β 5. Response β
βββββββββββββββββββββββββββββββββββββββββββ
Use the FirebaseAuthGuard on any route requiring authentication:
import { UseGuards } from '@nestjs/common';
import { FirebaseAuthGuard } from './common/guards/firebase-auth.guard';
import { CurrentUser } from './common/decorators/current-user.decorator';
@UseGuards(FirebaseAuthGuard)
@Get('profile')
getProfile(@CurrentUser() user: any) {
return {
uid: user.uid,
email: user.email,
};
}Interactive API documentation at http://localhost:3000/api/docs:
- Browse all endpoints with request/response schemas
- Test endpoints directly in the browser
- Click "Authorize" and paste your Firebase JWT to test protected routes
- View DTO validation requirements
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /health |
No | Health check |
| POST | /auth/verify |
No | Verify a Firebase token |
| GET | /auth/me |
Yes | Get current user profile |
| PUT | /auth/me |
Yes | Update current user |
| DELETE | /auth/me |
Yes | Delete current user account |
See DEPLOYMENT.md for detailed instructions.
# 1. Install gcloud CLI
# 2. Authenticate
gcloud auth login
gcloud config set project YOUR_PROJECT_ID
# 3. Deploy
./deploy-cloud-run.sh
# 4. Set environment variables in Cloud Run Console# 1. Install flyctl
# 2. Authenticate
flyctl auth login
# 3. Launch and deploy
flyctl launch --no-deploy
flyctl secrets set DATABASE_URL="..." FIREBASE_PROJECT_ID="..." # all env vars
flyctl deploysrc/
βββ main.ts # Application entry + Swagger setup
βββ app.module.ts # Root module
βββ app.controller.ts # Health check endpoint
βββ common/
β βββ guards/ # FirebaseAuthGuard
β βββ decorators/ # @CurrentUser() decorator
βββ config/
β βββ configuration.ts # Environment config
β βββ database.module.ts # Supabase client setup
βββ modules/
β βββ auth/ # Firebase auth verification
β βββ storage/ # R2 storage with pre-signed URLs
β βββ cache/ # Redis caching service
β βββ queue/ # BullMQ job processing
βββ example.* # Example implementations (optional)
| Variable | Required | Description |
|---|---|---|
PORT |
No | Server port (default: 3000) |
HOST |
No | Server host (default: 0.0.0.0) |
NODE_ENV |
No | Environment (development/production) |
DATABASE_URL |
Yes | Supabase Postgres URL (use port 6543) |
SUPABASE_URL |
Yes | Supabase project URL |
SUPABASE_ANON_KEY |
Yes | Supabase anonymous key |
SUPABASE_SERVICE_ROLE_KEY |
Yes | Supabase service role key |
FIREBASE_PROJECT_ID |
Yes | Firebase project ID |
FIREBASE_CLIENT_EMAIL |
Yes | Firebase service account email |
FIREBASE_PRIVATE_KEY |
Yes | Firebase private key (with \n) |
R2_ACCOUNT_ID |
Yes | Cloudflare account ID |
R2_ACCESS_KEY_ID |
Yes | R2 access key |
R2_SECRET_ACCESS_KEY |
Yes | R2 secret key |
R2_BUCKET_NAME |
Yes | R2 bucket name |
R2_PUBLIC_URL |
No | Public URL for R2 bucket |
REDIS_URL |
Yes | Upstash Redis URL |
THROTTLE_TTL |
No | Rate limit window in seconds (default: 60) |
THROTTLE_LIMIT |
No | Max requests per window (default: 10) |
CORS_ORIGIN |
No | Allowed origins (default: *) |
- STACK_PROFILES.md - Choose your stack configuration (Minimal/Standard/Full)
- ARCHITECTURE.md - Modular architecture and technical reference
- CACHING.md - Redis caching patterns and pagination best practices
- DEPLOYMENT.md - Deploy to Cloud Run or Fly.io
- CONFIGURATION_CHECKLIST.md - Setup verification checklist
- .github/copilot-instructions.md - AI agent developer guide
If you see error:1E08010C:DECODER routines::unsupported:
- Ensure the key is wrapped in double quotes in
.env - The key should contain literal
\ncharacters, not actual newlines - Copy directly from the JSON file, including
-----BEGIN PRIVATE KEY-----
- Always use port
6543(pooler) not5432(direct) - Check if your IP is allowed in Supabase β Database β Network
- Use
rediss://(with double s) for TLS connections - Upstash URLs should work as-is
ISC