Creare un SaaS con NestJS e Angular: Guida Completa 2026
Perché NestJS + Angular per il tuo SaaS?
L'anno scorso ho lanciato il mio primo prodotto SaaS. Dopo tre mesi di sviluppo, avevo un sistema di autenticazione, un form di pagamento mezzo funzionante, e zero funzionalita' specifiche per il mio prodotto. Ho buttato via quei tre mesi e ho ricominciato con un boilerplate. In due settimane ero piu' avanti di dove mi trovavo dopo tre mesi di lavoro da zero. Questa e' la lezione che vorrei aver imparato prima.
Il mercato SaaS italiano sta vivendo una crescita senza precedenti nel 2026. Sempre più aziende e startup scelgono di costruire prodotti software-as-a-service, e la combinazione NestJS + Angular rappresenta una delle scelte tecnologiche più solide per questo tipo di progetto.
NestJS offre un framework backend strutturato, con un'architettura modulare ispirata ad Angular stesso. Angular, sviluppato da Google, è il framework frontend più robusto per applicazioni enterprise. Entrambi condividono TypeScript come linguaggio, permettendo la condivisione di tipi e interfacce tra frontend e backend.
Questa guida ti accompagnerà nella costruzione di un SaaS completo, dall'architettura al deploy. Per un confronto dettagliato con Next.js, consulta il nostro confronto approfondito NestJS + Angular vs Next.js.
Struttura del Progetto
Utilizziamo un monorepo Nx per organizzare il codice:
saas-project/
├── apps/
│ ├── api/ # Backend NestJS
│ │ ├── src/
│ │ │ ├── auth/ # Modulo autenticazione
│ │ │ ├── billing/ # Modulo pagamenti
│ │ │ ├── tenants/ # Gestione tenant
│ │ │ └── users/ # Gestione utenti
│ ├── web/ # Frontend Angular
│ │ ├── src/
│ │ │ ├── app/
│ │ │ │ ├── auth/
│ │ │ │ ├── dashboard/
│ │ │ │ └── shared/
├── libs/ # Librerie condivise
│ ├── shared-types/ # Tipi TypeScript condivisi
│ └── shared-utils/ # Utility condivise
Cloudrix SaaS Starter ships with this pre-configured and tested.
Skip weeks of boilerplate — auth, payments, multi-tenancy, and deployment included out of the box.
Try the live demo →Passo 1: Configurazione dell'Ambiente
Iniziamo con la configurazione base del progetto:
# Crea il workspace Nx
npx create-nx-workspace@latest saas-project --preset=apps
# Aggiungi i plugin necessari
npm install @nx/nest @nx/angular
# Genera le applicazioni
nx g @nx/nest:app api
nx g @nx/angular:app web
Configura le variabili d'ambiente nel file .env:
DATABASE_URL=postgresql://postgres:password@localhost:5432/saas_db
JWT_SECRET=il-tuo-segreto-super-sicuro
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
APP_URL=http://localhost:4200
Passo 2: Sistema di Autenticazione
L'autenticazione JWT è il fondamento di ogni applicazione SaaS.
Backend: Servizio di Autenticazione
// auth.service.ts
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
private readonly jwtService: JwtService,
private readonly tenantsService: TenantsService,
) {}
async registrazione(dto: RegisterDto) {
const utenteEsistente = await this.usersService.findByEmail(dto.email);
if (utenteEsistente) {
throw new ConflictException('Email già registrata');
}
const passwordHash = await bcrypt.hash(dto.password, 12);
const utente = await this.usersService.create({
email: dto.email,
password: passwordHash,
nome: dto.nome,
cognome: dto.cognome,
});
// Crea automaticamente un nuovo tenant
const tenant = await this.tenantsService.create({
nome: dto.nomeAzienda,
proprietarioId: utente.id,
piano: 'free',
});
return this.generaTokens(utente, tenant.id);
}
async login(dto: LoginDto) {
const utente = await this.usersService.findByEmail(dto.email);
if (!utente || !(await bcrypt.compare(dto.password, utente.password))) {
throw new UnauthorizedException('Credenziali non valide');
}
return this.generaTokens(utente, utente.tenantId);
}
private generaTokens(utente: User, tenantId: string) {
const payload = {
sub: utente.id,
email: utente.email,
tenantId,
ruolo: utente.ruolo,
};
return {
accessToken: this.jwtService.sign(payload, { expiresIn: '15m' }),
refreshToken: this.jwtService.sign(payload, { expiresIn: '7d' }),
utente: {
id: utente.id,
email: utente.email,
nome: utente.nome,
},
};
}
}
Frontend: Servizio Angular
// auth.service.ts
@Injectable({ providedIn: 'root' })
export class AuthService {
private readonly http = inject(HttpClient);
private readonly router = inject(Router);
private utenteCorrente = signal<User | null>(null);
readonly isAutenticato = computed(() => !!this.utenteCorrente());
login(credenziali: { email: string; password: string }) {
return this.http.post<AuthResponse>('/api/auth/login', credenziali).pipe(
tap(risposta => {
localStorage.setItem('accessToken', risposta.accessToken);
localStorage.setItem('refreshToken', risposta.refreshToken);
this.utenteCorrente.set(risposta.utente);
this.router.navigate(['/dashboard']);
}),
);
}
logout() {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
this.utenteCorrente.set(null);
this.router.navigate(['/login']);
}
}
Passo 3: Architettura Multi-Tenant
L'isolamento dei dati tra tenant è fondamentale in un'applicazione SaaS:
// tenant.middleware.ts
@Injectable()
export class TenantMiddleware implements NestMiddleware {
constructor(
private readonly tenantsService: TenantsService,
private readonly cls: ClsService,
) {}
async use(req: Request, res: Response, next: NextFunction) {
const token = this.estraiToken(req);
if (token) {
const payload = this.jwtService.verify(token);
const tenant = await this.tenantsService.findById(payload.tenantId);
if (!tenant || !tenant.attivo) {
throw new ForbiddenException('Tenant non valido o disattivato');
}
// Salva il tenantId nel contesto della richiesta
this.cls.set('tenantId', tenant.id);
this.cls.set('piano', tenant.piano);
}
next();
}
private estraiToken(req: Request): string | null {
const auth = req.headers.authorization;
return auth?.startsWith('Bearer ') ? auth.slice(7) : null;
}
}
Filtro Automatico delle Query
// tenant.subscriber.ts
@EventSubscriber()
export class TenantSubscriber implements EntitySubscriberInterface {
constructor(
dataSource: DataSource,
private readonly cls: ClsService,
) {
dataSource.subscribers.push(this);
}
beforeInsert(event: InsertEvent<any>) {
const tenantId = this.cls.get('tenantId');
if (tenantId && event.entity) {
event.entity.tenantId = tenantId;
}
}
}
Passo 4: Integrazione Pagamenti con Stripe
Per una guida completa sull'integrazione Stripe, consulta il nostro tutorial completo Stripe Subscriptions con NestJS.
Vuoi tutto questo gia' pronto? SaaS Starter include autenticazione, pagamenti, multi-tenancy e 55+ test. Prova la versione Lite gratuita →
// billing.service.ts
@Injectable()
export class BillingService {
private stripe: Stripe;
constructor(private readonly configService: ConfigService) {
this.stripe = new Stripe(this.configService.get('STRIPE_SECRET_KEY'));
}
async creaSessioneCheckout(tenantId: string, pianoId: string) {
const prezzi = {
starter: 'price_starter_mensile',
pro: 'price_pro_mensile',
enterprise: 'price_enterprise_mensile',
};
return this.stripe.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
line_items: [{ price: prezzi[pianoId], quantity: 1 }],
success_url: `${this.configService.get('APP_URL')}/pagamento/successo`,
cancel_url: `${this.configService.get('APP_URL')}/pagamento/annullato`,
metadata: { tenantId, pianoId },
});
}
async gestisciWebhook(evento: Stripe.Event) {
switch (evento.type) {
case 'customer.subscription.created':
await this.attivaAbbonamento(evento.data.object);
break;
case 'customer.subscription.deleted':
await this.disattivaAbbonamento(evento.data.object);
break;
case 'invoice.payment_failed':
await this.gestisciPagamentoFallito(evento.data.object);
break;
}
}
}
Passo 5: Dashboard Angular
// dashboard.component.ts
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [StatCardComponent, GraficoComponent, CurrencyPipe],
template: `
<h1 class="text-2xl font-bold mb-6">Dashboard</h1>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
<app-stat-card
titolo="Ricavi Mensili"
[valore]="statistiche()?.ricaviMensili | currency:'EUR'"
icona="trending_up"
/>
<app-stat-card
titolo="Utenti Attivi"
[valore]="statistiche()?.utentiAttivi"
icona="people"
/>
<app-stat-card
titolo="Tasso di Conversione"
[valore]="statistiche()?.tassoConversione + '%'"
icona="analytics"
/>
<app-stat-card
titolo="Tasso di Abbandono"
[valore]="statistiche()?.tassoAbbandono + '%'"
icona="warning"
/>
</div>
<app-grafico
titolo="Andamento Ricavi"
[dati]="datiRicavi()"
tipo="line"
/>
`,
})
export class DashboardComponent {
private readonly analyticsService = inject(AnalyticsService);
statistiche = toSignal(this.analyticsService.getStatistiche());
datiRicavi = toSignal(this.analyticsService.getDatiRicavi());
}
Passo 6: Deploy in Produzione
# docker-compose.prod.yml
services:
api:
build:
context: .
dockerfile: apps/api/Dockerfile
target: production
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
restart: unless-stopped
depends_on:
db:
condition: service_healthy
web:
build:
context: .
dockerfile: apps/web/Dockerfile
ports:
- "80:80"
- "443:443"
restart: unless-stopped
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 10s
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
pgdata:
Costruire da Zero vs Usare un Boilerplate
Costruire un SaaS da zero richiede mesi solo per le funzionalità di base: autenticazione, pagamenti, multi-tenancy, dashboard admin. Si parla di 4-6 mesi di sviluppo prima di poter iniziare a lavorare sulle funzionalità specifiche del tuo prodotto.
SaaS Starter include tutto questo, pronto all'uso:
- Autenticazione JWT + OAuth2 (Google, GitHub)
- Integrazione Stripe per abbonamenti
- Architettura multi-tenant completa
- Controllo accessi basato sui ruoli (RBAC)
- Dashboard admin Angular con grafici
- Configurazione Docker per produzione
- Pipeline CI/CD
Puoi iniziare con la versione Lite gratuita per esplorare le funzionalità base. Per progetti professionali, i piani Pro ($149), Business ($249) ed Enterprise ($399) includono funzionalità avanzate e supporto prioritario.
Conclusione: Inizia Oggi
NestJS e Angular formano un'accoppiata potente per lo sviluppo SaaS. La condivisione di TypeScript, l'architettura modulare e gli ecosistemi maturi di entrambi i framework permettono di costruire applicazioni scalabili e manutenibili.
Non perdere tempo a reinventare la ruota. Visita la demo live di SaaS Starter per vederlo in azione e inizia a costruire il tuo prodotto SaaS oggi stesso.
Piani disponibili:
- Lite (Gratuito) — Esplora l'architettura di base
- Pro ($149) — Autenticazione + pagamenti + dashboard
- Business ($249) — Multi-tenancy + RBAC + funzionalita' avanzate
- Enterprise ($399) — Tutte le funzionalita' + supporto prioritario
Vedi i prezzi | Prova la demo live | Scarica la versione Lite gratuita
Articoli correlati: Dashboard Angular: costruire da zero vs boilerplate | Tutorial NestJS REST API | Guida al deploy su AWS