API Maker

The framework for AI era

Level 3 · ScaleArchitecture 08 of 13

Multi-tenant: a database per customer

One product and one set of APIs, with the data of every customer in a database of its own.

True multi-tenancy, supported out of the box: each request names its tenant, and API Maker runs it on that customer's own database, found in a tenants table. Customers share the servers, never the data.

See it live

Every request is drawn as it travels. Slow it down, pause, go step by step, or open the full canvas and zoom in.

api-maker/architectures/multi-tenantLive
  • Acme
  • Globex
  • Initech
  • Umbrella
  • Tenant lookup
  • Write
Tenants
3customers
Databases
3one per tenant
API servers
2shared
Pools
6tenant × server

One set of APIs for every customer. Every customer calls the same APIs. The tenant is part of the path: /api/gen/saas/crm::acme/crm/customers.

How it works

The same steps as the diagram, in more detail.

  1. One set of APIs for every customer.

    The product has one set of APIs, schemas and custom code. The instance "crm" is marked as a multi-tenant structure, and each request names its tenant after two colons: crm::acme, crm::globex. API Maker runs the same API on the database of that customer, with the same permissions and hooks for all of them.

  2. Each tenant is a row of a table.

    The instance points to a table of your own, through a secret: which column holds the username of the tenant, which one holds its connection string, and an optional filter such as active tenants only. On the first request of a tenant, API Maker reads its row, decrypts the connection string, opens a pool for that tenant and reuses it for the next requests.

  3. Every customer in a database of its own.

    Isolation does not rely on a tenant_id column that every query must remember: each customer has a separate database, so a request of one tenant is physically unable to read the data of another. Backups, restores, exports and deletions happen per customer.

  4. Each customer where it fits.

    Where a tenant's database lives is only a connection string. Put many small customers on a shared server, give a large one a server of its own, move one when it grows: copy its database, update its row, and call the multiTenantInstanceUpdated system API so every server resets its pool.

  5. A new customer is a new row.

    Onboarding a customer is creating its database, running your migrations on it, and inserting one row in the tenants table. A regulated customer can even keep its database in its own data center, reached through a private tunnel, while using the same product as everyone else.

  6. Servers for everyone, databases per customer.

    API servers are shared by all tenants and scale like any fleet of API Maker: add servers behind the load balancer and each one serves every tenant, opening pools on demand. Cached responses are kept per tenant, so one customer never receives the cache of another.

Why choose this architecture

Isolation by design

Each customer has its own database. No tenant_id to forget in a query, no way to read another tenant's data.

One product to maintain

The same APIs, schemas, permissions and custom code serve every customer. Fix once, every tenant gets it.

Each tenant where it fits

Small tenants share a server, large ones get their own, regulated ones keep their database at home.

Onboarding is a row

Create the database, insert one row in the tenants table, and the new customer is served at once.

Encrypted connection strings

Connection strings in the tenants table can be encrypted by API Maker and are decrypted only when a pool opens.

Per-customer operations

Back up, restore, export, move or delete one customer without touching the others.

Server configuration

Plain VPS from any provider, or your own servers, with Ubuntu 22.04 LTS. Sizes are a starting point: measure and adjust.

API servers

×2 or more
CPU
4 – 8 vCPU
Memory
8 – 16 GB
Storage
40 GB SSD

Runs

  • API Maker, behind a load balancer
  • A connection pool per tenant, opened on demand

Ports

  • 38246 and 38245: from the load balancer only

Each server keeps a pool for every active tenant: plan the connection limits of the database servers for it.

Shared tenant DB servers

as needed
CPU
8 vCPU
Memory
32 GB
Storage
500 GB NVMe

Runs

  • One database per small tenant
  • The same engine as the multi-tenant instance

Ports

  • From the API servers only

Dedicated tenant DB servers

1 per large tenant
CPU
16 vCPU
Memory
64 GB
Storage
1 TB+ NVMe

Runs

  • The database of one large customer

Ports

  • From the API servers only

Platform

×1
CPU
4 vCPU
Memory
8 GB
Storage
100 GB NVMe

Runs

  • The tenants table, in one of your databases
  • API Maker DB and Redis
In the secret of the admin user: where the tenants aresecret
{    "multiTenant": {        "crm": {            "instanceName": "catalog",            "databaseName": "public",            "collectionName": "tenants",            "usernameColumn": "username",            "connectionStringColumn": "connection_string",            "find": { "active": true }        }    }}

Then check "Is Multi Tenant Structure Instance" on the instance "crm" and set its "Connection String Multi Tenant" to multiTenant.crm.

The same API, the database of each customerrequests
GET /api/gen/saas/crm::acme/crm/customers      # the database of AcmeGET /api/gen/saas/crm::globex/crm/customers    # the database of Globex# A tenant moved to another server: its row is updated, then every server resets its poolPOST /api/system-api/saas/multi-tenant-instance-updated   { "instanceName": "crm", "username": "globex" }

Custom code calls it as sys.system.multiTenantInstanceUpdated.

Good to know

  • Every schema change must run on every tenant database: automate your migrations for all of them.
  • Pools add up: API servers × active tenants. Watch the connection limits of the database servers.
  • All tenant databases of a multi-tenant instance use the same database engine.
When you outgrow itThe data tier itself must grow beyond single servers. Clustered Redis and databases

Questions

How does a request choose its tenant?

The tenant is written after the instance name, separated by two colons: /api/gen/saas/crm::acme/…. Your frontend builds this path from the account the user belongs to, or a pre hook can set it from the token.

Where are the connection strings stored?

In a table of your own, the tenants table, with a column for the username of the tenant and one for its connection string. Turn on the encryption conversion of that column and API Maker stores it encrypted.

Can tenants use different database types?

Not within one multi-tenant instance: its tenants share its engine, for example PostgreSQL. A product can have several multi-tenant instances, each with its own engine and tenants table.