saas

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 24, 2026 License: MIT Imports: 13 Imported by: 0

README

togo

togo-framework/saas

marketplace pkg.go.dev MIT

Part of the togo framework.

Install

togo install togo-framework/saas

togo

togo · saas

Multi-tenant SaaS for togo — turn any app or dashboard into a multi-tenant product.


saas resolves the current tenant from each request and scopes the app to it, with pluggable tenant resolution and isolation strategies — so you can model tenants as domains, as teams / tenant-ids, and store their data in one shared database or a database per tenant.

Install

togo install togo-framework/saas

Blank-importing the package registers the plugin; it creates a tenants table and adds the tenant middleware + the tenant CRUD API on boot.

Config (togo.yaml / env)

# togo.yaml
saas:
  tenant_resolver: header   # header | domain | subdomain
  isolation: shared         # shared | single-db
env values default meaning
SAAS_TENANT_RESOLVER header · domain · subdomain header how the tenant is identified per request
SAAS_ISOLATION shared · single-db shared where each tenant's data lives

Tenant resolution (who is this request for?)

  • header — a tenant-id / team in the X-Tenant-ID header (set it from a JWT claim, a path segment, your gateway, …). Best for teams/orgs inside one app.
  • domaindomain-as-tenant: the full host (acme.com) maps to Tenant.Domain.
  • subdomain — the first label (acme.app.comacme).

Register your own: saas.RegisterResolver("path", func(r *http.Request) string { … }) and set SAAS_TENANT_RESOLVER=path.

Isolation (where does the data live?)

  • shared — one database, every tenant row carries a tenant_id. Scope your queries with saas.TenantID(ctx). Cheapest; good default.
  • single-dbone database per tenant: each tenant row stores a db_dsn, and Service.DB(ctx) returns that tenant's own connection (cached). Strongest isolation.

Use it in a handler

import "github.com/togo-framework/saas"

func handler(w http.ResponseWriter, r *http.Request) {
    t, ok := saas.CurrentTenant(r.Context())     // the resolved tenant
    if !ok { http.Error(w, "no tenant", 400); return }

    s, _ := saas.From(kernel)
    db, _ := s.DB(r.Context())                    // tenant DB (single-db) or shared DB
    // shared isolation: filter by saas.TenantID(r.Context())
    _ = db
}

Tenant CRUD API

GET    /api/tenants          list tenants
POST   /api/tenants          create { name, domain, plan, db_dsn }
GET    /api/tenants/{id}     get
PUT    /api/tenants/{id}     update
DELETE /api/tenants/{id}     delete

License

MIT


Premium sponsors

ID8 Media  ·  One Studio

Support togo — become a sponsor.

Documentation

Overview

Package saas turns a togo app into a multi-tenant SaaS. It resolves the current tenant from each request — by domain/subdomain or a tenant-id/team header — and scopes the app to it, with two isolation strategies: a shared database scoped by tenant_id (row-level), or a single database per tenant (connection routing).

Enable by blank-import: `togo install togo-framework/saas`.

Config (togo.yaml / env):

SAAS_TENANT_RESOLVER = header | domain | subdomain   (default: header; header = X-Tenant-ID)
SAAS_ISOLATION       = shared | single-db            (default: shared)

In a handler: read the tenant with saas.CurrentTenant(ctx) / saas.TenantID(ctx), and get the right *sql.DB with the saas service's DB(ctx) (the kernel DB under "shared", or the tenant's own DB under "single-db").

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RegisterResolver

func RegisterResolver(name string, r Resolver)

RegisterResolver adds (or overrides) a named tenant resolver. Call from init().

func TenantID

func TenantID(ctx context.Context) string

TenantID returns the current tenant id ("" if none) — use it to scope queries under the shared-database isolation strategy.

Types

type Resolver

type Resolver func(r *http.Request) string

Resolver extracts a tenant key (a domain or a tenant id) from a request.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service is the saas service, stored in the kernel container under "saas".

func From

func From(k *togo.Kernel) (*Service, bool)

From fetches the saas service from the kernel container.

func (*Service) DB

func (s *Service) DB(ctx context.Context) (*sql.DB, error)

DB returns the database to use for the current request: under "single-db" isolation it is the tenant's own database (opened from Tenant.DBDSN, cached); otherwise the shared kernel database — scope your queries by TenantID(ctx).

func (*Service) Middleware

func (s *Service) Middleware(next http.Handler) http.Handler

Middleware resolves the current tenant from the request and stores it in the context (read it with CurrentTenant). Registered on the kernel router.

type Tenant

type Tenant struct {
	ID        string `db:"id" json:"id"`
	Name      string `db:"name" json:"name"`
	Domain    string `db:"domain" json:"domain"`
	Plan      string `db:"plan" json:"plan"`
	DBDSN     string `db:"db_dsn" json:"db_dsn"`
	CreatedAt string `db:"created_at" json:"created_at"`
}

Tenant is an isolated customer of the app.

func CurrentTenant

func CurrentTenant(ctx context.Context) (*Tenant, bool)

CurrentTenant returns the tenant resolved for this request, if any.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL