Building SaaS with Svelar
A complete guide to building production SaaS applications with Svelar. Covers what you get out of the box, how to extend it, multi-tenancy, billing, deployment, and scaling.
What You Get
Running npx @beeblock/svelar new my-saas scaffolds a fully working SaaS application:
npx @beeblock/svelar new my-saas
cd my-saas
npm run dev
The new command generates .env with secure random APP_KEY and INTERNAL_SECRET, installs dependencies, runs all migrations, and seeds the database automatically. You get:
Authentication (ready to use)
/register— Account creation with form validation (superforms + Zod)/login— Session-based login with CSRF protection/logout— Session invalidation and redirect- API endpoints:
POST /api/auth/register,POST /api/auth/login,POST /api/auth/logout,GET /api/auth/me DatabaseSessionStorefor persistent sessions across restarts- Gates for authorization (
admin-access,edit-post,delete-post,manage-users)
User Dashboard
/dashboard— Stats overview (API keys, teams, account role)/dashboard/api-keys— Create, list, copy, and revoke API keys/dashboard/team— Team management with invitations, role assignment, member removal/dashboard/billing— Subscription management, cancel/resume, payment method (Stripe Portal), invoice history- Auth guard — unauthenticated users redirected to
/login
Admin Panel
/admin— User management with inline role changes, user deletion- Billing tab — list all subscriptions, cancel, issue refunds
- Roles and permissions display with seeded data
- Admin guard — non-admin users redirected to
/dashboard - Admin API routes for full CRUD on users, roles, permissions, role-permissions, user-roles, user-permissions, billing
- Run
npx svelar make:dashboardfor the full monitoring UI (queue, scheduler, logs, system health)
API Routes (16 endpoints)
- Auth: register, login, logout, me
- Posts: list + create, single CRUD (GET/PUT/DELETE), my posts
- Admin: users, roles, permissions, role-permissions, user-roles, user-permissions
- Health check:
GET /api/health - Broadcasting: SSE channel subscription
- Internal broadcast bridge (scheduler <-> web server)
Background Processing
- 3 jobs:
SendWelcomeEmail,DailyDigestJob,ExportDataJob - 5 scheduled tasks:
CleanupExpiredTokens,CleanExpiredSessions,DailyDigestEmail,PruneAuditLogs,QueueHealthCheck - Run scheduler:
npx svelar schedule:run
Domain Layer (DDD)
- Models:
User(withHasRolesmixin),Post - Repositories:
UserRepository,PostRepository - Services:
AuthService,PostService - Controllers:
AuthController,PostController,AdminController - DTOs:
RegisterRequest,LoginRequest,CreatePostRequest,UpdatePostRequest - Actions:
RegisterUserAction,CreatePostAction - Schemas:
auth.ts,post.ts(Zod validation)
Stripe Billing (opt-in)
- Install
stripe, uncommentStripe.configure()inapp.ts, add env vars - Subscriptions, checkout sessions, customer portal, invoices, refunds
- Webhook handler at
/api/webhooks/stripewith signature verification - See Stripe Billing for the full setup guide
Database
- 12 migrations: users, posts, permissions, role column, sessions, audit logs, notifications, failed jobs, stripe customer ID, subscription plans, subscriptions, invoices
DatabaseSeederwith admin user, demo user, roles, permissions, sample posts- Default accounts:
admin@svelar.dev/admin123,demo@svelar.dev/password
Infrastructure
- Full
app.tsbootstrap: Database, Hash, Auth, Queue, Audit, ApiKeys, Webhooks, Teams, Uploads, EmailTemplates, Broadcast, Dashboard, Stripe (commented out — uncomment to enable) - Tailwind CSS v4 with brand theme tokens
- Auth-aware navigation layout
- Error page with status-specific messaging
Project Structure
my-saas/
├── src/
│ ├── app.ts # Full service bootstrap
│ ├── app.css # Tailwind v4 + @theme
│ ├── hooks.server.ts # Middleware pipeline
│ ├── lib/
│ │ ├── models/ # User.ts, Post.ts
│ │ ├── repositories/ # UserRepository.ts, PostRepository.ts
│ │ ├── services/ # AuthService.ts, PostService.ts
│ │ ├── controllers/ # AuthController.ts, PostController.ts, AdminController.ts
│ │ ├── dtos/ # RegisterRequest.ts, LoginRequest.ts, etc.
│ │ ├── actions/ # RegisterUserAction.ts, CreatePostAction.ts
│ │ ├── auth/ # gates.ts
│ │ ├── schemas/ # auth.ts, post.ts (Zod)
│ │ ├── jobs/ # SendWelcomeEmail.ts, DailyDigestJob.ts, ExportDataJob.ts
│ │ ├── scheduler/ # CleanupExpiredTokens.ts, DailyDigestEmail.ts, etc.
│ │ ├── database/
│ │ │ ├── migrations/ # 12 migration files
│ │ │ └── seeders/ # DatabaseSeeder.ts
│ │ └── shared/
│ │ └── providers/ # EventServiceProvider.ts
│ └── routes/
│ ├── +layout.svelte # Auth-aware nav
│ ├── +layout.server.ts # Pass user to layout
│ ├── +error.svelte # Error page
│ ├── +page.svelte # Landing page
│ ├── login/ # Login page
│ ├── register/ # Registration page
│ ├── logout/ # Logout action
│ ├── dashboard/ # User dashboard
│ │ ├── api-keys/ # API key management
│ │ ├── team/ # Team management
│ │ └── billing/ # Subscription & invoices
│ ├── admin/ # Admin panel
│ └── api/
│ ├── health/ # Health check
│ ├── auth/ # Auth endpoints
│ ├── posts/ # Posts CRUD
│ ├── broadcasting/[channel]/ # SSE
│ ├── internal/broadcast/ # Scheduler bridge
│ ├── webhooks/stripe/ # Stripe webhook
│ └── admin/ # Admin API (users, roles, permissions, billing)
├── storage/ # logs, cache, uploads, sessions
├── .env.example
├── svelar.database.json
└── vite.config.ts
Extending the Scaffold
Adding a New Domain Module
Follow the DDD pattern established by the scaffold:
# Generate the files
npx svelar make:model Invoice
npx svelar make:migration create_invoices_table
npx svelar make:controller InvoiceController
npx svelar make:job ProcessInvoiceJob
Then wire it up:
// src/lib/models/Invoice.ts
import { Model } from '@beeblock/svelar/orm';
export class Invoice extends Model {
static table = 'invoices';
static timestamps = true;
static fillable = ['user_id', 'amount', 'status', 'due_date'];
declare id: number;
declare user_id: number;
declare amount: number;
declare status: 'pending' | 'paid' | 'overdue';
declare due_date: Date;
declare created_at: Date;
declare updated_at: Date;
user() {
return this.belongsTo(User, 'user_id');
}
}
import { User } from './User.ts';
// src/lib/services/InvoiceService.ts
import { CrudService } from '@beeblock/svelar/services';
import { Repository } from '@beeblock/svelar/repositories';
import { Invoice } from '../models/Invoice.js';
class InvoiceRepository extends Repository<Invoice> {
model() { return Invoice; }
async findOverdue(): Promise<Invoice[]> {
return this.query()
.where('status', 'pending')
.where('due_date', '<', new Date())
.get();
}
}
const repo = new InvoiceRepository();
export class InvoiceService extends CrudService<Invoice> {
protected repository() { return repo; }
async findOverdue() { return repo.findOverdue(); }
}
Adding Scheduled Tasks
Create a new file in src/lib/scheduler/ with a default export:
// src/lib/scheduler/MarkOverdueInvoices.ts
import { ScheduledTask } from '@beeblock/svelar/scheduler';
export default class MarkOverdueInvoices extends ScheduledTask {
name = 'mark-overdue-invoices';
schedule() {
return this.dailyAt('06:00');
}
async handle(): Promise<void> {
const { InvoiceService } = await import('../services/InvoiceService.ts');
const service = new InvoiceService();
const overdue = await service.findOverdue();
for (const invoice of overdue) {
invoice.status = 'overdue';
await invoice.save();
}
}
}
Register the task in src/lib/shared/scheduler/index.ts for DDD apps or src/lib/scheduler/index.ts for flat apps:
import { Scheduler } from '@beeblock/svelar/scheduler';
import MarkOverdueInvoices from './MarkOverdueInvoices.js';
export function createScheduler(): Scheduler {
const scheduler = new Scheduler().persistToDatabase();
scheduler.register(new MarkOverdueInvoices());
return scheduler;
}
Run it with:
npx svelar schedule:run
See Scheduler for all scheduling expressions (everyMinute(), hourly(), weekly(), cron(), etc.).
Adding Background Jobs
// src/lib/jobs/ProcessInvoiceJob.ts
import { Job } from '@beeblock/svelar/queue';
export class ProcessInvoiceJob extends Job {
maxAttempts = 3;
retryDelay = 60;
constructor(private invoiceId: number) {
super();
}
async handle(): Promise<void> {
const { Invoice } = await import('../models/Invoice.ts');
const invoice = await Invoice.find(this.invoiceId);
if (!invoice) return;
// Process payment, send receipt, etc.
invoice.status = 'paid';
await invoice.save();
}
failed(error: Error): void {
console.error(`Failed to process invoice #${this.invoiceId}:`, error.message);
}
serialize() { return { invoiceId: this.invoiceId }; }
static restore(data: Record<string, unknown>) {
return new ProcessInvoiceJob(data.invoiceId as number);
}
}
Register in src/app.ts:
import { ProcessInvoiceJob } from './lib/jobs/ProcessInvoiceJob.js';
Queue.registerAll([SendWelcomeEmail, DailyDigestJob, ExportDataJob, ProcessInvoiceJob]);
Dispatch from anywhere:
import { Queue } from '@beeblock/svelar/queue';
import { ProcessInvoiceJob } from '$lib/jobs/ProcessInvoiceJob.js';
await Queue.dispatch(new ProcessInvoiceJob(invoice.id));
See Queue & Jobs for queue drivers, workers, and monitoring.
Adding API Routes
Follow the controller pattern from the scaffold:
// src/lib/controllers/InvoiceController.ts
import { Controller } from '@beeblock/svelar/routing';
import { InvoiceService } from '../services/InvoiceService.js';
const invoiceService = new InvoiceService();
export class InvoiceController extends Controller {
async index(event: any) {
const userId = event.locals.user?.id;
if (!userId) return this.json({ message: 'Unauthenticated' }, 401);
const invoices = await invoiceService.findAll();
return this.json(invoices);
}
async store(event: any) {
// validate, create, return
}
}
// src/routes/api/invoices/+server.ts
import { InvoiceController } from '$lib/controllers/InvoiceController.js';
const ctrl = new InvoiceController();
export const GET = ctrl.handle('index');
export const POST = ctrl.handle('store');
Adding Events & Listeners
Wire up cross-module communication in EventServiceProvider:
// src/lib/shared/providers/EventServiceProvider.ts
import { EventServiceProvider as BaseProvider } from '@beeblock/svelar/events';
export class EventServiceProvider extends BaseProvider {
protected listen = {
'invoice:paid': [SendInvoiceReceipt, UpdateAccountBalance],
'user:registered': [SendWelcomeEmailListener],
};
protected observers = {};
protected subscribe = [];
}
Emit events from services:
// Inside a service method
await this.emit({ type: 'invoice:paid', invoice });
See Additional Features for the full event system.
Multi-Tenancy with Teams
Svelar's built-in Teams system handles multi-tenancy. It's configured out of the box in app.ts:
import { Teams } from '@beeblock/svelar/teams';
Teams.configure({
driver: 'database',
roles: ['owner', 'admin', 'member', 'viewer'],
maxTeamsPerUser: 10,
});
Team Operations
import { Teams } from '@beeblock/svelar/teams';
// Create a team
const team = await Teams.create({
name: "Acme Corp",
ownerId: user.id,
personalTeam: false,
});
// Invite members
await Teams.invite(team.id, 'colleague@example.com', 'admin');
// Get team members
const members = await Teams.getMembers(team.id);
// Update member role
await Teams.updateMemberRole(team.id, memberId, 'admin');
// Remove member
await Teams.removeMember(team.id, memberId);
// Get user's teams
const teams = await Teams.getUserTeams(user.id);
Team slugs are made unique automatically. Roles are validated against the configured role list, invitation tokens can only be accepted once, and the owner role is reserved for the team owner.
Team-Scoped Queries
Filter data by team in your repositories:
export class ProjectRepository extends Repository<Project> {
model() { return Project; }
async findByTeam(teamId: string): Promise<Project[]> {
return this.query().where('team_id', teamId).orderBy('created_at', 'desc').get();
}
}
The dashboard team page at /dashboard/team provides a complete UI for team management, invitations, and member roles.
API Keys
API keys are configured in app.ts and managed via the /dashboard/api-keys page:
import { ApiKeys } from '@beeblock/svelar/api-keys';
ApiKeys.configure({ driver: 'database', prefix: 'sk_' });
Programmatic Usage
import { ApiKeys } from '@beeblock/svelar/api-keys';
// Create a key
const { plainTextKey, record } = await ApiKeys.create({
name: 'Production Key',
userId: user.id,
permissions: ['read', 'write'],
});
// Empty permission arrays are deny-by-default.
// Use '*' only for deliberately unrestricted machine tokens.
// List user's keys
const keys = await ApiKeys.listForUser(user.id);
// Revoke a key
await ApiKeys.revoke(keyId);
Users authenticate API requests with:
Authorization: Bearer sk_your_key_here
Permissions & Roles
The scaffold includes a full Spatie-inspired permission system with seeded roles (admin, editor, user) and permissions (manage-users, manage-roles, manage-posts, create-posts, view-dashboard, view-admin).
Using Permissions
import { Permissions } from '@beeblock/svelar/permissions';
// Create roles and permissions
const role = await Permissions.createRole({ name: 'editor', description: 'Can manage posts' });
const perm = await Permissions.createPermission({ name: 'publish-posts' });
// Assign permissions to roles
await Permissions.giveRolePermission(role.id, perm.id);
// Assign roles to users
await Permissions.assignRole('User', user.id, role.id);
// Check permissions on User model (HasRoles mixin)
const user = await User.find(userId);
await user.hasRole('admin'); // true/false
await user.hasPermission('manage-users'); // true/false
await user.can('manage-users'); // Laravel-style authorization helper
Authorization Gates
Gates defined in src/lib/auth/gates.ts provide inline authorization:
import { Gate } from '@beeblock/svelar/auth';
Gate.define('admin-access', (user) => user?.role === 'admin');
Gate.define('edit-post', (user, post) => {
if (!user) return false;
return user.id === post.user_id || user.role === 'admin';
});
// Super user bypass — admins pass all gates
Gate.defineSuperUser((user) => user?.role === 'admin');
Authorization Policies
Use policies for model-specific authorization. Register each policy against its model name, then pass a model class/name for class-level checks or a model instance for instance-level checks:
import { Gate, Policy } from '@beeblock/svelar/auth';
import { Post } from '$lib/server/models/Post';
class PostPolicy extends Policy {
create(user: any) {
return user.role === 'editor' || user.role === 'admin';
}
update(user: any, post: Post) {
return user.id === post.user_id || user.role === 'admin';
}
}
Gate.policy('Post', new PostPolicy());
await Gate.forUser(user).allows('create', Post);
await Gate.forUser(user).authorize('update', post);
Admin API Routes
The scaffold includes 6 admin API routes for managing the permission system:
| Endpoint | Methods | Description |
|---|---|---|
/api/admin/users |
GET, PUT, DELETE | List, update role, delete users |
/api/admin/roles |
POST, DELETE | Create, delete roles |
/api/admin/permissions |
POST, DELETE | Create, delete permissions |
/api/admin/role-permissions |
POST, DELETE | Attach/detach permissions from roles |
/api/admin/user-roles |
POST, DELETE | Assign/remove roles from users |
/api/admin/user-permissions |
POST, DELETE | Grant/revoke direct user permissions |
All admin routes require role === 'admin'.
Broadcasting & Real-Time Updates
SSE-based broadcasting is configured out of the box:
import { Broadcast } from '@beeblock/svelar/broadcasting';
Broadcast.configure({
default: 'sse',
drivers: { sse: { driver: 'sse' } },
});
Server-Side Broadcasting
// Send events to connected clients
await Broadcast.to('notifications').send('toast', {
variant: 'success',
title: 'Invoice paid',
description: 'Invoice #1234 has been processed',
});
Client-Side Subscription
import { useSSE } from '@beeblock/svelar/broadcasting/client';
const channel = useSSE('notifications');
channel.listen('toast', (data) => {
// Show toast notification
});
// Cleanup
channel.close();
Scheduler HTTP Bridge
The scheduler runs in a separate process and can't access in-memory SSE channels. It uses the internal HTTP bridge at /api/internal/broadcast:
// From a scheduled task
const res = await fetch('http://localhost:5173/api/internal/broadcast', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Internal-Secret': process.env.INTERNAL_SECRET!,
},
body: JSON.stringify({ channel: 'notifications', eventName: 'toast', data: { title: 'Update' } }),
});
CSRF is excluded for /api/internal/ paths.
Webhooks
Send and receive webhooks:
import { Webhooks } from '@beeblock/svelar/webhooks';
Webhooks.configure({ driver: 'database', maxAttempts: 5 });
// Register a webhook endpoint
await Webhooks.register({
url: 'https://example.com/webhook',
events: ['invoice.paid', 'user.created'],
active: true,
});
// Dispatch a webhook event
await Webhooks.dispatch('invoice.paid', { invoiceId: 123, amount: 99.99 });
Stripe Billing
Stripe billing is available as the @beeblock/svelar-stripe plugin. Set it up in three steps:
1. Install & Configure
npm install stripe
// src/app.ts — uncomment the Stripe block
import { Stripe } from '@beeblock/svelar-stripe';
Stripe.configure({
secretKey: process.env.STRIPE_SECRET_KEY ?? '',
publishableKey: process.env.STRIPE_PUBLISHABLE_KEY ?? '',
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET ?? '',
currency: 'usd',
});
2. Create Products in Stripe Dashboard
Go to Stripe Products, create your plans (e.g., Free, Pro at $29/mo, Enterprise at $99/mo), and copy each Price ID (price_xxx).
3. Handle Webhooks
Register event handlers in app.ts:
Stripe.webhooks()
.on('customer.subscription.created', async (event) => {
// Sync subscription to your database
})
.on('invoice.payment_failed', async (event) => {
// Notify user about failed payment
});
The webhook endpoint at /api/webhooks/stripe is already scaffolded with signature verification.
For the full guide including checkout sessions, customer portal, refunds, subscription management, and testing — see Stripe Billing.
Audit Logging
Track user actions:
import { Audit } from '@beeblock/svelar/audit';
Audit.configure({ driver: 'database', enabled: true });
// Log an action
await Audit.log({
action: 'invoice.created',
modelType: 'Invoice',
modelId: invoice.id,
userId: user.id,
changes: { status: 'pending' },
});
// Query audit trail
const entries = await Audit.query({ limit: 50 });
Email Templates
import { EmailTemplates } from '@beeblock/svelar/email-templates';
EmailTemplates.configure({ driver: 'database' });
EmailTemplates.registerDefaults(); // registers welcome, reset-password, etc.
Feature Flags
Database-backed feature flags with per-user, per-team, and percentage rollout support:
import { Features } from '@beeblock/svelar/feature-flags';
Features.configure({ driver: 'database' });
// Define flags at startup (in app.ts)
await Features.define('new-dashboard', { description: 'Redesigned UI' });
await Features.define('beta-api', { description: 'API v2', percentage: 20 });
// Check flags in routes
if (await Features.enabledFor('beta-api', user.id)) {
// This user gets the beta experience
}
// Per-team flags (for enterprise features)
if (await Features.enabledForTeam('enterprise-sso', teamId)) {
// This team has SSO
}
// Admin controls
await Features.enable('new-dashboard'); // Turn on globally
await Features.enableFor('beta-api', userId); // Force on for a user
await Features.disableForTeam('beta-api', teamId); // Force off for a team
Tables (feature_flags, feature_flag_overrides) are managed by Svelar core migrations.
See Feature Flags for the full guide including percentage rollouts, admin APIs, and Svelte page integration.
Excel Import/Export
Export data for your users or import bulk records:
import { Excel } from '@beeblock/svelar/excel';
// Export endpoint
export async function GET() {
const users = await User.query().all();
const buffer = await Excel.export({
sheets: [{
name: 'Users',
columns: [
{ header: 'Name', key: 'name', width: 30 },
{ header: 'Email', key: 'email', width: 40 },
],
rows: users,
}],
});
return new Response(buffer, {
headers: {
'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'Content-Disposition': 'attachment; filename="users.xlsx"',
},
});
}
For large datasets, use Excel.stream() with async generators to keep memory low. See Excel for streaming examples.
File Uploads
import { Uploads } from '@beeblock/svelar/uploads';
Uploads.configure({ driver: 'database', maxFileSize: 10 * 1024 * 1024 }); // 10MB
Production Checklist
Database
- Switch from SQLite to PostgreSQL: update
svelar.database.jsonand.env - Set
DB_HOST,DB_PORT,DB_NAME,DB_USER,DB_PASSWORD - Run migrations:
npx svelar migrate - Set up automated daily backups
Security
- Set a strong
APP_KEY(random 32+ char string) - Set
INTERNAL_SECRETfor the broadcast bridge - Enable HTTPS with TLS certificate
- Set
NODE_ENV=production - Use environment secrets (not
.envfile in production) - Configure CORS for your domain
Stripe (if using billing)
- Switch to live mode keys (
sk_live_,pk_live_) - Set
STRIPE_WEBHOOK_SECRETfor production webhook endpoint - Register webhook URL in Stripe Dashboard:
https://yourdomain.com/api/webhooks/stripe - Test the webhook with
stripe trigger customer.subscription.created
Sessions & Cache
- Already using
DatabaseSessionStore(persistent across restarts) - For Redis sessions/cache: install Redis, set
CACHE_DRIVER=redisin.env
Queue & Scheduler
- Switch queue driver from
synctoredis: setQUEUE_DRIVER=redisin.env - Run workers and scheduler as dedicated services:
docker compose up -d worker scheduler - In local development, use
npm run dev:workerandnpm run dev:scheduler - Run
npx svelar make:dashboardand use/adminto monitor queue and scheduler
Docker Deployment
npx svelar make:docker --db=postgres --redis
docker compose up -d --build
docker compose exec app npx svelar migrate
docker compose exec app npx svelar seed:run
Logging & Monitoring
- Configure container log retention
- Monitor queue depth and job failures via admin dashboard
- Set up error tracking (Sentry, etc.)
- Monitor the health endpoint:
GET /api/health
Scaling
Horizontal Scaling
Run multiple instances behind a load balancer:
# docker-compose.yml
services:
app-1:
build: .
environment:
- INSTANCE_ID=1
app-2:
build: .
environment:
- INSTANCE_ID=2
postgres:
image: postgres:15
redis:
image: redis:latest
Requirements for horizontal scaling:
- Use PostgreSQL (not SQLite) for shared database
- Use Redis for queue, cache, and sessions
DatabaseSessionStoreensures sessions work across instances- The scheduler's
SchedulerLockprevents duplicate task execution across instances
Caching Strategy
import { Cache } from '@beeblock/svelar/cache';
// Cache expensive queries
const posts = await Cache.remember(
'user:' + userId + ':posts',
() => Post.where('user_id', userId).get(),
3600 // 1 hour TTL
);
// Invalidate on changes
await Cache.forget('user:' + userId + ':posts');
Database Optimization
- Index frequently queried columns (email, slug, user_id, team_id)
- Use
paginate()for large result sets - Use eager loading (
with()) to avoid N+1 queries - Monitor slow queries in production
Adding i18n
The scaffold ships with hardcoded English. To add internationalization:
- Install Paraglide: follow the i18n guide
- Replace hardcoded strings with
m.xxx()message functions - Add
LanguageSwitcherto your layout - Update
hooks.server.tsto includei18nconfig:
import { paraglideMiddleware } from '$lib/paraglide/server';
import { getTextDirection } from '$lib/paraglide/runtime';
export const { handle, handleError } = createSvelarApp({
auth,
secret: process.env.APP_KEY!,
sessionStore: new DatabaseSessionStore(),
csrfExcludePaths: ['/api/webhooks', '/api/internal/'],
i18n: { paraglideMiddleware, getTextDirection },
});
Plugin Ecosystem
Creating Custom Plugins
// src/lib/plugins/AnalyticsPlugin.ts
import { Plugin } from '@beeblock/svelar/plugins';
import { Container } from '@beeblock/svelar/container';
export class AnalyticsPlugin extends Plugin {
readonly name = 'analytics';
readonly version = '1.0.0';
async register(app: Container) {
app.singleton('analytics', () => new AnalyticsService());
}
async boot(app: Container) {
// Initialize tracking
}
config() {
return {
key: 'analytics',
defaults: {
trackingId: process.env.ANALYTICS_ID,
},
};
}
}
Register in src/app.ts:
import { PluginManager } from '@beeblock/svelar/plugins';
import { AnalyticsPlugin } from './lib/plugins/AnalyticsPlugin.js';
const plugins = new PluginManager(app);
plugins.use(new AnalyticsPlugin());
await plugins.boot();
See Plugins for the full plugin API.
Next Steps
- Getting Started — Walkthrough of setup and first 10 minutes
- Controllers & Routing — API design patterns
- Authentication — JWT, refresh tokens, API tokens, request signatures
- Queue & Jobs — Background processing and workers
- Scheduler — Cron task configuration
- Additional Features — Events, logging, mail, storage, broadcasting
- Architecture — DDD boundaries, module communication, anti-patterns
Svelar SaaS Guide © 2026