A key feature of these software solutions is real-time data sync to the client—meaning the database and the client should theoretically always be in sync. Additionally, not needing a traditional backend due to client-side database access secured with a new concept - row level security - allowing you to write rules to define what a user can and cannot access, based on the user’s id.
Context
- I was building a web app that relied on asynchronous backend workflows maintaining state that may be visible on the screen
- It was critical that the web app be:
- Client first (even if not local first/offline capable)
- Highly responsive, meaning data should be preloaded in almost all cases.
- Able to respond to changes on the backend.
- It needed to support sharing links to content, creating workspaces, and managing content within workspaces with inherited permissions that automatically sync. It also needed to allow users to invite external users and support real-time editing using a CRDT tool.
- This was achieved by making everything belong to a workspace (with each user getting a personal workspace) and using many-to-many SQL relationships between entities.
Row Level Security
Initially, Supabase’s marketing made it look like the correct solution for my use case. The problem came with overly complex rules when controlling access to each individual piece of content, using their “Row Level Security” concept, I would have to check the following list:
- If the content had public link access
- If the user was invited to the content
- If the user was invited to the organization that owned the content
This causes significant extra complexity in rules that are already tedious to write, even when simple, compared to basic route-level security with a traditional backend. Now, let’s also consider that the content has multiple sub-pieces (small pieces of data in different tables), which makes the process even worse. Firebase uses the same role level security concept, meaning it has the same issues, however as it is a document store I could use nested information instead of sub-pieces of data in different tables.
However, this initially seemed possible, only difficult, so I moved forward.
Authentication
Minimal built-ins
Supabase’s built-in authentication was minimal and didn’t provide many built-in tools compared to other open tools like better-auth (watch out for my better-auth post later on, it is not perfect either), or managed services like WorkOS or Clerk.
Consider the use case of adding Organizations to an application - in Supabase, you need to code this completely yourself. With better-auth, you just add the plugin, with Clerk, you turn it on in the dashboard and with WorkOS it it built in - that is their main selling point. Firebase similarly lacks built-in organization support and requires custom implementation, though it does offer more extensive third-party identity provider integrations out of the box.
Also, consider Captcha support. Supabase does not have its own CAPTCHA support, and you need to use a third-party service. Firebase provides built-in reCAPTCHA integration for phone authentication, supporting both visible and invisible reCAPTCHA, which helps prevent abuse during phone number verification. However, like Supabase, Firebase doesn’t offer built-in CAPTCHA support for email/password authentication flows.
To be fair, both Supabase and Firebase allow you to use a third-party auth service, but that significantly degrades the all-in-one experience, and you need to handle all the auth flows yourself, including adding role-level security rules that parse the JWT to ensure users have passed 2FA. They do also allow Auth0, but Auth0 is Auth0 (you will know what I mean if you ever used it before).
However, even if you go to the effort of setting up third party auth, support for things like organizations is still not easily achievable in either platform.
Lack of Automation
Let’s say you want to store the user’s name, date of birth, and similar information and access that through the API. You will need to create another table for that, manage it yourself, and keep it in sync when a user creates or deletes an account with Supabase. Firebase is the same as Supabase in this regard - both require manual management of user profile data beyond basic authentication information.
On the topic of account deletion, Supabase provides no documented API method to fully delete accounts. better-auth makes this extremely easy. WorkOS and Clerk both provide this functionality in an easy way also. Firebase does offer account deletion capabilities through its Admin SDK, but requires additional setup compared to dedicated auth services.
Supabase functions only allow for Deno functions
Supabase functions only allow for Deno functions, which can be frustrating when the rest of your codebase is built for Node.js or serverless environments. Traditionally, native binding libraries have not worked with Deno, and even if this has recently improved, it is still not quite where it should be, especially considering Deno 2 support is still in alpha.
If you use a web framework with Node-powered build tools (Next.js, Nuxt, SvelteKit, SolidStart, or similar), you’ll need to use Deno’s version of the framework’s tooling, including its package manager. This essentially requires setting up the framework again or dealing with a split codebase where half uses Node.js and half uses Deno, with shared code unable to reference packages from both ecosystems.
I will give them credit for having WebSockets working in edge functions, however, as this is problematic with some other services like Firebase or even traditional serverless services like Cloudflare Workers (yes, Durable Objects exist, but they have significant usability issues).
Conclusion
While Supabase, Firebase, and similar BaaS solutions offer tempting quick starts with their real-time capabilities and client-side database access, they often fall short when dealing with complex application requirements. The limitations become particularly apparent when implementing:
- Complex Access Control: Row-level security, while powerful in theory, becomes unwieldy with nested permissions and organizational structures.
- Authentication Gaps: The lack of built-in support for common features like organizations and flexible captcha solutions adds significant development overhead.
- Technical Constraints: Being locked into Deno for serverless functions can create unnecessary friction in your development workflow.
When to Consider BaaS
These platforms might still be a good fit if:
- You’re building a side project that does not require sharing, other than link sharing (if I was writing an admin panel for a personal blog, or writing a todo app to learn a new framework, Supabase or Firebase could still be a good fit)
- You are getting started as a developer and want to focus on writing great web apps, not on backend logic.
For fucks sake, just use NextJS/SvelteKit/Nuxt/SolidStart/similar
Seriously though, using a framework, telefunc/trpc/similar, tanstack query (react query), and a managed database (and maybe even a managed auth service, otherwise better-auth works great for me) is all that most applications need. And, if you find you need more, you can always integrate an alternative service like Convex that lets you implement access controls in backend code, while still allowing for full reactivity - remember, when using SQL, most changes are one migration away, whether it is a schema change or a database service migration.
Summary
The promise of “no backend” often leads to significant technical debt as applications grow in complexity. While BaaS solutions can accelerate early development, they may not be the most cost-effective or maintainable choice in the long run. Evaluate your specific needs carefully before committing to any platform, and don’t be afraid to invest in a more customizable solution if your requirements demand it.
Future Thought
For me, as my realtime features were almost all just the CRDT or could handle basic refresh-every-ten-seconds logic, I ended up using Cloudflare Workers for my backend, connected to better-auth, with a one database per organization model. And, best of all, I can still have reactivity around background functions with server sent events, where reloads are just not enough.
A friend has however recommended Appwrite, which I will be trying out soon, as they seem to have a much nicer permissions-in-code model that will likely give me most of what I need, while allowing me to get easy full reactivity. They seem to be going with a multi-tenant native model, which seems great. However, captcha in auth is still a problem.
Alternatively, I could also end up on Convex, which would resolve a lot of my issues, while giving me reactivity, but quite honestly I do not need the reactivity enough to complicate the tech stack with an additional service, if it does not simplify enough things. If I write a more reactive app, I will most likely end up on Convex with WorkOS for auth, unless AppWrite sorts out auth sooner.