API Maker

The framework for AI era

Database APIs

Multi-Tenant

One set of APIs for all your customers, each with a database of its own.

Mark an instance as a multi-tenant structure and point it to a tenants table. Each request names its tenant, and API Maker runs the same APIs, schemas, hooks and permissions on that tenant's database, whose connection string it reads from the tenants table. Customers share the servers and the APIs, not the databases.

See it live

Step through it, slow it down, or open the full canvas.

api-maker/features/multi-tenantLive
  • Request
  • Tenant lookup
  • Tenant data
Active tenants
2in the table
Pools open
0one per tenant
Tenant lookups
0rows read
Last status
–HTTP

The request names its tenant. crm::acme in the path: the instance crm, the tenant acme. The same API serves every customer.

How it works

  1. The request names its tenant.

    crm::acme in the path: the instance crm, the tenant acme. The same API serves every customer.

  2. API Maker reads the row of the tenant.

    It finds acme in the tenants table, with the find of the secret entry, and decrypts its connection string.

  3. The database of the tenant answers.

    A pool opens for crm::acme and the query runs on the database of Acme. Only the customers of Acme come back.

  4. Same API, another customer.

    globex comes in a header. Its row gives another connection string, another pool and another database.

  5. The next requests reuse the pool.

    Acme asks again. Its pool is open, so the query goes straight to its database, without reading the tenants table.

What you get

Isolation by design

Each customer has its own database, so there is no tenant_id filter to add to every query, or to forget.

One product to maintain

Schemas, hooks, settings, permissions and custom code are defined once and apply to every tenant.

Users per tenant

Your users can live in a users table inside each tenant database. A user token made for a tenant works for that tenant only.

Cache kept apart

The tenant is part of every cache key, so one customer never receives the cached answer of another.

Move a tenant any time

Copy its database, update its row, and call the multi-tenant-instance-updated system API: every server drops the pool of that tenant and clears the cache of the instance.

Tenants in the admin panel

Pick a tenant in the databases panel and on the API testing page to work with its data. Logs record the tenant of each call.

An example

A B2B SaaS

A CRM sold to companies keeps each company in its own PostgreSQL database. Small customers share a database server, a large one gets its own, and a regulated one keeps its database in its own data center. The product has one set of APIs, and onboarding a customer is creating its database and adding one row to the tenants table.

In the default secret: where the tenants aresecret.ts
import * as T from 'types';let Secret: T.ISecretType | any = {    // … common and your other keys    multiTenant: {        crm: <T.IMultiTenantSecretObj>{            instanceName: 'catalog',            databaseName: 'public',            collectionName: 'tenants',            usernameColumn: 'username',            connectionStringColumn: 'connection_string',            find: { active: true },        },    },};module.exports = Secret;

Then choose multiTenant.crm as "Connection String Multi Tenant" on the instance crm. For Oracle, add the columns of the username and password of each tenant, and of its privilege when it connects with one.

The same API, the database of each customerrequest
GET /api/gen/admin/crm::acme/crm/customersx-am-authorization: <API user token>GET /api/gen/admin/crm/crm/customersx-am-authorization: <API user token>x-am-tenant-username: globex

The first request runs on the database of Acme, the second on the database of Globex. The instance finds the tenant in its own tenants table, so the request names the tenant only.

After moving a tenant to another servercustom-api.ts
await g.sys.system.multiTenantInstanceUpdated({ instanceName: 'crm', username: 'globex' });

Good to know

  • A user token made for a tenant is refused on every other tenant and on the structure database. Tokens from a users table outside the multi-tenant instance are not tied to a tenant: to keep a caller to its own tenant, compare g.req.params.tenantUsername with the caller in a pre hook.
  • All tenants of a multi-tenant instance use the same database type as the instance.
  • A schema change has to run on every tenant database: plan your migrations for all of them.
  • Each API Maker process keeps a pool per active tenant. Size the connection limits of your database servers for it.

Questions

How do I set it up?

Keep your tenants in a table that has a schema in API Maker, with a username column and a connection string column. Add an entry for that table to the default secret. Then check "Is Multi Tenant Structure Instance" on the instance and choose that entry in "Connection String Multi Tenant".

What does the instance itself connect to?

Its own connection string, the structure database. It holds the tables whose schemas every tenant uses. A request without a tenant works on it.

How does a user get a token for its tenant?

Call the token API with the tenant header and the name of a DB token generator on the users table of the multi-tenant instance. The user is read from the database of that tenant, and the token works for that tenant only, also after a refresh.

Where does the tenant come from in my frontend?

From the account of the signed-in user. Put it in the path (crm::acme) or in the x-am-tenant-username header of every request.

Can I have several multi-tenant instances?

Yes. Each one has its own secret entry and tenants table, and its own database type.

How are new tenants picked up?

The first request of a new tenant reads its row and opens its pool. No restart and no deployment.