loading...
Cover image for Building a multi-tenant application with Prisma

Building a multi-tenant application with Prisma

errorname profile image Thibaud Courtoison Originally published at medium.zenika.com ・4 min read

“Wait, what’s a multi-tenant application?”

Glad you asked, here is the Wikipedia definition:

The term “software multi-tenancy” refers to a software architecture in which a single instance of software runs on a server and serves multiple tenants.

Multi-tenancy

For example, Slack is a multi-tenant application, and prisma.slack.com is one of its tenant.

“Alright, I get it. But why would I make a multi-tenant application?”

Let’s make an analogy in which your application is a building, and each group of users is given an apartment. All these tenants are thus sharing the same walls, electricity and water network, waste treatment, etc.: you are mutualising the costs!

In our software world, it goes even further: As each “apartment” is made out of the same “plans” (i.e. the same piece of code), you can deploy your changes once, and they’ll be applied to every tenant!

“Ok so multi-tenancy is a good way for me to reduce my costs in terms of infrastructure complexity and deployment time… That sounds interesting! How can I do that?”

Well, you can use Prisma services feature. Follow the guide:


Using Prisma for multi-tenancy

Prisma

For the rest of this article, I will assume you already know about Prisma and how it works. If you don’t, Prisma is a really awesome library that replaces traditional ORMs. I strongly suggest you discover it and follow the getting started tutorial.

Prisma services

A Prisma service is defined by a GraphQL datamodel and a name/stage identification. Prisma servers can be configured to host multiple Prisma services.
Each Prisma service has exactly one endpoint, composed of the server host, service name and the service stage.

Service endpoint

If you do not provide a service name or stage, they will be both assigned the value “default”.

To use Prisma services for a multi-tenant application, we can deploy the same GraphQL datamodel to multiple services.

Starting our app with a single tenant

As an illustration, we’re going to take our previous example: Slack. Let’s then start with our datamodel and create an User and a Post:

#datamodel.graphql

type User {
  id: ID!
  name: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: "String!"
  content: String!
  author: User!
}

Then, deploy your datamodel to https://localhost:4466/company_a/dev.

We should now have a service ready for our application. Let’s write the server:

// server.js

const { Prisma } = require('./generated/prisma-client')
const { ApolloServer } = require('apollo-server')
const resolvers = require('./resolvers')

const prisma = new Prisma({
  endpoint: 'https://localhost:4466/company_a/dev'
})

const server = new ApolloServer({
  resolvers: resolvers,
  context: ctx => ({
    ...ctx,
    prisma
  })
})

/*------------------------*/
/* In your resolvers file */
module.exports = {
  Query: {
    users: (_, args, ctx) => ctx.prisma.users(args)
  }
}

If you run your server, you should be able to query the users from the company_a/dev service. That’s a great start but as we’ve hardcoded our client’s service in our server, adding a new tenant wouldn’t be easy.

Evolving into a multi-tenant app

In order to make our server multi-tenant, we will use the prisma-multi-tenant package.

prisma-multi-tenant

npm install prisma-multi-tenant

Then, redeploy your datamodel to two more services:

  • https://localhost:4466/company_a/prod
  • https://localhost:4466/company_b/dev

Finally, we will change our server file to the following:

// server.js

const { MultiTenant } = require('prisma-multi-tenant')
const { Prisma } = require('./generated/prisma-client')
const { ApolloServer } = require('apollo-server')
const resolvers = require('./resolvers')

const multiTenant = new MultiTenant({
  instanciate: (name, stage) =>
    new Prisma({
      endpoint: `https://localhost:4466/${name}/${stage}`,
      // Here, you are in charge of extracting the name/stage
      // of the service from the request.
      // As an example, we extract it from the headers
      nameStageFromReq: req => req.headers['prisma-service'].split('/')
    })
})

const server = new ApolloServer({
  resolvers: resolvers,
  context: ctx => ({
    ...ctx,
    prisma: multiTenant.current(ctx.req)
  })
})

/*------------------------*/
/* In your resolvers file */

// Did you notice we haven't touched our resolvers?
module.exports = {
  Query: {
    users: (_, args, ctx) => ctx.prisma.users(args) 
  }
}

As you can see, we created a MultiTenant object, and used its current method in the context option of ApolloServer. We also did not change our resolvers, as if we were still dealing with a single tenant.

For more details about prisma-multi-tenant, check its readme.

Now, when you query your server, add the prisma-service HTTP header with the name/stage of the service you want to access (“company_a/prod” for example), and it will query the right service.

Aaand, that’s it. You’ve got a multi-tenant server.

“Wait… That’s all we have to do? Using a library and querying with an HTTP header? Really?”

We haven’t written a Slack-like application, but yes, this is the foundation of your multi-tenant application. Prisma is dealing with multiple database, and your server won’t care which. It will just work. Moreover, your server can now be deployed once, and handle requests coming from any tenants!


I hope I’ve shown you why the Prisma services feature is the premium choice to easily build a multi-tenant application, with as little impact on your developer experience as possible.

At Zenika, we built a production-ready multi-tenant application called FAQ to create an “Internal Knowledge Database for company collaborators” that helped reduce the recurrent email/slack questions about our internal processes. We had to overcome the challenges of working with multi-tenancy: simultaneous deployment (prisma and third-parties), developers tools, continuous integration of services…

If you are interested to know how we dealt with these issues, don’t hesitate to check the github repository or contact me on twitter!

Discussion

markdown guide