Typesafe Supabase Flutter Queries
Generate Flutter / Dart 🎯 classes from your Supabase schema.
// allBooks is a typeof List<Books>
final allBooks = await supabase
.books
.select("*")
.withConverter(Books.converter);
- 🌐 Cli and Web App
- 🛠️ Typesafe Queries (Create, Read, Equality)
- 🧱 Immutable Generated Classes
- 🗂️ Roundtrip Serialization fromJson to toJson and back
- 📊 Supports Column Selection Queries
- 🔢 Supports all Supabase Major datatypes
- 🗂️ Supports Defined as array types
- 🗂️ Supports Enums
We don't recommend using serial types when using this package, if you have serial types you need to add a [supadart:serial]
to the column like this
You probably don't have them Serial types aren't available in the Supabase editor and must be added via SQL editor manually.
COMMENT ON COLUMN test_table.bigserialx IS '[supadart:serial]';
COMMENT ON COLUMN test_table.smallserialx IS 'you can still add comment [supadart:serial]';
COMMENT ON COLUMN test_table.serialx IS 'this part [supadart:serial] just needs to be included';
-- otherwise the insert method will always ask for a value even though serial types are auto-generated
# This is an official package from dart and is used for parsing dates
flutter pub add intl
# or
dart pub add intl
Unless you are not using any date types, you can skip this step
this tool will automatically convert snake_case to camelCase for both table (GeneratedClassName) and column (FieldName) generated names.
snake_case => camelCase
user_table => UserTable
snake_case => camelCase
user_id => userId
Using the Web App (no enum support)
# 🎯 Active from pub.dev
dart pub global activate supadart
# 🚀 Run via
supadart
# or
dart pub global run supadart
# Initialize supadart.yaml config file
supadart --init
# Generate classes
supadart --url <supabase_url> --key <supabase_anon_key>
# if SUPABASE_URL and SUPABASE_ANON_KEY are set in the environment variables
# if SUPABASE_URL and SUPABASE_ANON_KEY are set in supadart.yaml
supadart
ENUMS: If you have enums, you need to specify them in the config file
#### CLI Usage
```bash
-h, --help Show usage information
-i, --init Initialize config file supadart.yaml
-c, --config Specify a path to config file of yaml (default: ./supadart.yaml)
-u, --url Supabase URL (if not set in yaml)
-k, --key Supabase ANON KEY (if not set in yaml)
-v, --version
Assuming the following table schema
create table
public.books (
id bigint generated by default as identity,
name character varying not null,
description text null,
price integer not null,
created_at timestamp with time zone not null default now(),
constraint books_pkey primary key (id)
) tablespace pg_default;
1. Use the CLI or the Web App to generate dart classes
class Books implements SupadartClass<Books> {
final BigInt id;
final String name;
final String? description;
final int price;
final DateTime? createdAt;
const Books({
required this.id,
required this.name,
this.description,
required this.price,
this.createdAt,
});
static String get table_name => 'books';
static String get c_id => 'id';
static String get c_name => 'name';
static String get c_description => 'description';
static String get c_price => 'price';
static String get c_createdAt => 'created_at';
static List<Books> converter(List<Map<String, dynamic>> data) {
return data.map(Books.fromJson).toList();
}
static Books converterSingle(Map<String, dynamic> data) {
return Books.fromJson(data);
}
static Map<String, dynamic> _generateMap({
BigInt? id,
String? name,
String? description,
int? price,
DateTime? createdAt,
}) {
return {
if (id != null) 'id': id.toString(),
if (name != null) 'name': name.toString(),
if (description != null) 'description': description.toString(),
if (price != null) 'price': price.toString(),
if (createdAt != null) 'created_at': createdAt.toUtc().toString(),
};
}
static Map<String, dynamic> insert({
BigInt? id,
required String name,
String? description,
required int price,
DateTime? createdAt,
}) {
return _generateMap(
id: id,
name: name,
description: description,
price: price,
createdAt: createdAt,
);
}
static Map<String, dynamic> update({
BigInt? id,
String? name,
String? description,
int? price,
DateTime? createdAt,
}) {
return _generateMap(
id: id,
name: name,
description: description,
price: price,
createdAt: createdAt,
);
}
factory Books.fromJson(Map<String, dynamic> json) {
return Books(
id: json['id'] != null
? BigInt.parse(json['id'].toString())
: BigInt.from(0),
name: json['name'] != null ? json['name'].toString() : '',
description:
json['description'] != null ? json['description'].toString() : '',
price: json['price'] != null ? json['price'] as int : 0,
createdAt: json['created_at'] != null
? DateTime.tryParse(json['created_at'].toString()) as DateTime
: DateTime.fromMillisecondsSinceEpoch(0),
);
}
Map<String, dynamic> toJson() {
// Promotion doesn't work well with public fields due to the possibility of the field being modified elsewhere.
return _generateMap(
id: id,
name: name,
description: description,
price: price,
createdAt: createdAt,
);
}
}
we now have a typesafe'ish to interact with the database.
// allBooks is a typeof List<Books>
final allBooks = await supabase
.books
.select("*")
.withConverter(Books.converter);
// book is a typeof Books
final book = await supabase
.books
.select("*")
.eq(Books.c_id, 1)
.single()
.withConverter(Books.converterSingle);
// Yes we know which one's are optional or required.
final data = Books.insert(
name: 'Learn Flutter',
description: 'Endless brackets and braces',
price: 2,
);
await supabase.books.insert(data);
final many_data = [
Books.insert(
name: 'Learn Minecraft',
description: 'Endless blocks and bricks',
price: 2,
),
Books.insert(
name: 'Description is optional',
created_at: DateTime.now(),
price: 2,
),
];
await supabase.books.insert(many_data);
final newData = Books.update(
name: 'New Book Name',
);
await supabase.books.update(newData).eq(Books.c_id, 1);
await supabase.books.delete().eq(Books.c_id, 1);
IMPORTANT:
- You need to specify your enums on supadart.yaml config file
- Enum
names
are converted toUPPERCASE
to follow dart enum naming conventions - Enum
values
are case-sensitive in postgres, so you need to specify them as they are in the database - Optional: We recommend defining the enum values as lowercase to follow dart enum naming conventions
Assuming the following schema
-- We recommend defining the enum values as lowercase to follow dart enum naming conventions
CREATE TYPE mood AS ENUM ('happy', 'sad', 'neutral', 'excited', 'angry');
CREATE TABLE enum_types (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
mood mood NOT NULL
);
In your supadart.yaml config file
enums:
# Case sensitive, define them as they are in the database
mood: [happy, sad, neutral, excited, angry]
enum MOOD { happy, sad, neutral, excited, angry }
MOOD firstEnumVal = MOOD.angry;
MOOD newEnumVal = MOOD.excited;
// Create
await supabase.enum_types.insert(EnumTypes.insert(
mood: firstEnumVal,
));
await supabase.enum_types
// Update
.update(EnumTypes.update(mood: newEnumVal))
// Equality ⚠️ you need to do manual ⬇️ enum to string conversion
.eq(EnumTypes.c_mood, firstEnumVal.toString().split(".").last);
// Read
await supabase.enum_types.select().withConverter(EnumTypes.converter);
IMPORTANT:
- When you select columns, the class will fill the missing columns with the default values
final book = await supabase
.from('books')
.select('${Books.c_id}, ${Books.c_name}')
.eq(Books.c_id, 69) // Assuming 69 is the id
.single()
.withConverter(Books.converterSingle);
print(book.id); // 69
print(book.name); // "Supadart"
print(book.description); // ""
print(book.price); // 0
print(book.created_at); // 1970-01-01 00:00:00.000
if a value is an enum, the first value of that enum will be used as the default value