Skip to content
← Back to blog
NestJSAngularSaaSTutorialPortuguesTypeScript

Como Criar um SaaS com NestJS e Angular: Guia Completo 2026

Firas Sayah·July 18, 2026·10 min read

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.

Desenvolvedor trabalhando em codigo TypeScript

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: true em 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;
}

Design de banco de dados com PostgreSQL

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.

Deploy em nuvem com Docker e 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.

F

Firas Sayah

Senior Software Engineer

Full-stack developer with 5+ years building production SaaS applications with NestJS and Angular.