Como Criar um SaaS com NestJS e Angular: Guia Completo 2026
Vou ser direto com voce: meu primeiro SaaS levou 5 meses antes de eu escrever uma unica linha do produto real. Autenticacao, webhooks do Stripe, multi-tenancy, painel admin, emails transacionais... quando finalmente lancei, ja tinha queimado metade do orcamento. Hoje vou te mostrar como evitar esse erro e construir as bases de um SaaS profissional -- sem perder meses com infraestrutura.
Por que NestJS e Angular formam a dupla ideal para SaaS
O Brasil tem uma das maiores comunidades de desenvolvedores do mundo, mas a quantidade de conteudo tecnico de qualidade em portugues ainda e desproporcional ao tamanho do mercado. Este guia preenche essa lacuna.
NestJS e Angular compartilham a mesma filosofia arquitetural: modulos, injecao de dependencias, decorators e TypeScript do inicio ao fim. Se voce ja trabalha com Angular no frontend, vai se sentir em casa com NestJS no backend. Essa sinergia reduz drasticamente a curva de aprendizado e permite que uma unica equipe domine toda a stack.
Para entender melhor essa sinergia, recomendo nosso artigo sobre por que Angular + NestJS e a melhor combinacao para SaaS.
Neste guia, vamos construir as fundacoes de uma aplicacao SaaS completa e pronta para producao.
Arquitetura da aplicacao
Antes de comecar a codar, vamos definir a estrutura:
┌─────────────────────────────────────┐
│ Angular Dashboard │
│ (Autenticacao, Painel, Admin) │
├─────────────────────────────────────┤
│ API NestJS │
│ (Guards, Pipes, Interceptors) │
├──────────┬──────────┬───────────────┤
│ Modulo │ Modulo │ Modulo │
│ Usuarios │ Cobranca │ Inquilinos │
├──────────┴──────────┴───────────────┤
│ PostgreSQL + Redis │
└─────────────────────────────────────┘
Para um aprofundamento nessa arquitetura multi-tenant, confira nosso guia dedicado sobre arquitetura multi-tenant com NestJS.
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: Criar o projeto
Usamos Nx para gerenciar backend e frontend em um monorepo:
npx create-nx-workspace@latest meu-saas --preset=nest
cd meu-saas
nx g @nx/angular:app painel
Instale as dependencias do backend:
npm install @nestjs/typeorm typeorm pg
npm install @nestjs/jwt @nestjs/passport passport passport-jwt
npm install class-validator class-transformer bcrypt
npm install -D @types/bcrypt @types/passport-jwt
Passo 2: Configurar o banco de dados
Configure a conexao com PostgreSQL no modulo raiz:
// app.module.ts
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT) || 5432,
username: process.env.DB_USUARIO || 'postgres',
password: process.env.DB_SENHA || 'postgres',
database: process.env.DB_NOME || 'meu_saas',
autoLoadEntities: true,
synchronize: process.env.NODE_ENV !== 'production',
}),
UsuariosModule,
AuthModule,
InquilinosModule,
],
})
export class AppModule {}
Dica importante: Nunca use
synchronize: trueem producao. Use migracoes do TypeORM. Ja vi bancos perderem colunas por esse erro -- incluindo o meu.
Passo 3: A entidade Usuario
Defina a entidade de usuario com TypeORM:
// usuarios/entities/usuario.entity.ts
import {
Entity, PrimaryGeneratedColumn, Column, CreateDateColumn,
} from 'typeorm';
@Entity('usuarios')
export class Usuario {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ unique: true })
email: string;
@Column()
nome: string;
@Column()
senha: string;
@Column({ default: 'usuario' })
papel: string;
@Column()
inquilinoId: string;
@CreateDateColumn()
criadoEm: Date;
}
Passo 4: Autenticacao com JWT
O servico de autenticacao gerencia cadastro e login. Para um guia completo sobre autenticacao JWT e OAuth, confira nosso artigo dedicado.
// auth/auth.service.ts
@Injectable()
export class AuthService {
constructor(
private readonly usuariosService: UsuariosService,
private readonly jwtService: JwtService,
) {}
async cadastrar(dto: CadastrarDto) {
const existente = await this.usuariosService
.buscarPorEmail(dto.email);
if (existente) {
throw new ConflictException('Este email ja esta cadastrado');
}
const senhaHash = await bcrypt.hash(dto.senha, 12);
const usuario = await this.usuariosService.criar({
...dto,
senha: senhaHash,
});
const tokens = this.gerarTokens(usuario);
return { usuario: this.semSenha(usuario), ...tokens };
}
async entrar(dto: EntrarDto) {
const usuario = await this.usuariosService
.buscarPorEmail(dto.email);
if (!usuario ||
!await bcrypt.compare(dto.senha, usuario.senha)) {
throw new UnauthorizedException('Credenciais invalidas');
}
const tokens = this.gerarTokens(usuario);
return { usuario: this.semSenha(usuario), ...tokens };
}
private gerarTokens(usuario: Usuario) {
const payload = {
sub: usuario.id,
email: usuario.email,
inquilinoId: usuario.inquilinoId,
papel: usuario.papel,
};
return {
accessToken: this.jwtService.sign(payload, {
expiresIn: '15m',
}),
refreshToken: this.jwtService.sign(payload, {
expiresIn: '7d',
}),
};
}
private semSenha(usuario: Usuario) {
const { senha, ...resultado } = usuario;
return resultado;
}
}
O controller expoe os endpoints:
// auth/auth.controller.ts
@Controller('api/auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('cadastrar')
cadastrar(@Body() dto: CadastrarDto) {
return this.authService.cadastrar(dto);
}
@Post('entrar')
entrar(@Body() dto: EntrarDto) {
return this.authService.entrar(dto);
}
@Post('renovar-token')
@UseGuards(JwtAuthGuard)
renovarToken(@Req() req) {
return this.authService.gerarTokens(req.user);
}
}
🚀 Quer pular semanas de configuracao? O Cloudrix SaaS Starter inclui autenticacao JWT completa, multi-tenancy, integracao Stripe, RBAC e deploy com Docker -- tudo configurado e testado. Veja a demo ao vivo.
Passo 5: Multi-tenancy
Em uma aplicacao SaaS, cada organizacao precisa ter seus dados isolados. Usamos um middleware para extrair o inquilino de cada requisicao. Para um guia aprofundado, leia nosso artigo sobre multi-tenancy com TypeORM e PostgreSQL.
// tenancy/inquilino.interceptor.ts
@Injectable()
export class InquilinoInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest();
const usuario = request.user;
if (usuario?.inquilinoId) {
request.inquilinoId = usuario.inquilinoId;
}
return next.handle();
}
}
Nos servicos, sempre filtramos por inquilino:
// projetos/projetos.service.ts
@Injectable()
export class ProjetosService {
constructor(
@InjectRepository(Projeto)
private readonly repo: Repository<Projeto>,
) {}
async listar(inquilinoId: string) {
return this.repo.find({
where: { inquilinoId },
order: { criadoEm: 'DESC' },
});
}
async criar(inquilinoId: string, dto: CriarProjetoDto) {
const projeto = this.repo.create({
...dto,
inquilinoId,
});
return this.repo.save(projeto);
}
async atualizar(
inquilinoId: string,
id: string,
dto: AtualizarProjetoDto,
) {
const projeto = await this.repo.findOne({
where: { id, inquilinoId },
});
if (!projeto) {
throw new NotFoundException('Projeto nao encontrado');
}
Object.assign(projeto, dto);
return this.repo.save(projeto);
}
}
Passo 6: Controle de acesso baseado em papeis
Criamos um decorator e guard para controlar permissoes. Para uma implementacao mais robusta, confira nosso guia de RBAC no NestJS.
// auth/decorators/papeis.decorator.ts
export const Papeis = (...papeis: string[]) =>
SetMetadata('papeis', papeis);
// auth/guards/papeis.guard.ts
@Injectable()
export class PapeisGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const papeisNecessarios = this.reflector
.getAllAndOverride<string[]>('papeis', [
context.getHandler(),
context.getClass(),
]);
if (!papeisNecessarios) return true;
const { user } = context.switchToHttp().getRequest();
return papeisNecessarios.includes(user.papel);
}
}
Exemplo de uso em um controller:
@Delete(':id')
@Papeis('admin')
@UseGuards(JwtAuthGuard, PapeisGuard)
remover(@Param('id') id: string) {
return this.projetosService.remover(id);
}
Passo 7: O painel Angular
No frontend, criamos um store de autenticacao com Angular Signals:
// auth/auth.store.ts
@Injectable({ providedIn: 'root' })
export class AuthStore {
private readonly http = inject(HttpClient);
private readonly router = inject(Router);
readonly usuario = signal<Usuario | null>(null);
readonly autenticado = computed(() => this.usuario() !== null);
readonly carregando = signal(false);
async entrar(email: string, senha: string) {
this.carregando.set(true);
try {
const resposta = await firstValueFrom(
this.http.post<AuthResposta>('/api/auth/entrar', {
email,
senha,
}),
);
localStorage.setItem('token', resposta.accessToken);
this.usuario.set(resposta.usuario);
this.router.navigate(['/painel']);
} finally {
this.carregando.set(false);
}
}
sair() {
localStorage.removeItem('token');
this.usuario.set(null);
this.router.navigate(['/auth/entrar']);
}
}
Configure as rotas com lazy loading:
// app.routes.ts
export const routes: Routes = [
{
path: 'auth',
loadChildren: () => import('./auth/auth.routes')
.then(m => m.AUTH_ROUTES),
},
{
path: 'painel',
canActivate: [authGuard],
loadChildren: () => import('./painel/painel.routes')
.then(m => m.PAINEL_ROUTES),
},
{ path: '', redirectTo: 'painel', pathMatch: 'full' },
];
Para construir um dashboard admin completo, confira nosso tutorial de dashboard admin com Angular.
Passo 8: Deploy com Docker
Crie um docker-compose.yml para producao. Temos tambem um guia completo de Docker Compose para NestJS e Angular.
services:
api:
build:
context: .
dockerfile: apps/api/Dockerfile
environment:
- DATABASE_URL=postgresql://usuario:senha@db:5432/saas
- JWT_SECRET=${JWT_SECRET}
ports:
- "3000:3000"
depends_on:
- db
painel:
build:
context: .
dockerfile: apps/painel/Dockerfile
ports:
- "80:80"
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
Para deploy na AWS com Terraform, confira nosso guia de deploy NestJS + Angular na AWS.
O proximo passo 💡
Este guia cobre os fundamentos, mas um SaaS em producao exige muito mais: integracao com Stripe para pagamentos, filas de tarefas com BullMQ, emails transacionais, logs de auditoria, rate limiting, conformidade com LGPD e testes automatizados.
SaaS Starter e um boilerplate NestJS + Angular que inclui tudo isso pronto para uso. A versao Lite esta disponivel gratuitamente no GitHub para voce comecar agora mesmo. Quer ver todas as funcionalidades em acao, incluindo multi-tenancy completo, integracao com Stripe e painel administrativo? Acesse a demo ao vivo em demo.cloudrix.io.
Eu uso esse stack para meus proprios projetos e ele me economizou literalmente meses de desenvolvimento. Invista seu tempo no que torna seu produto unico -- deixe a infraestrutura com um boilerplate testado e aprovado.
Pronto para comecar? Teste a demo | Veja os precos -- a versao Lite e totalmente gratuita.