NestJSとAngularでSaaSを構築する:完全ガイド2026
なぜNestJS + AngularでSaaSを構築するのか?
去年、私はクライアントのSaaSプロジェクトで認証、課金、マルチテナント対応をゼロから実装しました。結果は?4ヶ月かかって、やっとユーザーがログインして請求書を見られる段階まで到達。プロダクト固有の機能にはまだ一行もコードを書いていませんでした。その経験から学んだのは、SaaS開発で最も重要なのは「何を自分で書くか」ではなく「何を書かないか」を決めることです。
2026年、SaaS市場は急速に成長を続けています。日本のエンタープライズ市場でも、クラウドベースのソリューションへの需要が高まっています。NestJSとAngularの組み合わせは、エンタープライズグレードのSaaSアプリケーションを構築するための最も強力なスタックの一つです。
NestJSはTypeScriptベースのバックエンドフレームワークで、Angularにインスパイアされたモジュラーアーキテクチャを採用しています。AngularはGoogleが開発した堅牢なフロントエンドフレームワークで、大規模アプリケーションに最適です。
両フレームワークがTypeScriptを共有していることで、フロントエンドとバックエンド間でインターフェースや型定義を共有でき、開発効率が大幅に向上します。NestJS + Angularと他の技術スタックとの比較については、NestJS + Angular vs Next.jsの詳細比較をご覧ください。
アーキテクチャ設計
エンタープライズSaaSのアーキテクチャは、スケーラビリティと保守性を重視して設計する必要があります。
プロジェクト構造
saas-project/
├── apps/
│ ├── api/ # NestJS バックエンド
│ │ ├── src/
│ │ │ ├── auth/ # 認証モジュール
│ │ │ ├── billing/ # 課金モジュール
│ │ │ ├── tenants/ # テナント管理
│ │ │ └── users/ # ユーザー管理
│ ├── web/ # Angular フロントエンド
│ │ ├── src/
│ │ │ ├── app/
│ │ │ │ ├── auth/
│ │ │ │ ├── dashboard/
│ │ │ │ └── shared/
├── libs/ # 共有ライブラリ
│ ├── shared-types/ # 共有型定義
│ └── shared-utils/ # 共有ユーティリティ
マルチテナントアーキテクチャ
SaaSアプリケーションにおいて、マルチテナント対応は必須です。NestJSでは以下のようにテナント分離を実装します:
// tenant.middleware.ts
@Injectable()
export class TenantMiddleware implements NestMiddleware {
constructor(private readonly tenantService: TenantService) {}
async use(req: Request, res: Response, next: NextFunction) {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
throw new UnauthorizedException('テナントIDが必要です');
}
const tenant = await this.tenantService.findById(tenantId);
if (!tenant || !tenant.isActive) {
throw new UnauthorizedException('無効なテナントです');
}
req['tenant'] = tenant;
next();
}
}
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 →認証システムの構築
JWTベースの認証は、SaaSアプリケーションの基盤です。認証システムの詳細については、認証機能の概要もご参照ください。
バックエンド(NestJS)
// auth.service.ts
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
) {}
async login(loginDto: LoginDto) {
const user = await this.usersService.validateUser(
loginDto.email,
loginDto.password,
);
if (!user) {
throw new UnauthorizedException('メールアドレスまたはパスワードが正しくありません');
}
const payload = {
sub: user.id,
email: user.email,
tenantId: user.tenantId,
role: user.role,
};
return {
accessToken: this.jwtService.sign(payload),
refreshToken: this.jwtService.sign(payload, {
expiresIn: '7d',
}),
user: {
id: user.id,
email: user.email,
name: user.name,
},
};
}
}
フロントエンド(Angular)
// auth.service.ts
@Injectable({ providedIn: 'root' })
export class AuthService {
private readonly http = inject(HttpClient);
private readonly router = inject(Router);
private currentUser = signal<User | null>(null);
readonly isAuthenticated = computed(() => !!this.currentUser());
readonly user = this.currentUser.asReadonly();
login(credentials: LoginCredentials): Observable<AuthResponse> {
return this.http.post<AuthResponse>('/api/auth/login', credentials).pipe(
tap(response => {
localStorage.setItem('accessToken', response.accessToken);
this.currentUser.set(response.user);
this.router.navigate(['/dashboard']);
}),
);
}
}
サブスクリプション課金の実装
Stripe連携による課金システムを構築します。詳細なStripe実装ガイドはStripe Subscriptions完全ガイドをご覧ください。
このコードを手動で書く必要はありません。 SaaS Starterには、この認証・課金システムが55以上のテスト付きで含まれています。無料のLite版を試す →
Stripe連携による課金システムを構築します:
// 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'),
{ apiVersion: '2025-12-18.acacia' },
);
}
async createCheckoutSession(
tenantId: string,
priceId: string,
): Promise<Stripe.Checkout.Session> {
return this.stripe.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${this.configService.get('APP_URL')}/billing/success`,
cancel_url: `${this.configService.get('APP_URL')}/billing/cancel`,
metadata: { tenantId },
});
}
async handleWebhook(event: Stripe.Event): Promise<void> {
switch (event.type) {
case 'customer.subscription.created':
await this.activateSubscription(event.data.object);
break;
case 'customer.subscription.deleted':
await this.deactivateSubscription(event.data.object);
break;
case 'invoice.payment_failed':
await this.handlePaymentFailure(event.data.object);
break;
}
}
}
ロールベースアクセス制御(RBAC)
エンタープライズアプリケーションには細かなアクセス制御が必要です。マルチテナントアーキテクチャとRBACの詳細な実装パターンについては、NestJSマルチテナントアーキテクチャガイドをご参照ください。
// roles.guard.ts
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly 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.role === role);
}
}
// 使用例
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {
@Get('users')
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async getUsers() {
return this.usersService.findAll();
}
}
Angularダッシュボードの構築
管理ダッシュボードはSaaSの中核です。Angular Signalsを使った最新のアプローチで実装します:
// dashboard.component.ts
@Component({
selector: 'app-dashboard',
template: `
<div class="dashboard-grid">
<app-stats-card
title="月間収益"
[value]="stats()?.monthlyRevenue | currency:'JPY'"
[trend]="stats()?.revenueTrend"
/>
<app-stats-card
title="アクティブユーザー"
[value]="stats()?.activeUsers"
[trend]="stats()?.userTrend"
/>
<app-stats-card
title="解約率"
[value]="stats()?.churnRate + '%'"
[trend]="stats()?.churnTrend"
[invertTrend]="true"
/>
</div>
`,
})
export class DashboardComponent {
private readonly analyticsService = inject(AnalyticsService);
stats = toSignal(this.analyticsService.getDashboardStats());
}
デプロイメント
Docker Composeを使用した本番環境のデプロイ構成:
# docker-compose.prod.yml
services:
api:
build:
context: .
dockerfile: apps/api/Dockerfile
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/saas
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
web:
build:
context: .
dockerfile: apps/web/Dockerfile
ports:
- "443:443"
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
パフォーマンス最適化のヒント
- データベースインデックス: テナントIDに対するインデックスを必ず設定
- キャッシング: Redis を使用して頻繁にアクセスされるデータをキャッシュ
- 遅延読み込み: Angular のルートレベルで遅延読み込みを活用
- 接続プーリング: TypeORMの接続プール設定を最適化
- CDN: 静的アセットにCloudFrontなどのCDNを使用
ゼロから構築する vs ボイラープレートを使う
SaaSアプリケーションをゼロから構築する場合、認証、課金、マルチテナント対応、管理ダッシュボードなどの基盤機能だけで3〜6ヶ月かかることがあります。
SaaS Starterは、これらの機能をすべて含んだNestJS + Angularボイラープレートです。無料のLite版で基本機能を試すことができ、Pro版($149)、Business版($249)、Enterprise版($399)で完全な機能セットを利用できます。
主な特徴:
- JWT認証 + OAuth2連携
- Stripeサブスクリプション管理
- マルチテナントアーキテクチャ
- ロールベースアクセス制御
- Angular管理ダッシュボード
- Docker本番環境構成
- CI/CDパイプライン
まとめ:今すぐ始めましょう
NestJSとAngularの組み合わせは、エンタープライズグレードのSaaSアプリケーションに最適な技術スタックです。TypeScriptの型安全性、モジュラーアーキテクチャ、豊富なエコシステムにより、保守性の高い大規模アプリケーションを効率的に構築できます。
開発を加速させたい方は、SaaS Starterのライブデモをぜひお試しください。無料のLite版から始めて、プロジェクトの成長に合わせてアップグレードできます。
料金プラン:
- Lite(無料) — 基本アーキテクチャを体験
- Pro($149) — 認証 + 課金 + ダッシュボード
- Business($249) — マルチテナント + RBAC + 高度な機能
- Enterprise($399) — 全機能 + 優先サポート
料金プランの詳細を見る | ライブデモを試す | 無料Lite版を入手
関連記事: Angular管理ダッシュボードガイド | NestJS REST APIチュートリアル | AWSへのデプロイガイド