Xay dung SaaS voi NestJS va Angular: Huong dan day du 2026
Ban co muon xay dung mot san pham SaaS nhung khong biet bat dau tu dau? 🚀 Ban khong don doc. Hang ngan lap trinh vien Viet Nam dang doi mat voi cung mot thach thuc — mat hang tuan de xay dung authentication, billing va multi-tenancy truoc khi viet duoc dong code nao cho san pham that su.
Toi da trai qua hanh trinh nay, va trong bai viet nay toi se chia se tat ca nhung gi toi hoc duoc — giup ban tiet kiem hang thang lam viec.
Tai sao NestJS va Angular la lua chon tot nhat cho SaaS?
Thi truong SaaS tai Viet Nam dang phat trien manh me. Ngay cang nhieu startup va doanh nghiep Viet Nam xay dung cac san pham SaaS phuc vu ca thi truong noi dia va quoc te. De xay dung mot san pham SaaS chat luong, ban can mot nen tang ky thuat vung chac tu ngay dau.
NestJS va Angular la su ket hop ly tuong. NestJS mang den mot backend co cau truc ro rang voi module, dependency injection va ho tro san cho WebSockets, GraphQL va microservices. Angular cung cap mot frontend framework hoan chinh voi TypeScript manh me, state management phan ung va he sinh thai component truong thanh.
💡 NestJS hay Next.js? Doc bai so sanh chi tiet cua chung toi de hieu ro hon framework nao phu hop voi du an SaaS cua ban.
Ca hai deu su dung TypeScript, cho phep ban chia se kieu du lieu giua frontend va backend — giam thieu loi va tang toc do phat trien.
Buoc 1: Thiet lap Du an voi Nx Monorepo
Su dung Nx de quan ly du an monorepo. Day la cach tot nhat de to chuc code cho ung dung full-stack:
npx create-nx-workspace@latest my-saas --preset=apps
cd my-saas
nx g @nx/nest:application api
nx g @nx/angular:application dashboard
nx g @nx/js:library shared-types
Cau truc thu muc se nhu sau:
my-saas/
apps/
api/ # NestJS backend
dashboard/ # Angular frontend
libs/
shared-types/ # Kieu du lieu dung chung
Viec chia se kieu du lieu giua frontend va backend giup dam bao API contract luon dong bo. Khi ban thay doi mot interface, TypeScript se bao loi o nhung noi can cap nhat.
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 →Buoc 2: Xac thuc nguoi dung voi JWT
Xac thuc la nen tang cua moi ung dung SaaS. Trien khai dang nhap bang JWT voi refresh token:
// auth.service.ts (NestJS)
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async dangNhap(email: string, matKhau: string): Promise<TokenPair> {
const user = await this.usersService.findByEmail(email);
if (!user || !await bcrypt.compare(matKhau, user.passwordHash)) {
throw new UnauthorizedException('Email hoac mat khau khong dung');
}
return {
accessToken: this.jwtService.sign(
{ sub: user.id, orgId: user.organizationId, role: user.role },
{ expiresIn: '15m' },
),
refreshToken: this.jwtService.sign(
{ sub: user.id, type: 'refresh' },
{ expiresIn: '7d' },
),
};
}
}
Tren phia Angular, tao mot HTTP interceptor de tu dong dinh kem token vao moi request:
// auth.interceptor.ts (Angular)
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
const token = authService.getAccessToken();
if (token) {
req = req.clone({
setHeaders: { Authorization: `Bearer ${token}` },
});
}
return next(req).pipe(
catchError((error) => {
if (error.status === 401) {
return authService.lamMoiToken().pipe(
switchMap(() => next(req)),
);
}
return throwError(() => error);
}),
);
};
Nho them xac thuc hai yeu to (2FA) de tang cuong bao mat.
Buoc 3: Multi-Tenancy — Nen tang cua SaaS
Moi san pham SaaS deu can multi-tenancy. Khach hang cua ban chia se ha tang nhung du lieu cua ho phai duoc cach ly hoan toan. Trien khai row-level isolation voi TypeORM (doc them huong dan chi tiet ve multi-tenancy voi TypeORM):
// base-tenant.entity.ts
export abstract class BaseTenantEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
@Index()
tenantId: string;
@CreateDateColumn()
createdAt: Date;
}
// tenant-scoped.repository.ts
@Injectable({ scope: Scope.REQUEST })
export class TenantScopedRepository<T extends BaseTenantEntity> {
constructor(
private repo: Repository<T>,
@Inject(REQUEST) private request: Request,
) {}
private get tenantId(): string {
return this.request['user'].organizationId;
}
async timTatCa(): Promise<T[]> {
return this.repo.find({ where: { tenantId: this.tenantId } as any });
}
async taoMoi(data: Partial<T>): Promise<T> {
return this.repo.save({ ...data, tenantId: this.tenantId } as any);
}
async capNhat(id: string, data: Partial<T>): Promise<T> {
const entity = await this.repo.findOne({
where: { id, tenantId: this.tenantId } as any,
});
if (!entity) throw new NotFoundException('Khong tim thay du lieu');
Object.assign(entity, data);
return this.repo.save(entity);
}
}
Mau thiet ke nay dam bao rang moi truy van co so du lieu deu tu dong loc theo to chuc hien tai. Khong co nguy co ro ri du lieu giua cac tenant.
Buoc 4: Phan quyen theo vai tro (RBAC)
Dinh nghia cac vai tro va quyen han linh hoat. Tim hieu them ve RBAC trong NestJS:
// roles.enum.ts
export enum VaiTro {
CHU_SO_HUU = 'owner',
QUAN_TRI = 'admin',
THANH_VIEN = 'member',
NGUOI_XEM = 'viewer',
}
// roles.guard.ts
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<VaiTro[]>('roles', context.getHandler());
if (!requiredRoles) return true;
const { user } = context.switchToHttp().getRequest();
return requiredRoles.includes(user.role);
}
}
// Su dung trong controller
@Roles(VaiTro.QUAN_TRI, VaiTro.CHU_SO_HUU)
@UseGuards(AuthGuard, RolesGuard)
@Delete(':id')
async xoaNguoiDung(@Param('id') id: string) {
return this.usersService.remove(id);
}
Buoc 5: Tich hop thanh toan voi Stripe
Stripe ho tro thanh toan quoc te, rat phu hop cho SaaS Viet Nam nham thi truong toan cau. Trien khai checkout session va webhook:
// billing.service.ts
@Injectable()
export class BillingService {
constructor(private stripe: Stripe) {}
async taoPhienThanhToan(orgId: string, priceId: string) {
const org = await this.orgRepo.findOne({ where: { id: orgId } });
return this.stripe.checkout.sessions.create({
customer: org.stripeCustomerId,
mode: 'subscription',
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${this.appUrl}/billing?status=thanh-cong`,
cancel_url: `${this.appUrl}/billing?status=huy-bo`,
});
}
}
// webhook.controller.ts
@Post('webhooks/stripe')
@HttpCode(200)
async xuLyWebhook(
@Req() req: RawBodyRequest<Request>,
@Headers('stripe-signature') signature: string,
) {
const event = this.stripe.webhooks.constructEvent(
req.rawBody,
signature,
this.webhookSecret,
);
switch (event.type) {
case 'checkout.session.completed':
await this.billingService.kichHoatDangKy(event.data.object);
break;
case 'customer.subscription.deleted':
await this.billingService.huyDangKy(event.data.object);
break;
case 'invoice.payment_failed':
await this.billingService.xuLyThanhToanThatBai(event.data.object);
break;
}
}
Luu y quan trong: Webhook la nguon thong tin duy nhat dang tin cay ve trang thai thanh toan. Dung bao gio dua vao redirect URL tu checkout de cap nhat trang thai.
🔥 Ban co biet? Xay dung authentication, billing, multi-tenancy va deployment tu dau mat 8-14 tuan va $24,000-$84,000. Doc phan tich chi phi day du. Voi Cloudrix SaaS Starter, tat ca da san sang tu ngay dau tien — thu demo mien phi ngay! 🎯
Buoc 6: Xay dung Dashboard voi Angular
Tao giao dien dashboard phan hoi voi Angular Material:
@Component({
selector: 'app-tong-quan',
standalone: true,
imports: [MatCardModule, MatGridListModule],
template: `
<h2>Tong quan</h2>
<mat-grid-list cols="4" rowHeight="120px" gutterSize="16">
@for (kpi of duLieuKpi(); track kpi.nhan) {
<mat-grid-tile>
<mat-card class="kpi-card">
<mat-card-header>{{ kpi.nhan }}</mat-card-header>
<mat-card-content>
<span class="gia-tri">{{ kpi.giaTri }}</span>
<span [class.tang]="kpi.xuHuong > 0" [class.giam]="kpi.xuHuong < 0">
{{ kpi.xuHuong > 0 ? '+' : '' }}{{ kpi.xuHuong }}%
</span>
</mat-card-content>
</mat-card>
</mat-grid-tile>
}
</mat-grid-list>
`,
})
export class TongQuanComponent {
duLieuKpi = signal<KpiData[]>([]);
}
Buoc 7: Trien khai san pham
Su dung Docker Compose cho moi truong phat trien va AWS voi Terraform cho production:
services:
api:
build:
context: .
dockerfile: apps/api/Dockerfile
ports:
- '3000:3000'
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/saas
depends_on:
- db
dashboard:
build:
context: .
dockerfile: apps/dashboard/Dockerfile
ports:
- '4200:80'
db:
image: postgres:16
environment:
POSTGRES_DB: saas
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
Thoi gian dau tu thuc te
Xay dung tat ca tu dau mat bao lau? Day la uoc tinh thuc te:
| Tinh nang | Thoi gian |
|---|---|
| Thiet lap du an | 2 ngay |
| Xac thuc (JWT, OAuth, 2FA) | 5 ngay |
| Multi-tenancy | 4 ngay |
| Phan quyen RBAC | 3 ngay |
| Tich hop thanh toan | 4 ngay |
| Giao dien dashboard | 5 ngay |
| Trien khai CI/CD | 3 ngay |
| Tong cong | ~26 ngay lam viec |
Hon mot thang lam viec toan thoi gian — chua tinh loi, truong hop dac biet hay kiem tra bao mat.
Hoac: Bat dau voi SaaS Starter 🎯
SaaS Starter bao gom tat ca nhung gi o tren — xac thuc, multi-tenancy, RBAC, thanh toan Stripe, Angular dashboard va cau hinh trien khai — da duoc xay dung va kiem thu san.
Phien ban Lite mien phi cung cap toan bo kien truc NestJS + Angular de ban danh gia. Khong can thanh toan, khong gioi han thoi gian.
Muon so sanh voi cac boilerplate khac? Doc bai Cloudrix vs ShipFast vs Supastarter.
Xem ban demo truc tiep tai demo.cloudrix.io — dang nhap, tao to chuc, moi thanh vien, chuyen doi giua cac nhom. Tat ca nhung gi ban thay deu nam trong boilerplate.
Ngung xay dung ha tang — hay xay dung san pham. Clone repo va bat dau voi nhung tinh nang lam cho san pham cua ban khac biet. Phien ban Lite mien phi, hoac nhan Pro voi gia chi $249 mot lan tai day. Tiet kiem hon mot thang lam viec va tap trung vao dieu quan trong nhat! 🚀
Cong dong lap trinh vien Viet Nam dang phat trien nhanh chong. Dung de cong viec ha tang lam cham buoc tien cua ban.