Supapod : supabase 🚀 + serverpod 🐎 = :fire:

pub package License: MIT

Seamlessly integrate Supabase into your Serverpod applications. Use Supabase as your primary database backend with familiar Serverpod-style syntax, and get full access to Supabase's powerful features like Authentication, Realtime, and Storage.

supapod acts as a compatibility layer, allowing you to leverage Serverpod's code generation for models and table definitions while interacting with a Supabase database.

Features

  • Serverpod-like Database Operations: Use familiar methods like insertRow, findFirstRow, find, updateRow, deleteRow directly on your Supabase tables.
  • Leverages Serverpod's Code Generation: Supapod intelligently uses the table and column definitions generated by Serverpod (*.generated.dart files) for type-safe queries and correct data mapping.
  • Full Supabase SDK Access: Easily access other Supabase services:
    • Supabase.instance.auth for user authentication.
    • Supabase.instance.storage for file storage.
    • Supabase.instance.realtime for live data synchronization.
    • Supabase.instance.functions for serverless functions.
  • Easy Setup: Minimal configuration to get started.

Why Supapod?

  • Best of Both Worlds: Combine Serverpod's robust server-side framework and code generation with Supabase's managed backend-as-a-service features.
  • Familiar Syntax: If you're used to Serverpod's database interaction, supapod provides a very similar experience for Supabase, reducing the learning curve.
  • Rapid Development: Quickly build powerful backends by leveraging Supabase's out-of-the-box solutions for common needs.

Prerequisites

  1. A Serverpod project.
  2. A Supabase project: You'll need your Supabase Project URL and Anon Key.
  3. Important: Your Supabase tables and columns should match the structure defined in your Serverpod model files (*.spy.yaml). supapod uses Serverpod's generated code for table/column names and types but does not handle schema migrations to Supabase. You need to ensure your Supabase schema is compatible.

Installation

  1. Add supapod to your Serverpod project's pubspec.yaml file in the dependencies section:

    dependencies:
      serverpod: # your serverpod version
      supapod: ^latest_version # Replace with the latest version from pub.flutter-io.cn
    
  2. Run dart pub get in your Serverpod project's root directory.

Setup

Initialize Supabase when your Serverpod server starts. This is typically done in your server.dart file.

// server.dart
import 'package:serverpod/serverpod.dart';
import 'package:supapod/supapod.dart'; // Main supapod import

import 'src/generated/protocol.dart';
import 'src/generated/endpoints.dart';

// This will initialize the Supabase client
Future<void> initializeSupabase() async {
  const supabaseUrl = 'YOUR_SUPABASE_URL';        // Replace with your Supabase Project URL
  const supabaseAnonKey = 'YOUR_SUPABASE_ANON_KEY'; // Replace with your Supabase Anon Key

  await Supabase.initialize(
    url: supabaseUrl,
    anonKey: supabaseAnonKey,
    // Optional:
    // authOptions: const AuthClientOptions(),
    // debug: true,
  );
  print('Supabase initialized!');
}

void run(List<String> args) async {
  // Initialize Supabase BEFORE Serverpod starts
  await initializeSupabase();

  // Initialize Serverpod and connect it with arguments.
  final pod = Serverpod(
    args,
    Protocol(),
    Endpoints(),
  );

  // Start the server.
  await pod.start();
}

Usage Example

Let's assume you have a Serverpod model defined in greeting.spy.yaml in serverpod:

# greeting.spy.yaml

class: Greeting
table: greeting # This will be the table name in Supabase
fields:
  message: String
  author: String
  timestamp: DateTime

After running serverpod generate, you'll have Greeting and GreetingTable models available.

Here's how you can use supapod in an endpoint:

// initialize.dart

import 'greeting.dart';
import 'package:supapod/supapod.dart';
import 'package:supapod/supapod.dart' show Supabase, SupabaseRepository;

void initializeSupabase() {
  Supabase.initialize(
    supabaseUrl: 'SUPABASE_URL',
    supabaseAnonKey: 'SUPABASE_ANON_KEY',
  );

  var greetingInstance = GreetingSupabaseRepository(Greeting.t);
  Supabase.register<Greeting>(Greeting.t, greetingInstance);

  // --- Initialize all your auto-generated Models here like above ---
}

// --- Initialize all your auto-generated Models here like above ---

class GreetingSupabaseRepository extends SupabaseRepository<Greeting> {
  GreetingSupabaseRepository(super._table);
}
// greeting_endpoint.dart
import 'package:serverpod/serverpod.dart';
import 'package:supapod/supapod.dart'; // Import supapod

// Import your Serverpod generated model
import '../generated/greeting.dart';

class GreetingEndpoint extends Endpoint {
  Future<Greeting> hello(Session session, String name) async {
    final greeting = Greeting(
      message: 'Hello $name from Supapod!',
      author: 'Supapod User',
      timestamp: DateTime.now().toUtc(),
    );

    // The usual command in serverpod to interact with postgres database
    Greeting.db.insertRow(session, greeting);
    Greeting.db.findFirstRow(session, where: (t) => t.author.equals('John Doe'));
    
    // supapod command to interact with supabase database
    Supabase.db<Greeting>()?.insertRow(session, greeting);
    
    // Supapod internally handles the tables and columns as well from the auto-generated code from serverpod
    // so you can expect column names and types to be correct
    Supabase.db<Greeting>()?.findFirstRow(session, where: (t) => (t as GreetingTable).author.equals('John Doe'));
    
    // with obviously all the goodies that supabase provides like Auth, Realtime, Storage
    Supabase.instance.storage.from('avatars').getPublicUrl('public/avatar1.png');

    return greeting;
  }
}

How Supabase.db<T>() works:

  • Supabase.db<YourModel>() gives you a specialized database handler for your YourModel type.
  • This handler uses Serverpod's generated YourModelTable (e.g., GreetingTable) to understand the table structure, column names, and data types.
  • When you write where: (t) => (t as YourModelTable).columnName.equals('value'), supapod translates this into a Supabase query filter.

Supported Operations

Supabase.db<T>() aims to support common operations:

  • insertRow(session, object)
  • insertRows(session, objects)
  • findFirstRow(session, where: ..., orderBy: ..., orderDescending: ..., offset: ...)
  • find(session, where: ..., orderBy: ..., orderDescending: ..., limit: ..., offset: ...)
  • findById(session, id)
  • updateRow(session, object)
  • updateRows(session, objects)
  • deleteRow(session, where: ...)
  • deleteRows(session, where: ...)
  • count(session, where: ...)

Important Considerations

  • Schema Management: As mentioned, supapod does not handle database schema migrations. You can ensure your Supabase database schema (table names, column names, types, constraints, RLS policies) matches your Serverpod model definitions with the serverpod --apply-migration on the supabase database directly.
  • Row Level Security (RLS): Leverage Supabase's powerful RLS policies for fine-grained access control. supapod operations will respect these policies. Ensure your RLS is set up correctly for the operations your server intends to perform.
  • Transactions: Supabase itself doesn't offer multi-statement transactions in the same way traditional SQL databases do over a single client connection. If you need atomicity for multiple operations, you'll typically use Supabase Edge Functions (Database Functions - plpgsql). supapod itself won't provide cross-table transactions since the underlying Supabase client doesn't.
  • Error Handling: Wrap Supabase.db calls and Supabase.instance calls in try-catch blocks to handle potential errors from Supabase (e.g., network issues, RLS violations, data conflicts).

Contributing

Contributions are welcome! If you'd like to contribute, please:

  1. Fork the repository.
  2. Create a new branch for your feature or bug fix.
  3. Make your changes.
  4. Add tests for your changes.
  5. Ensure all tests pass.
  6. Submit a pull request with a clear description of your changes.

License

This package is licensed under the MIT License - see the LICENSE file for details

Libraries

supapod