Supapod : supabase 🚀 + serverpod 🐎 = :fire:
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
- A Serverpod project.
- A Supabase project: You'll need your Supabase Project URL and Anon Key.
- 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
-
Add
supapod
to your Serverpod project'spubspec.yaml
file in thedependencies
section:dependencies: serverpod: # your serverpod version supapod: ^latest_version # Replace with the latest version from pub.flutter-io.cn
-
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 yourYourModel
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 andSupabase.instance
calls intry-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:
- Fork the repository.
- Create a new branch for your feature or bug fix.
- Make your changes.
- Add tests for your changes.
- Ensure all tests pass.
- 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