The MakerKit Data Model for your SaaS project built with Next.js and Firebase
Learn how MakerKit defines the model of your SaaS application with Next.js and Firebase
MakerKit's data model is voluntarily as simple as possible, with a limited set of assumptions.
MakerKit is not supposed to be a finite product but a solid foundation on which it is quick to get started and build your SaaS product.
To summarize, the Firestore model contains three main entities: users
, organizations
, and invites
.
- Users: a Firestore representation of the authentication database. In fact, to store data about users, we need to create a user record within our
/users
collection in Firestore - Organizations: Organizations are groups of users. Here, we store the list of members that belong to the organizations and everything that needs to be shared between the users: for example, templates, settings, integrations, and so on.
- Invites: invites are a sub-collection of organizations used to store invitations that users can accept.
Users
Users are, of course, foundational to any application.
If a user signed in using Firebase Authentication, it merely means we have verified their credentials, not that they have an account.
We have to create an additional collection to store a user's data, such as:
- settings
- the memberships they belong to
- any other information which is not possible to update using their Authentication record (profile photo, name, email, and sign-in providers)
We only store the user's organizations as an object on the users
collections by default.
The Firestore model looks like something similar:
- users - [userId]: - ... user data
We store the relationships between users and organizations within the Organizations
collection.
Organizations
Organizations
are groups of users.
If the name doesn't suit your domain, don't worry: you can always change the terminology using the localization files.
A user can belong to one or many organizations: each organization lists its members in an object, similar to what we do with users
.
Below is what the Organizations
schema looks like:
- organizations - [organizationId] - name - timezone - members - [memberId] - role - user // this is a Firestore reference }
Of course, we can assign a name
to every organization and a timezone
property (before you realize you're going to need it).
We define each membership by the user ID. Each membership contains the following information:
- the user role
- the user reference to
/users/[userId]
Thus, we store the role in both collections to reduce the number of reads when we query the project members: we need to keep them in sync.
User Roles
By default, MakerKit defines three roles: Owner (can be only one), Admin, and Member.
This enum is hierarchical and associated with a number:
enum MembershipRole { Member, // 0 Admin, // 1 Owner // 2}
An Admin can do anything a member can do, and an Owner can do anything an Admin can.
Invites
To create an invite to join an organization, we use a new collection invites
placed below an organization
document: for example, /organizations/1/invites/1
.
The invite's interface is also straightforward:
interface MembershipInvite { code: string; email: string; role: MembershipRole; expiresAt: number; organization: { id: string; name: string; };}
- the
email
property represents the email of the user invited. The application supports users signing up with a different email: you have to change this if you wish to disallow it - the
role
is the assigned role, which cannot beOwner
- the
code
property is the unique identifier from which the user can access the invite through a link - the
expiresAt
property is the Unix timestamp when the invite will expire. By default, it's going to be one week - the
organization
object duplicates some valuable data from its parent (which saves us from rereading the document)