Flutter + Supabase + Metabase - The Best Tech Stack Combo I Use to Build a Dental Management App as a Mobile Developer.
In this blog, I will share why I chose these 3 stacks, what my use case was, how they solved my problems, and what are limitations of these frameworks when building a full stack end-to-end app.
A month ago, I posted a tweet about the "Best Tech Stack Combo for Mobile Developer" on all social media platforms. It received a really good response compared to what I expected, with the majority of responses being positive.
Hence, I thought of providing some more details on :
Why I chose these 3 stacks.
What was my use case.
How they solve my problems.
What are the limitations of these frameworks.
I will keep this blog short and hope that by the end of it, you can decide whether it fits your needs or not.
Before we dive into the tech stack, let me tell you about what I was building. It's a dental clinic management app for my wife's clinic. She is a non-technical person and wants to understand how the clinic is doing by analyzing income, expenses, patient information including history and treatments, while providing services like appointments, reminders, and invoice generation.
I tried a few available apps on the market, but all of them had poor UX and a lot of features that we didn't even need (feature designed for a dental hospital with multiple receptionists, doctors, and staff).
We also needed some custom features, which I didn't find in other apps (such as integrating oral camera photos in our app). Therefore, I thought of building my own app. In this exploration of building a full end-to-end app, I chose these 3 stacks.
So let's start with the first tech stack, the heart of any software: The Database!
1. Supabase: Persist and Organize Data
The frontend, third-party services, and analytics are all connected to your database. The majority of scaling problems are also related to the database. The database needs to scale well when the time comes. Therefore, we want to make sure that we build the database right from the beginning.
And that was where I made my first mistake. I started off by using Google Sheets as my database. Yes, Google Sheets!
The idea was that since my data was simple, consisting of just patient information, income and expenses, and there was no direct relationship between those data , Google Sheets seemed like a good choice. Also, google Sheets is a lot more feature-rich when it comes to analyzing data through charts and tables, and any non-technical person can also use it.
If you are interested in the app, email me and I can give beta access to you.
It worked for a couple of months. But as with all software development goes, we started to have more features.
We started to have more patient information linking to history and treatment plans, which also got connected with income. So basically, we started to have more relationships among these data, and this is where things started to get complicated.
At that time, I thought of going with Firebase, but Firebase is also a NoSQL database and it would have the same problem as Google Sheets. I've had to hack the data relationships to make it work in NoSQL.
Then I came across Supabase. I chose it for multiple reasons.
1.1 Cheap and Easy to Get Started
Mobile developers don't get much time to play around with SQL. And, Supabase provides a very easy way to create tables, establish relationships among data, and take away all the hosting headaches for a newbie developer.
Supabase uses PostgreSQL under the hood. Check out this short video which covers how supabase work behind the scenes.
Now, someone might argue that we can pretty much build and setup PostgreSQL ourselves, but as a mobile developer, it will cost me a lot of time and money. If I do everything myself, then I have to:
Design and create a database schema manually.
Need to take care of hosting it, which also costs money.
Build an API on top of it.
Supabase provides a REST API and SDK for a variety of programming languages (JS, Python, Dart, etc.), which also allows flexibility to integrate Supabase with our own database or third-part services in the future.
All of this is available in a pretty generous free tier. But remember, nothing is free in this world. Everything has a cost, which I will discuss more in the "Problem with Supabase" section.
1.2 Build Relational Data Using PostgreSQL
I have previously used Firebase and MongoDB, which are NoSQL databases. Although they have client libraries that perform queries like SQL, it is still tricky to manage relationships with NoSQL.
In NoSQL, we have to normalize and denormalize data in order to make the queries efficient and cost-effective. Especially with Firebase, it can end up costing us a lot of money if it done wrong.
As I mentioned before, I started off with Google Sheets because I didn't have any relationships among the data. But once it started to grow, I needed a relational database.
The challenge for a mobile developer is the learning curve of understanding the database relationships, but the advantage with Supabase is that, its has a great UI/UX which helps us to set up this easily.
Once that is done, it provides a nice overview diagram of how those all these relationships look.
1.3 Authentication and Row Level Security (RLS)
Supabase takes care of all authentication work for us, including creating users, maintaining sessions, generating, validating, expiring, refreshing tokens, and resetting passwords.
Firebase Authentication does something similar, but Supabase does it differently. Supabase maintains all authentication information in a separate auth table. This allows, unlike Firebase, to migrate our users easily.
The Supabase auth approach becomes more powerful when it's used with RLS. Let's say we want the doctor to access the patient they've created, i.e. their own patients. But the same doctor cannot access or delete another doctor's patient.
We can achieve this at database level using RLS without writing any code.
First, we set the current authenticated user_id to the Patients table by using auth.uid() as the default value. i.e. whoever calls a create request for a Patients , it will automatically have their user_id.
And then we create an RLS policy for the Patients table. Remember, RLS is a PostgreSQL feature. Supabase provides a nice UX on top of it. Below, we set a policy for row access when it matches the auth.uid = user_id condition.
This is helpful when we want to restrict access to a row to the user who created it and do not want to write policies by hand. Checkout this video for more details on RLS.
Now clients only need to query directly without mentioning anything about user_id. Supabase SDK takes care of that.
Note : Sometimes RLS can cause issues. I spent a couple of hours trying to figure out why I was not getting required data from the table, just because I forgot to define auth.uid() = userId in the RLS Policy.
Also, we don't need RLS on all the tables. For example, Locations and Treatments can be public tables and can be access by anyone in the system.
1.4 Customizations and Integration
If we hit the limit of SDK API for our queries, then we can always use Functions in Supabase, which allows us to write custom queries and return the results in the format we prefer.
This was helpful for me when I wanted to create a monthly summary of expenses based on who paid them.
You might be wondering how did I write this complex SQL query If I don't know much about the database ? The answer is... ChatGPT and Claude.
This is where I find this tool really helpful. Write the requirements in Claude with the rough schema and ask it to generate queries, and voila, in a few seconds you get the query. Although I will caution that double-check those queries every time in local before using them in a function. Also, sometimes it makes mistakes that I need to correct manually.
I am planning to create a video on how we can leverage this tool as a Flutter Dev. Let me know in the comments if you are interested.
For integration, it has webhooks and edge-to-edge functionality, which I am currently working on. I will share the learning soon. So subscribe and stay tuned.
1.5 Problem With Supabase : Backup and Migrations
Nothing is free in this world. Everything comes with a cost. And for Supabase, it's backup and migrations.
For Backup, In the free tier, Supabase doesn't provide any backup. So if you delete a row or table by accident, there is no way to recover it unless you have their paid plan. In my opinion, 1 day or 1 week backup would be helpful in the free tier.
Since the app is small right now, it makes sense for me to use the free tier and deal with this problem manually. But since it's a PostgreSQL, Supabase provides a connection string which I can use to connect to my local machine and take a backup every day. You can easily write a script to do that. But it's still too much work for me.
Also, in the free tier, it provides up to 50k monthly active users. What if the app grows more than that? I think that's a good problem to have 😄. I haven't thought about it that much. We will see when we get there.
For migration, since it's a PostgreSQL, I am not getting locked into Supabase like Firebase. It's just that I can take the dump manually and then migrate to another system. Obviously, migration is always a pain, but it's a much better option than others I have used.
Also, migrating everything together is hard. The best way to do it is in small steps. If you are planning to host your own database, then start all the new work in the new database and start using Supabase client and admin libraries to integrate it with your backend. You can also use webhooks and edge-to-edge functions to do that.
2. Flutter : Collect Data From Everywhere
The majority of the negative comments on my post were against Flutter, especially on Reddit. This is because I posted it on the Supabase subreddit, where I am assuming the majority of developer are from the web and supporting React Native.
Its fine, I am getting this kind of comment since Flutter first stable release 7 years ago.
The reason I chose Flutter is because it's just a "Form Over Data" application and it doesn't need any platform-specific features (yet!!). And that's where Flutter shines.
I've already shared my opinion on Flutter many times, so I am not going to repeat it here.
Although, we need to remember that Flutter has now evolved from cross-platform to multi-platform. It is not just for mobile apps anymore. It covers Desktop and Web.
Problem with Flutter
The web is something I still feel needs a lot of work. Flutter web is for apps and not websites. And for the "Form over Data" application, it feels slow to me.
Also, If you are moving from native without Compose or SwiftUI experience, it would be challenging to get started. The paradigm shift takes time. I wrote about my this transition 7 years ago.
For most use cases, we use Flutter plugins, but customizing a plugin can be challenging. For example, when sending an invoice through WhatsApp, I used whatsapp_share, but unfortunately it does not support iOS.
We can use Pigeon for boilerplate code, but we still need to learn the native platforms.
3. Metabase : Analyze and Generate Business Insights
The goal of business is to make money. In order to make money, we need to understand how our business is doing and what our bottlenecks are. To understand this, we need to collect and organize data in a way that we can generate insights on how we can improve it. Because...
What get measured, get improved
While searching for the "Best analytics tools", I came across Grafana and Clickhouse. I found them to be very feature-rich and complex for my simple use case.
So I changed my search query to "Best Chart Visualization tool for Supabase" and that's where I came across Metabase, an open-source visualization tool. Again, I could have used d3js but then I would have had to set up everything myself which I wanted to avoid in the first place.
I liked Metabase for a few reasons.
3.1 Easy to setup.
To run Metabase, we just need Docker. By copying and pasting a command, Metabase can be running on our local machine within a minute. It literally took 2 minutes to set up.
Connect Supabase to Metabase using a database connection string.
Note: Make sure you are using parameters under "Session pooler" and the NOT Direct connection string in Supabase. I spent a few hours figuring out why my database connection was failing on Metabase.
Checkout this supabase blog for more details.
3.2 Start with Business Question
To build any chart, the UX starts by asking a question. This allows me to think about what business problem I am trying to solve rather than just focusing on the technical part of it.
If I don't have any questions, I use ChatGPT for some brainstorming and use that to build my visualization.
Once I had the question, I started to look for the table and began connecting with other tables. This is where the database relationship helps a lot. We can pull information from other tables using primary-foreign key relationships.
For example, I built a chart on Patient Trend below by joining multiple tables.
3.3 Customization
If you cannot build or connect the table the way you want, there is always the option to run a custom query and then start building visualization on top of it.
There are tons of options to customize the chart as well. You can check out this video for all the details. Once you have multiple charts, you can build the dashboard from them.
The good part about Metabase is that a non-technical person (someone who knows Excel) can also build this dashboard.
3.4 Problem with Metabase
Building complex structures from Metabase UI is challenging. Sometimes I find it easier to write a custom query and build a chart directly on top of it rather than fighting with Metabase UI. There is also a learning curve to understand statistics and math terminologies.
No free hosting like Supabase. Although I am happy with the local setup because I am the only one managing it, but if I need to move to the cloud, I need to either pay for Metabase cloud or find my own hosting and migrate all the setup I did locally to that hosting.
That's it folks.
These are my learnings on building a full end-to-end dental clinic management app. If you have any questions or learnings from these tools, or if there is something I can do better, please let me know in the comments.
Also, if you are interested in the app, please email me at burhanrashid5253@gmail.com and I can give you beta access.
I am currently open for consultation part-time/full-time specialized in mobile development with Android and Flutter. So if you are looking for someone to:
Build product architecture from scratch
Train existing developers to level up
Fix major bottlenecks in legacy codebase
Improve code quality
And most importantly ship things faster
then reach out to me at burhanrashid5253@gmail.com.