Skip to content
← Back to blog
NestJSAngularSaaSРуководствоTypeScriptПолный стек

Создание SaaS с NestJS и Angular: Полное руководство 2026

Firas Sayah·July 18, 2026·10 min read

Вы тоже устали от бесконечных TODO-списков инфраструктурных задач, которые стоят между вами и запуском вашего SaaS-продукта? 🚀 Аутентификация, биллинг, мультитенантность, деплой — каждый из этих модулей может съесть недели работы. А ведь вы ещё не написали ни строчки бизнес-логики.

Я прошёл через это сам, и в этом руководстве расскажу, как построить production-ready SaaS максимально эффективно — и где можно сэкономить месяцы работы.

Современное рабочее пространство разработчика

Почему NestJS + Angular для SaaS?

Выбор технологического стека для SaaS-приложения — это решение, с которым вы будете жить годами. Неправильный выбор означает рефакторинг на пятом месяце, когда уже есть платящие клиенты и каждый час простоя стоит денег.

NestJS и Angular решают эту проблему через архитектурную зрелость. Оба фреймворка были спроектированы для масштабных приложений: модульная система, встроенный dependency injection, строгая типизация через TypeScript. Это не инструменты для прототипов — это инструменты для продуктов, которые будут расти.

💡 NestJS vs Next.js — что выбрать? Прочитайте наше подробное сравнение, чтобы понять, какой фреймворк лучше подходит для вашего SaaS.

Для русскоязычного рынка есть и практическое преимущество: архитектура NestJS очень похожа на Spring Boot, что упрощает переход для Java-разработчиков. Angular широко используется в enterprise-проектах в России и СНГ, поэтому найти опытных Angular-разработчиков проще, чем для менее распространённых фреймворков.

Архитектура проекта

SaaS Starter использует Nx monorepo для управления всеми компонентами:

saas-project/
  apps/
    api/          # NestJS бэкенд
    web/          # Angular фронтенд
    worker/       # Обработчик фоновых задач
  libs/
    shared/       # Общие DTO и интерфейсы
    database/     # TypeORM сущности и миграции
    auth/         # Модуль аутентификации
    billing/      # Интеграция с платёжными системами
    tenancy/      # Модуль мультитенантности
  infra/
    terraform/    # Инфраструктура как код
    docker/       # Docker-конфигурации

Монорепозиторий позволяет разделять типы и DTO между фронтендом и бэкендом без дублирования кода.

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 →

Аутентификация

Полноценная система аутентификации для SaaS включает JWT-токены, OAuth через социальные сети, двухфакторную аутентификацию (2FA) и управление ролями (RBAC).

JWT-аутентификация

// libs/auth/src/jwt.strategy.ts
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(
    private readonly configService: ConfigService,
    private readonly usersService: UsersService,
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: configService.get('JWT_SECRET'),
    });
  }

  async validate(payload: JwtPayload): Promise<User> {
    const user = await this.usersService.findById(payload.sub);
    if (!user || !user.isActive) {
      throw new UnauthorizedException('Пользователь не найден или заблокирован');
    }
    return user;
  }
}

OAuth — социальные сети

// apps/api/src/auth/auth.controller.ts
@Controller('auth')
export class AuthController {
  @Get('google')
  @UseGuards(AuthGuard('google'))
  googleLogin() {}

  @Get('google/callback')
  @UseGuards(AuthGuard('google'))
  async googleCallback(@Req() req: Request): Promise<AuthResponse> {
    return this.authService.loginWithOAuth(req.user);
  }

  @Get('github')
  @UseGuards(AuthGuard('github'))
  githubLogin() {}

  @Get('github/callback')
  @UseGuards(AuthGuard('github'))
  async githubCallback(@Req() req: Request): Promise<AuthResponse> {
    return this.authService.loginWithOAuth(req.user);
  }
}

Двухфакторная аутентификация

// libs/auth/src/two-factor.service.ts
@Injectable()
export class TwoFactorService {
  generateSecret(user: User): TwoFactorSecret {
    const secret = authenticator.generateSecret();
    const otpauthUrl = authenticator.keyuri(
      user.email,
      'SaaS Starter',
      secret,
    );
    return { secret, otpauthUrl };
  }

  verifyToken(token: string, secret: string): boolean {
    return authenticator.verify({ token, secret });
  }
}

Управление ролями (RBAC)

// libs/auth/src/guards/roles.guard.ts
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<Role[]>(
      ROLES_KEY,
      [context.getHandler(), context.getClass()],
    );

    if (!requiredRoles) return true;

    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.some((role) => user.roles?.includes(role));
  }
}

// Использование
@Controller('settings')
export class SettingsController {
  @Patch('billing')
  @Roles(Role.Admin, Role.BillingManager)
  @UseGuards(JwtAuthGuard, RolesGuard)
  updateBilling(@Body() dto: UpdateBillingDto) {
    return this.settingsService.updateBilling(dto);
  }
}

Облачная инфраструктура и серверы

Мультитенантность

Мультитенантность — фундамент любого SaaS. SaaS Starter использует изоляцию на уровне PostgreSQL-схем: каждый клиент получает собственную схему в базе данных. Подробнее о паттернах мультитенантности читайте в нашем руководстве по multi-tenancy с TypeORM.

// libs/tenancy/src/tenant.middleware.ts
@Injectable()
export class TenantMiddleware implements NestMiddleware {
  constructor(private readonly dataSource: DataSource) {}

  async use(req: Request, res: Response, next: NextFunction) {
    const tenantId = this.resolveTenant(req);

    if (!tenantId) {
      throw new BadRequestException('Не указан идентификатор организации');
    }

    // Переключаемся на схему текущего тенанта
    await this.dataSource.query(
      `SET search_path TO "tenant_${tenantId}", public`
    );

    req['tenantId'] = tenantId;
    next();
  }

  private resolveTenant(req: Request): string | null {
    // Поддержка через поддомен: company.yourapp.com
    const host = req.headers.host;
    const subdomain = host?.split('.')[0];
    if (subdomain && subdomain !== 'www' && subdomain !== 'api') {
      return subdomain;
    }
    // Или через заголовок запроса
    return req.headers['x-tenant-id'] as string;
  }
}

Регистрация нового клиента

@Injectable()
export class TenantService {
  async provision(dto: CreateTenantDto): Promise<Tenant> {
    const tenantId = slugify(dto.companyName);
    const schema = `tenant_${tenantId}`;

    // Создаём схему
    await this.dataSource.query(`CREATE SCHEMA IF NOT EXISTS "${schema}"`);

    // Применяем миграции
    await this.runMigrations(schema);

    // Создаём администратора
    await this.dataSource.query(`SET search_path TO "${schema}"`);
    await this.usersService.create({
      email: dto.adminEmail,
      password: dto.adminPassword,
      role: Role.Admin,
    });

    // Создаём запись о тенанте в public-схеме
    return this.tenantsRepository.save({
      id: tenantId,
      name: dto.companyName,
      schema,
      plan: 'trial',
    });
  }
}

Интеграция с платёжными системами

Stripe для международных платежей

Подробное руководство по интеграции Stripe с подписками доступно отдельно.

// libs/billing/src/stripe.service.ts
@Injectable()
export class StripeService {
  private stripe: Stripe;

  constructor(private configService: ConfigService) {
    this.stripe = new Stripe(configService.get('STRIPE_SECRET_KEY'));
  }

  async createSubscription(customerId: string, priceId: string) {
    return this.stripe.subscriptions.create({
      customer: customerId,
      items: [{ price: priceId }],
      payment_behavior: 'default_incomplete',
      expand: ['latest_invoice.payment_intent'],
    });
  }

  async handleWebhook(payload: Buffer, signature: string) {
    const event = this.stripe.webhooks.constructEvent(
      payload,
      signature,
      this.configService.get('STRIPE_WEBHOOK_SECRET'),
    );

    switch (event.type) {
      case 'customer.subscription.updated':
        await this.updateSubscriptionStatus(event.data.object);
        break;
      case 'customer.subscription.deleted':
        await this.handleCancellation(event.data.object);
        break;
      case 'invoice.payment_failed':
        await this.handlePaymentFailure(event.data.object);
        break;
    }
  }
}

🔥 Знаете ли вы? Создание аутентификации, биллинга, мультитенантности и деплоя с нуля обойдётся в 8-14 недель и $24,000-$84,000. Читайте полный анализ стоимости. С Cloudrix SaaS Starter всё это готово к использованию с первого дня — попробуйте бесплатное демо! 🎯

Фоновые задачи с BullMQ

Отправка писем, генерация отчётов и обработка вебхуков не должны блокировать HTTP-запросы. BullMQ с Redis обеспечивает надёжную очередь задач.

// apps/worker/src/processors/email.processor.ts
@Processor('emails')
export class EmailProcessor {
  private readonly logger = new Logger(EmailProcessor.name);

  @Process('welcome')
  async sendWelcome(job: Job<{ userId: string; tenantId: string }>) {
    this.logger.log(`Отправка приветственного письма: ${job.data.userId}`);

    await this.dataSource.query(
      `SET search_path TO "tenant_${job.data.tenantId}"`
    );

    const user = await this.usersService.findById(job.data.userId);

    await this.emailService.send({
      to: user.email,
      subject: 'Добро пожаловать!',
      template: 'welcome',
      context: {
        name: user.displayName,
        loginUrl: `https://${job.data.tenantId}.yourapp.com/login`,
      },
    });
  }

  @Process('invoice')
  async sendInvoice(job: Job<InvoiceJobData>) {
    this.logger.log(`Генерация счёта для тенанта: ${job.data.tenantId}`);
    const pdf = await this.invoiceService.generatePdf(job.data);
    await this.emailService.sendWithAttachment({
      to: job.data.email,
      subject: `Счёт за ${job.data.period}`,
      attachment: pdf,
    });
  }
}

Angular: Фронтенд

Управление состоянием через Signals

Подробнее об Angular Signals для управления состоянием.

// apps/web/src/app/core/auth.store.ts
@Injectable({ providedIn: 'root' })
export class AuthStore {
  private readonly http = inject(HttpClient);

  currentUser = signal<User | null>(null);
  isAuthenticated = computed(() => !!this.currentUser());
  currentTenant = signal<string | null>(null);

  async login(credentials: LoginDto): Promise<void> {
    const response = await firstValueFrom(
      this.http.post<AuthResponse>('/api/auth/login', credentials)
    );
    localStorage.setItem('token', response.accessToken);
    this.currentUser.set(response.user);
    this.currentTenant.set(response.tenantId);
  }
}

Защита маршрутов

// apps/web/src/app/core/guards/auth.guard.ts
export const authGuard: CanActivateFn = (route, state) => {
  const authStore = inject(AuthStore);
  const router = inject(Router);

  if (authStore.isAuthenticated()) {
    return true;
  }

  return router.createUrlTree(['/login'], {
    queryParams: { returnUrl: state.url },
  });
};

// Маршруты
export const routes: Routes = [
  { path: 'login', component: LoginComponent },
  {
    path: 'dashboard',
    canActivate: [authGuard],
    loadChildren: () => import('./dashboard/routes'),
  },
];

Панель управления с аналитикой

Деплой в продакшен

SaaS Starter включает полную конфигурацию Docker и Terraform для AWS:

# Локальная разработка
docker-compose up -d

# Деплой на AWS
cd infra/terraform/environments/production
terraform init
terraform plan
terraform apply

Продакшен-архитектура:

  • ECS Fargate — контейнеры NestJS API с автомасштабированием
  • S3 + CloudFront — глобальная CDN для Angular-приложения
  • RDS PostgreSQL — управляемая база данных с бекапами
  • ElastiCache Redis — кеш и очереди BullMQ
  • CloudWatch — мониторинг и алертинг

Начните прямо сейчас 🎯

Весь код, описанный в этом руководстве, уже реализован в SaaS Starter. Вам не нужно собирать всё с нуля.

  1. Откройте demo.cloudrix.io и попробуйте бесплатную версию
  2. Клонируйте репозиторий и запустите docker-compose up
  3. За несколько минут у вас будет рабочий каркас SaaS-приложения

Бесплатная lite-версия включает аутентификацию, базовую мультитенантность и UI-компоненты. Профессиональная версия добавляет Terraform-деплой, интеграцию со Stripe, расширенный RBAC, аудит-логирование и систему фоновых задач.

Хотите сравнить с другими boilerplate? Прочитайте наше сравнение Cloudrix vs ShipFast vs Supastarter.

Хватит строить инфраструктуру — стройте продукт. Попробуйте Cloudrix SaaS Starter — начните с бесплатной версии, или получите Pro за единоразовый платёж $249 здесь. Сэкономьте 2-3 месяца и сфокусируйтесь на том, что делает ваш продукт уникальным! 🚀

F

Firas Sayah

Senior Software Engineer

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