DEV Community

Cover image for Full-stack Without Coding a Backend Using Supabase
ymc9 for ZenStack

Posted on • Edited on • Originally published at zenstack.dev

Full-stack Without Coding a Backend Using Supabase

Backend development is difficult for people who come entirely from a frontend background. The languages, frameworks, and tools differ, but more importantly, the frontend and backend systems have very different principles. Fortunately, a new generation of libraries and services is trying to fill the gap and simplify coding a backend by …, not coding it at all.

To code a backend or not to code

In this post, let’s talk about Supabase - what it is, how it works, and what kind of scenarios it fits best.

What’s Supabase?

Supabase is an open-source cloud service, and you can understand it as one of the following two things:

  • A PostgreSQL hoster

    It allows you to create fresh Postgres instances with just a few clicks and use it in your application’s backend. It also provides a built-in SQL editor and a data browser for you to manage the database inside a browser.

  • A BaaS (Backend-as-a-Service)

    It offers a comprehensive set of services, including auth, database access control, blob storage, edge functions, etc. The capabilities allow you to use Supabase as your backend instead of implementing one by yourself.

In this article, let's explore Supabase in the context of BaaS.

Feature Package

Supabase provides five essential services:

  1. Auth authenticates users
  2. Database serves a standard PostgreSQL
  3. Realtime supports broadcasting messages and states to clients
  4. Edge Functions allows execution of custom typescript code
  5. Storage stores and serves files.

Each of these services can be used separately, but you'll likely use Auth together with everything else so that you can authorize users' requests.

Auth

The Auth service deals with user signup and authentication. It supports email, magic links, and a wide range of social login methods. If you're familiar with NextAuth, you can think of Supabase's Auth module as a reimplementation of NextAuth without the hard dependency on Next.js. What's neat about the Auth service is that, since it has a buddy Database service, it has a natural place to store user data, so you don't need to configure storage for it.

Auth service is at the centre of Superbase's architecture. It verifies a user's identity but doesn't control what she can or cannot do. The latter is called "authorization" and is handled by each of the other four services, respectively, but they rely on the verified identity to do that.

Database

Supabase sets itself apart from other BaaS with its strong heritage from PostgreSQL, and the Database service is proof of that. Database service directly inherited all the bells and whistles of Postgres - tables, views, functions, row-level security (RLS), etc.

The most significant addition Supabase added to Postgres is the "serverless APIs". Every table, view and function is automatically mapped to a RESTful and a GraphQL (still Beta) API. These APIs allow you to conduct CRUD operations directly from the frontend. But, of course, you must be immediately alarmed: what about security? Supabase delegates that entirely to PostgreSQL's RLS. You're supposed to use the "Auth" service to authenticate users and set up RLS to guard against unauthorized operations. A typical RLS looks like this:


# Simple RLS for blog posts

# owner has full access to her own posts
CREATE POLICY post_owner_policy ON post
    USING (owner = current_user);

# all users can read published posts
CREATE POLICY post_read_policy ON post FOR SELECT
    USING (published = true);

Enter fullscreen mode Exit fullscreen mode

The entire flow for an authorized request looks like:

Database auth flow

, and calling the database from the frontend looks like this:

// Initialize the JS client
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)

// Make a request
const { data: todos, error } = await supabase.from('todos').select('*')
Enter fullscreen mode Exit fullscreen mode

The overall idea is very similar to how PostgREST works. In fact, Supabase's RESTful layer is built with PostgREST. Please check out the following post if you're interested.

Realtime

Realtime is a utility service for subscribing to 3 types of real-time events: "Broadcast" for pub-sub pattern, "Presence" for synchronizing user's online status, and "Postgres Changes" for listening to database changes.

Realtime scenarios

It's worth noting that if you've configured RLS in database tables, the database changes notifications also respect RLS policies, and clients only get the messages they're supposed to see.

Storage

Supabase's storage offering is similar to blob storage services from most other cloud providers: a simple bucket/folder/file structure. Although Storage is not part of the SQL database store, its access control is configured with Postgres RSL, but how?

The idea is that, although blobs are stored elsewhere, their metadata resides in the SQL database as regular tables. So when you use RLS to specify Storage access policies, you're actually setting it up for its metadata. When the Storage service processes a request, it first checks with the Postgres side to see if it is permitted.

Edge Functions

Edge Functions are custom Typescript codes that you can deploy to Supabase and let them run on edge nodes, responding to client requests. Edge Functions uses Deno at runtime, apparently for better security and faster cold start; so supposedly, the Typescript code needs to stay within the boundary of what's supported by Deno.

Edge Functions has a simple way of supporting authorization. The client sends over the JWT token received during authentication. The edge function code gets the user's identity (extracted from the token) as its input, so it can use that to check permissions. If the function accesses the database, it can also create a Postgres client with the authenticated user's context, so the database's RLS policies can work their magic.

Is It a Good Choice For Me?

As you've seen, Supabase's BaaS offering is comprehensive. The combination of its services should suffice for many web apps of low to medium complexity. However, you may want to weigh the following non-functional factors before making a decision:

  1. Are you OK with using a 3rd party service as your entire backend?

    Supabase is open source, and you should be able to host it yourself. Still, right now, documentation for self-hosting is limited, and I've never heard or read anyone doing self-hosting for serious applications.

  2. Are you comfortable with working with PostgreSQL a lot?

    Supabase has a strong lineage from Postgres. You'll find yourself at home if you know Postgres well and may suffer if not. Solid knowledge of Postgres is needed to use it well.

  3. Do you like the idea of writing business logic in SQL?

    PostgreSQL's RLS is a powerful tool. It works as a very reliable gatekeeper to keep your data safe. However, as you use it more, you'll inevitably get into a situation where a big chunk of your business logic is expressed in SQL. IMHO, SQL language was not optimally designed for that.

  4. How about your application's data model not staying with the code anymore?

    If you use Supabase as a BaaS, you don't use an ORM anymore. Instead, you use serverless APIs (RESTful or GraphQL). As a result, one of the major benefits of an ORM - a centralized data model of your entire application within the source code - is lost.

Wrap Up

Supabase is an excellent product that covers many aspects of a web application's backend needs. It may significantly shorten your time-to-market. I hope this article can help you see the upsides and downsides of using it and finally make a sound decision.


P.S. We're building ZenStack — a toolkit for building secure CRUD apps with Next.js + Typescript. Our goal is to let you save time writing boilerplate code and focus on building what matters — the user experience.

Top comments (4)

Collapse
 
zmzlois profile image
Lois • Edited

PostgreSQL's RLS is a powerful tool. It works as a very reliable gatekeeper to keep your data safe. However, as you use it more, you'll inevitably get into a situation where a big chunk of your business logic is expressed in SQL. IMHO, SQL language was not optimally designed for that.

Umm then what language was designed for that? Javascript and other non-query languages?

If you use Supabase as a BaaS, you don't use an ORM anymore. Instead, you use serverless APIs (RESTful or GraphQL). As a result, one of the major benefits of an ORM - a centralized data model of your entire application within the source code - is lost.

I do feel like it's getting fragmented if I am calling supabase on every component, but if calling data from frontend is making things quicker, and we use either state mgmt tools or other context provider to make sure the state of the app is well managed, would that be ok and still ship things fast?
When you mention the benefit of a centralised data model, are you referring to something like this? diligent.com/insights/data-managem...

Collapse
 
ymc9 profile image
ymc9

I think the complexity of SQL is due to a large part of it is designed for complex analytics (OLAP) while most applications for their most part do relatively simple CRUD (OLTP). Prisma and Knex etc showed how pleasant it can be to program against databases with JS/TS. Maybe databases in the future will all have such kind of native bindings to popular programming languages as a built-in part (like what Supabase is trying to do).

For "the benefit of a centralized data model", I'm mainly thinking of a clear definition of your current database schema that resides in your code base, like Prisma's schema file or TypeORM's TS entity classes. With Supabase, it's easy to build up table structures with its visual builder, but then the schema becomes decoupled with your code and you need to think about how to keep them in sync.

Collapse
 
zmzlois profile image
Lois • Edited

Indeed and very true. There were one time we didn't write any row level policy, calling supabase directly from frontend, and a misplaced prisma file, with a simple pnpm db:push wiped cleaned our database. With tools like supabase and prisma I am almost forgetting how SQL query looks like when it gets even just a little bit more complex (relationship with multiple tables).

Luckily Supabase has back up to help us restore fast. The sacrifice of centralised approach brought us speed, but with some mistake our env. variable can easily exposed in frontend, and creating a lot of table never gets reused. And the most ridiculous thing is when our prisma file is not sync across team member we even need to send the prisma file separately (this is so dumb of us).

I am starting to read more about even-driven system design and database architecting and putting more consideration, even before I create a table.

Collapse
 
zmzlois profile image
Lois

In our use case, we also found that communicate with database using sdks caused us communication problem between developers.