MakerKit Data Model
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
/userscollection 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 dataWe 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
emailproperty 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
roleis the assigned role, which cannot beOwner - the
codeproperty is the unique identifier from which the user can access the invite through a link - the
expiresAtproperty is the Unix timestamp when the invite will expire. By default, it's going to be one week - the
organizationobject duplicates some valuable data from its parent (which saves us from rereading the document)