Building a Next JS Multi Tenant App with Prisma and PropelAuth is a great way to create scalable and maintainable applications. This approach allows for easy management of multiple tenants and their respective data.
Prisma is a popular ORM (Object-Relational Mapping) tool that simplifies database interactions, making it a great fit for Next JS multi tenant apps. By using Prisma, you can define your database schema and automatically generate boilerplate code.
PropelAuth is a authentication library specifically designed for Next JS applications. It provides a simple and secure way to manage user authentication and authorization.
By combining Prisma and PropelAuth, you can create a robust and scalable multi tenant app that meets the needs of your users.
What to Build?
When building a Next.js multi-tenant app, you need to decide what to build.
You'll be building a simple B2B application where each user can make posts within a tenant.
Each user in a tenant should be able to read their own posts, and no one outside the tenant should be able to view or access them.
Your application will have tenants, which are Organizations in this case.
The final product will look like a space where everyone in a tenant can view and make posts that only they can see.
There are different approaches to this problem, but it ultimately depends on your application.
Dealing with Users
Users can be in multiple tenants, which can make it tricky to determine which tenant's data to show.
In practice, this problem comes up often, especially with services like GitHub, Slack, and Google/Twitter that allow users to be in multiple organizations or accounts.
We can't just let anyone view all the posts for a specific organization, so we need a solution that can handle multiple tenants securely.
PropelAuth is an auth service designed for multi-tenant and B2B use cases that includes self-service UIs for each tenant to manage themselves.
Users will be able to sign up, create tenants, and invite their co-workers without us writing any code for it.
After configuring, users can sign up, log in, create tenants, etc - on their own, and all we have to do is check if they are in the tenant they are trying to access.
We can do this with PropelAuth's React library by wrapping our application with an AuthProvider that fetches our current user's metadata if they are logged in.
We can use higher order functions like withAuthInfo or hooks like useAuthInfo to access our user's information anywhere in our application.
For any tenants our users are not a member of, they will get a "Not found" error, which is a reasonable error message for a user that happens upon the wrong page.
Backend Setup
In our Next.js multi-tenant app, we'll set up the backend using Next.js API routes and Prisma, a popular TypeScript ORM that manages database operations.
We'll define our schema in the prisma/schema.prisma file, which is where we'll specify the structure of our database. Our schema will include a post with a post_id that defaults to a new UUID.
Each post will be associated with a tenant (organization) and a user, and will contain IDs for the tenant (org_id) and user (user_id), as well as the text to be displayed. We can update our schema in the future to add more fields like a timestamp.
Creating Backend with Prisma
We're using Next.js API routes to create our backend, which is a great choice for building scalable and efficient server-side APIs.
Prisma is a popular TypeScript ORM that we're using to manage our database operations, including migrations.
We define our schema in a new file called prisma/schema.prisma, where we specify the structure of our database.
Our schema is fairly straightforward, with a post that contains some text to display, associated with a tenant (organization) and a user.
Each post has a post_id that defaults to a new UUID, along with IDs for the tenant (org_id) and user (user_id).
We can update our schema in the future to add more fields like a timestamp, and Prisma's migration feature will take care of the rest.
We're using Prisma's migration feature to create or update our database to the most recent schema.
By using Prisma, we can focus on building our application without worrying about the underlying database operations.
Authenticated Backend Requests
Authenticated Backend Requests are crucial for securing your Next.js SaaS application. To make authenticated requests to our backend, we need to provide our backend with some verifiable piece of information that proves our user is who they say they are.
PropelAuth provides access tokens for this, which our backend can verify without making any external requests. We already pass back an access token for our user from useOrgMembershipByPathParam, so let's use it.
There are various ways to pass the orgId to our backend API, such as using a query parameter, a path parameter, or the body of a post request. The choice depends on our specific use case.
To handle the backend requests, we can use Next.js' built-in API routes. This will allow us to create a robust backend that securely handles user data and access.
Here's a simple example of how to create a backend API route using Next.js:
```
import { NextApiRequest, NextApiResponse } from 'next';
import { verifyToken } from '../utils/auth';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const token = req.headers['x-access-token'];
const user = verifyToken(token);
if (!user) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Handle the request
}
```
This code verifies the access token sent in the request headers and checks if the user is authenticated. If the user is not authenticated, it returns a 401 Unauthorized response.
Authentication
Authentication is a crucial aspect of any multi-tenant application. It ensures that only authorized users can access specific tenant data, preventing unauthorized access.
PropelAuth is an authentication service designed for multi-tenant and B2B use cases, making it an ideal choice for Next.js multi-tenant apps. It includes self-service UIs for each tenant/organization to manage themselves.
To implement authentication in a Next.js multi-tenant app, you can use the getServerSideProps method to dynamically authenticate pages. This is particularly useful for securing pages that are generated dynamically based on the URL.
Here's an example of how to implement dynamic page authentication using the getServerSideProps method:
- Use the getServerSideProps method to fetch user data from the backend.
- Verify the user's authentication status by checking the access token.
- Return the authenticated user data to the page component.
The Next.js authentication process involves several steps, including request initiation, data submission, verification, session creation, access or denial, and middleware integration. Here's a breakdown of each step:
- Request initiation: when a user tries to access a secured page or feature, the application initiates an authentication request.
- Data submission: the user provides their credentials, typically a username and password, or opts for a third-party authentication method.
- Verification: Next.js interacts with its backend or third-party services to verify the provided credentials.
- Session creation: once authenticated, a session is initiated, and the client application sends the session cookie with each request.
- Access or denial: depending on whether the credentials are valid, Next.js either grants access or denies it.
- Middle ware integration: for finer control, middleware can be integrated into the Next.js authentication process.
To integrate JWT sessions, you need to store the encrypted token in HTTPOnly cookies, validate the JWT on protected API routes and pages, and destroy the cookie on logout to enforce session expiration.
JSON Web Tokens (JWTs) enable secure user sessions in Node.js apps. Here's an example using the jsonwebtoken library to generate signed JWTs:
- Store the encrypted token in HTTPOnly cookies.
- Validate the JWT on protected API routes and pages.
- Destroy cookie on logout to enforce session expiration.
By following these authentication best practices, you can ensure that your Next.js multi-tenant app is secure and scalable.
Database and Schema
For a next js multi tenant app, we need to consider the database and schema. A multi-tenant database architecture is a must, which allows us to support multiple tenants in a SaaS environment.
This architecture is supported by MongoDB, where we can explore the boilerplate's schema and models. We'll be able to learn how to design our database to accommodate multiple tenants.
In this setup, we can use a single MongoDB instance to store data for all tenants, while still maintaining isolation between them. This is a common approach in SaaS applications, where scalability and cost-effectiveness are key.
Database Schema
The database schema is a crucial part of a multi-tenant database architecture, especially in a SaaS environment.
The boilerplate's MongoDB schema is designed to support multiple tenants, with separate collections for each tenant's data. This makes access control and data isolation easier compared to a shared collection.
There are a few common ways to model multi-tenant data, including separate databases, shared database with separate collections, and shared database with shared collection.
Here are the three strategies for modeling tenants:
The boilerplate uses separate collections for users, tenants, and each tenant's data, which keeps user and tenant information separate from the protected application data for each tenant.
Data Aggregation
Data Aggregation is a crucial aspect of database management, ensuring that tenants only access their own data.
To get metrics for a tenant, you need to filter on tenantId first. This is a straightforward process that prevents data breaches and unauthorized access.
Filtering by tenantId ensures that only a tenant's data is returned, which is a fundamental principle of multi-tenant databases.
Aggregation works the same way as querying, requiring a filter on tenantId to produce accurate metrics for a tenant.
This approach also handles permissions automatically based on the currently logged in user, streamlining the process and reducing errors.
By following this approach, you can ensure that your database is secure and scalable, meeting the needs of multiple tenants.
Routing and Pages
Server-side routing is a key feature in Next.js SaaS apps that enforces tenant separation.
Next.js handles critical needs for SaaS by isolating tenant data, securing resources, and providing performance.
The [tenantId] pattern in Next.js allows creating tenant-aware pages and API routes by parsing the tenant ID from the request URL.
This keeps tenant data isolated, making it a strong choice for building multi-tenant web apps.
Dynamic page authentication is a pattern that secures pages generated dynamically based on the URL, preventing exposure of unauthorized content.
The getServerSideProps method can be used for dynamic authentication, as shown in an example that uses a free testing API.
Customization and Theming
Customization and theming are crucial aspects of a Next JS multi-tenant app. The Next JS SaaS boilerplate provides a solid foundation for easily adapting the boilerplate styling and layout to your brand's needs.
This is achieved through reusable React layout components that wrap pages and sections, allowing for global configuration of the chrome and structure wrapping around inner content. With this approach, any page leveraging the Layout component automatically inherits its consistent header, sidebar, and footer.
Customizing those once then applies changes everywhere, simplifying theming dramatically compared to modifying templates individually.
Customizable UI and Theming
Customizable UI and Theming is a game-changer for SaaS platforms.
Easily adapting the boilerplate styling and layout to your brand's needs is a key benefit of the NextJS SaaS boilerplate.
Crafting a consistent user experience across a SaaS platform requires controlling layouts and views in one place. This is made possible by the reusable React layout components provided by the NextJS SaaS boilerplate.
Any page leveraging this Layout component automatically inherits its consistent header, sidebar, and footer. Customizing those once then applies changes everywhere, simplifying theming dramatically.
Composing these constructs guarantees UI consistency, while allowing global control. This empowers easily adapting views and layouts to suit your ideal SaaS user experience.
The NextJS SaaS boilerplate implements a theming engine for switching color schemes and styles via context API. This empowers features like theme switchers for toggling modes dynamically based on user preferences.
Having dynamic themes unlocks changing a SaaS app's look and feel programmatically.
SaaS Template Components
Next.js SaaS templates provide a solid foundation for building scalable SaaS applications with React. Most templates include a standard set of functions that separate user and tenant data, enabling support for multiple customer accounts within one codebase.
Authentication scaffolding is a crucial component, handling signup, login, access control, and exposing user/tenant info to pages via hooks like useSession(). This makes it easy to manage user sessions and authentication flows.
Dashboards and admin UI are also essential components, providing pre-built interfaces for managing subscribers, viewing usage analytics, editing tenant data, and more. These interfaces help you stay on top of your SaaS business.
API functions are another key component, offering CRUD APIs for database entities like users, tenants, and subscriptions. These APIs follow REST/RPC convention, making it easy to interact with your database.
Security and Environment
Security and Environment is a top priority for a next.js multi-tenant app.
Managing environment variables is critical to avoid exposing secrets like API keys in code when deployed.
The next-env module makes it easy to load variables from the relevant file depending on the environment, automatically using values from .env or .env.production files.
Next.js has built-in support for environment variables through .env files, which can store variables available only during development like database URLs.
The boilerplate must load variables from the relevant file to separate development and production config for smooth SaaS boilerplate deployment.
Using dotenv and dotenv-cli libraries can help manage hierarchies of .env files, making it even easier to handle environment variables correctly.
Testing and Summary
We've verified that we can't view other tenant's data, which is a crucial security measure in a multi-tenant app. This is confirmed by the "Not found" message displayed when trying to access an arbitrary tenant's posts page.
To ensure this restriction is enforced on the backend, we used cURL to make requests directly to our backend. We got a 401 response because we didn't specify an access token, signifying we're not a valid user.
If we pass in a valid access token but an org_id we're not in, we get a 403 response, indicating we don't have permission to view that organization's data.
Testing
Testing is a crucial part of software development, and it's essential to ensure that our application is secure and user-friendly. We tested that we cannot view other tenant's data by navigating to an arbitrary tenant's posts page, which displayed a "Not found" message.
This is just a frontend check, so we used cURL to verify that we cannot make requests directly to our backend. As expected, we received a 401 error because we didn't specify an access token signifying we are a valid user.
If we pass in a valid access token, but still pass in an org_id that we are not in, we'll get a 403 error, signifying that we don't have permission to view that organization's data. This confirms that our application is indeed secure and respects user permissions.
Summary
We've made significant progress on building a robust multi-tenant B2B application. Our users can now signup, create tenants, and manage their own tenants.
The backend of our application is powered by Next.js API routes, which are highly scalable. This ensures that our application can handle a large number of users and requests efficiently.
We've also implemented Prisma, which provides us with DB migrations and type safety between our application and the database. This helps prevent errors and ensures data consistency.
Sources
- https://www.propelauth.com/post/multi-tenant-next-js-app-with-prisma-and-propelauth
- https://nextjsstarter.com/blog/next-js-saas-boilerplate-unpacked-core-features-explained/
- https://blog.alexcloudstar.com/setting-up-nextjs-13-with-auth0-and-sub-domains-a-guide-to-multi-tenancy-web-apps
- https://frontegg.com/blog/next-js-authentication
- https://makerkit.dev/nextjs-saas-boilerplate
Featured Images: pexels.com