🧭 Use Prisma as a multi-tenant provider with Apollo Server or Yoga
What's a multi-tenant application?
A multi-tenant application is when a single instance of your application runs on a server and serves multiple tenants.
With a multi-tenant architecture, a software application is designed to provide every tenant a dedicated share of the instance - including its data, configuration, user management, tenant individual functionality and non-functional properties.
For example, you could run a social-network for companies, where each company would have it's own data and users.
Why is Prisma great for multi-tenancy?
Prisma handles services mapped to individual databases. You can use services to have multiple different applications, or your could create a service for each of your tenant.
A service is defined by a name and a stage. (e.g. company_a/dev
, company_a/prod
, company_b/pre-prod
, company_b/prod
)
Why do I need prisma-multi-tenant
?
Because Prisma Client only handles a single service. If you want your GraphQL Server (Apollo or Yoga) to handle multiple services seamlessly, you should use prisma-multi-tenant
!
npm install prisma-multi-tenant
The following example is using Prisma Client
. You will find instructions for prisma-binding
after this example.
/* In your backend's main file */
const { MultiTenant } = require('prisma-multi-tenant')
const { Prisma } = require('./generated/prisma-client')
const { ApolloServer } = require('apollo-server')
const multiTenant = new MultiTenant({
instanciate: (name, stage) =>
new Prisma({
endpoint: `https://localhost:4466/${name}/${stage}`
})
})
const server = new ApolloServer({
/* ..., */
context: ctx => ({
...ctx,
prisma: multiTenant.current(ctx.req) // or ctx.request if you use GraphQL-Yoga
})
})
/* In your resolvers */
module.exports = {
Query: {
users: (_, args, ctx) => ctx.prisma.users(args)
}
}
Then, from your frontend, you can use a prisma-service
HTTP header with [name]/[stage]
in your GraphQL operations to choose which service to target.
If you use prisma-binding
, you need to slightly change the previous code:
const { MultiTenant } = require('prisma-multi-tenant')
const { Prisma } = require('prisma-binding')
const multiTenant = new MultiTenant({
instanciate: (name, stage) =>
new Prisma({
typeDefs: './path/to/typedef.graphql',
endpoint: `https://localhost:4466/${name}/${stage}`
})
/* ... */
})
/* ... */
The constructor of MultiTenant accepts an option object argument with the following attributes:
interface MultiTenantOptions {
/* Returns a PrismaClient (or prisma-binding) instance given a name and a stage */
instanciate: (name: string, stage: string) => PrismaInstance
/* Extracts the name and stage from the Request object */
nameStageFromReq: (req: Object) => [string, string]
}
const defaultOptions: MultiTenantOptions = {
instanciate: () => ({}),
nameStageFromReq: (req: Object) => req.headers['prisma-service'].split('/')
}
By default, the
name/stage
of the service will be extracted from theprisma-service
HTTP header, but you can extract it anyway you want from the Request (url, body, another header, ...)
🙌 Thanks to @antoinecarat for the reviews of this library