AI Development Guide - Dart SDK
This guide provides a comprehensive, fast reference for AI systems to quickly develop applications using the BosBase Dart SDK. All examples are production-ready and follow best practices.
Table of Contents
- Authentication
- Initialize Collections
- Define Collection Fields
- Add Data to Collections
- Modify Collection Data
- Delete Data from Collections
- Query Collection Contents
- Add and Delete Fields from Collections
- Query Collection Field Information
- Upload Files
- Query Logs
- Send Emails
Authentication
Initialize Client
import 'package:bosbase/bosbase.dart';
final pb = Bosbase('http://localhost:8090');
Password Authentication
// Authenticate with email/username and password
final authData = await pb.collection('users').authWithPassword(
'user@example.com',
'password123',
);
// Auth data is automatically stored
print(pb.authStore.isValid); // true
print(pb.authStore.token); // JWT token
print(pb.authStore.record); // User record
OAuth2 Authentication
// Get OAuth2 providers
final methods = await pb.collection('users').listAuthMethods();
print(methods.oauth2.providers); // Available providers
// Authenticate with OAuth2
final authData = await pb.collection('users').authWithOAuth2(
'google',
(url) async {
// Open OAuth2 URL
await launchUrl(url);
},
);
OTP Authentication
// Request OTP
final otpResponse = await pb.collection('users').requestVerification('user@example.com');
// Authenticate with OTP
final authData = await pb.collection('users').authWithOTP(
otpResponse.otpId,
'123456', // OTP code
);
Check Authentication Status
if (pb.authStore.isValid) {
print('Authenticated as: ${pb.authStore.record?.email}');
} else {
print('Not authenticated');
}
Logout
pb.authStore.clear();
Initialize Collections
Create Base Collection
final collection = await pb.collections.create(body: {
'name': 'posts',
'type': 'base',
'fields': [
{
'name': 'title',
'type': 'text',
'required': true,
},
],
});
print('Collection ID: ${collection.id}');
Create Auth Collection
final authCollection = await pb.collections.create(body: {
'name': 'users',
'type': 'auth',
'fields': [
{
'name': 'name',
'type': 'text',
'required': false,
},
],
'passwordAuth': {
'enabled': true,
'identityFields': ['email', 'username'],
},
});
Create View Collection
final viewCollection = await pb.collections.create(body: {
'name': 'published_posts',
'type': 'view',
'viewQuery': 'SELECT * FROM posts WHERE published = true',
});
Get Collection by ID or Name
final collection = await pb.collections.getOne('posts');
// or by ID
final collection = await pb.collections.getOne('_pbc_2287844090');
Define Collection Fields
Add Field to Collection
final updatedCollection = await pb.collections.addField('posts', {
'name': 'content',
'type': 'editor',
'required': false,
});
Common Field Types
// Text field
{
'name': 'title',
'type': 'text',
'required': true,
'min': 10,
'max': 255,
}
// Number field
{
'name': 'views',
'type': 'number',
'required': false,
'min': 0,
}
// Boolean field
{
'name': 'published',
'type': 'bool',
'required': false,
}
// Date field
{
'name': 'published_at',
'type': 'date',
'required': false,
}
// File field
{
'name': 'avatar',
'type': 'file',
'required': false,
'maxSelect': 1,
'maxSize': 2097152, // 2MB
'mimeTypes': ['image/jpeg', 'image/png'],
}
// Relation field
{
'name': 'author',
'type': 'relation',
'required': true,
'collectionId': '_pbc_users_auth_',
'maxSelect': 1,
}
// Select field
{
'name': 'status',
'type': 'select',
'required': true,
'options': {
'values': ['draft', 'published', 'archived'],
},
}
Update Field
final updatedCollection = await pb.collections.updateField('posts', 'title', {
'max': 500,
'required': true,
});
Remove Field
final updatedCollection = await pb.collections.removeField('posts', 'old_field');
Add Data to Collections
Create Single Record
final record = await pb.collection('posts').create(body: {
'title': 'My First Post',
'content': 'This is the content',
'published': true,
});
print('Created record ID: ${record.id}');
Create Record with File Upload
import 'package:http/http.dart' as http;
final file = await http.MultipartFile.fromPath('image', '/path/to/image.jpg');
final request = http.MultipartRequest(
'POST',
Uri.parse('${pb.baseURL}/api/collections/posts/records'),
);
request.fields['title'] = 'Post with Image';
request.files.add(file);
request.headers['Authorization'] = 'Bearer ${pb.authStore.token}';
final response = await request.send();
final responseBody = await response.stream.bytesToString();
final record = RecordModel.fromJson(jsonDecode(responseBody));
Create Record with Relations
final record = await pb.collection('posts').create(body: {
'title': 'My Post',
'author': 'user_record_id', // Related record ID
'categories': ['cat1_id', 'cat2_id'], // Multiple relations
});
Batch Create Records
final records = await pb.batch([
{
'method': 'POST',
'url': '/api/collections/posts/records',
'body': {'title': 'Post 1'},
},
{
'method': 'POST',
'url': '/api/collections/posts/records',
'body': {'title': 'Post 2'},
},
]);
Modify Collection Data
Update Single Record
final updated = await pb.collection('posts').update('record_id', body: {
'title': 'Updated Title',
'content': 'Updated content',
});
Update Record with File
import 'package:http/http.dart' as http;
final file = await http.MultipartFile.fromPath('image', '/path/to/new_image.jpg');
final request = http.MultipartRequest(
'PATCH',
Uri.parse('${pb.baseURL}/api/collections/posts/records/record_id'),
);
request.fields['title'] = 'Updated Title';
request.files.add(file);
request.headers['Authorization'] = 'Bearer ${pb.authStore.token}';
final response = await request.send();
Partial Update
// Only update specific fields
final updated = await pb.collection('posts').update('record_id', body: {
'views': 100, // Only update views
});
Delete Data from Collections
Delete Single Record
await pb.collection('posts').delete('record_id');
Delete Multiple Records
// Using batch
await pb.batch([
{
'method': 'DELETE',
'url': '/api/collections/posts/records/record_id_1',
},
{
'method': 'DELETE',
'url': '/api/collections/posts/records/record_id_2',
},
]);
Delete All Records (Truncate)
await pb.collections.truncate('posts');
Query Collection Contents
List Records with Pagination
final result = await pb.collection('posts').getList(page: 1, perPage: 50);
print(result.page); // 1
print(result.perPage); // 50
print(result.totalItems); // Total count
print(result.items); // List of records
Filter Records
final result = await pb.collection('posts').getList(
page: 1,
perPage: 50,
filter: 'published = true && views > 100',
sort: '-created',
);
Filter Operators
// Equality
filter: 'status = "published"'
// Comparison
filter: 'views > 100'
filter: 'created >= "2023-01-01"'
// Text search
filter: 'title ~ "dart"'
// Multiple conditions
filter: 'status = "published" && views > 100'
filter: 'status = "draft" || status = "pending"'
// Relation filter
filter: 'author.id = "user_id"'
Sort Records
// Single field
sort: '-created' // DESC
sort: 'title' // ASC
// Multiple fields
sort: '-created,title' // DESC by created, then ASC by title
Expand Relations
final result = await pb.collection('posts').getList(
page: 1,
perPage: 50,
expand: 'author,categories',
);
// Access expanded data
for (final post in result.items) {
print(post.expand?['author']?.name);
print(post.expand?['categories']);
}
Get Single Record
final record = await pb.collection('posts').getOne(
'record_id',
expand: 'author',
);
Get First Matching Record
final record = await pb.collection('posts').getFirstListItem(
'slug = "my-post-slug"',
expand: 'author',
);
Get All Records
final allRecords = await pb.collection('posts').getFullList(
filter: 'published = true',
sort: '-created',
);
Add and Delete Fields from Collections
Add Field
final collection = await pb.collections.addField('posts', {
'name': 'tags',
'type': 'select',
'options': {
'values': ['tech', 'science', 'art'],
},
});
Update Field
final collection = await pb.collections.updateField('posts', 'tags', {
'options': {
'values': ['tech', 'science', 'art', 'music'],
},
});
Remove Field
final collection = await pb.collections.removeField('posts', 'old_field');
Get Field Information
final field = await pb.collections.getField('posts', 'title');
print('${field?.type}, ${field?.required}, ${field?.options}');
Query Collection Field Information
Get All Fields for a Collection
final collection = await pb.collections.getOne('posts');
for (final field in collection.fields) {
print('${field.name}, ${field.type}, ${field.required}');
}
Get Collection Schema (Simplified)
final schema = await pb.collections.getSchema('posts');
print(schema.fields); // List of field info
Get All Collection Schemas
final schemas = await pb.collections.getAllSchemas();
for (final collection in schemas) {
print('${collection.name}, ${collection.fields}');
}
Query Field Information for Single Collection
// Method 1: Get full collection
final collection = await pb.collections.getOne('posts');
final titleField = collection.fields.firstWhere(
(f) => f.name == 'title',
orElse: () => throw Exception('Field not found'),
);
// Method 2: Get specific field
final field = await pb.collections.getField('posts', 'title');
// Method 3: Get schema
final schema = await pb.collections.getSchema('posts');
final titleFieldInfo = schema.fields.firstWhere(
(f) => f.name == 'title',
orElse: () => throw Exception('Field not found'),
);
Upload Files
Upload File with Record Creation
import 'package:http/http.dart' as http;
import 'dart:io';
final file = File('/path/to/image.jpg');
final fileBytes = await file.readAsBytes();
final multipartFile = http.MultipartFile.fromBytes(
'image',
fileBytes,
filename: 'image.jpg',
);
final request = http.MultipartRequest(
'POST',
Uri.parse('${pb.baseURL}/api/collections/posts/records'),
);
request.fields['title'] = 'Post Title';
request.files.add(multipartFile);
request.headers['Authorization'] = 'Bearer ${pb.authStore.token}';
final response = await request.send();
final responseBody = await response.stream.bytesToString();
final record = RecordModel.fromJson(jsonDecode(responseBody));
Upload File with Record Update
final file = File('/path/to/new_image.jpg');
final fileBytes = await file.readAsBytes();
final multipartFile = http.MultipartFile.fromBytes(
'image',
fileBytes,
filename: 'new_image.jpg',
);
final request = http.MultipartRequest(
'PATCH',
Uri.parse('${pb.baseURL}/api/collections/posts/records/record_id'),
);
request.files.add(multipartFile);
request.headers['Authorization'] = 'Bearer ${pb.authStore.token}';
final response = await request.send();
Get File URL
final record = await pb.collection('posts').getOne('record_id');
final fileUrl = pb.files.getURL(record, record.data['image'] as String);
Get File URL with Options
final fileUrl = pb.files.getURL(
record,
record.data['image'] as String,
thumb: '100x100', // Thumbnail
download: true, // Force download
);
Get Private File Token
// For accessing private files
final token = await pb.files.getToken();
// Use token in file URL
final fileUrl = pb.files.getURL(
record,
record.data['image'] as String,
token: token,
);
Query Logs
List Logs
final logs = await pb.logs.getList(page: 1, perPage: 50);
print(logs.items); // List of log entries
Filter Logs
final logs = await pb.logs.getList(
page: 1,
perPage: 50,
filter: 'level >= 400', // Error level and above
sort: '-created',
);
Get Single Log
final log = await pb.logs.getOne('log_id');
print('${log.message}, ${log.data}');
Get Log Statistics
final stats = await pb.logs.getStats(
query: {'filter': 'level >= 400'},
);
for (final stat in stats) {
print('${stat.date}, ${stat.total}');
}
Log Levels
0- Debug1- Info2- Warning3- Error4- Fatal
Send Emails
Note: Email sending is typically handled server-side via hooks or backend code. The SDK doesn’t provide direct email sending methods, but you can trigger email-related operations.
Trigger Email Verification
// Request verification email
await pb.collection('users').requestVerification('user@example.com');
Trigger Password Reset Email
// Request password reset email
await pb.collection('users').requestPasswordReset('user@example.com');
Email Change Request
// Request email change
await pb.collection('users').requestEmailChange('newemail@example.com');
Server-Side Email Sending
Email sending is configured in the backend settings and triggered automatically by:
- User registration (verification email)
- Password reset requests
- Email change requests
- Custom hooks
To send custom emails, you would typically:
- Create a backend hook that uses
app.NewMailClient() - Or use the admin API to configure email templates
- Or trigger email-related record operations that automatically send emails
Complete Example: Full Application Flow
import 'package:bosbase/bosbase.dart';
final pb = Bosbase('http://localhost:8090');
Future<void> setupApplication() async {
// 1. Authenticate
await pb.collection('users').authWithPassword('admin@example.com', 'password');
// 2. Create collection
final collection = await pb.collections.create(body: {
'name': 'posts',
'type': 'base',
'fields': [
{'name': 'title', 'type': 'text', 'required': true},
{'name': 'content', 'type': 'editor'},
{'name': 'published', 'type': 'bool'},
],
});
// 3. Add more fields
await pb.collections.addField('posts', {
'name': 'views',
'type': 'number',
'min': 0,
});
// 4. Create records
final post = await pb.collection('posts').create(body: {
'title': 'Hello World',
'content': 'My first post',
'published': true,
'views': 0,
});
// 5. Query records
final posts = await pb.collection('posts').getList(
page: 1,
perPage: 10,
filter: 'published = true',
sort: '-created',
);
// 6. Update record
await pb.collection('posts').update(post.id, body: {
'views': 100,
});
// 7. Query logs
final logs = await pb.logs.getList(
page: 1,
perPage: 20,
filter: 'level >= 400',
);
print('Application setup complete!');
}
void main() {
setupApplication().catchError(print);
}
Quick Reference
Common Patterns
// Check if authenticated
if (pb.authStore.isValid) { /* ... */ }
// Get current user
final user = pb.authStore.record;
// Refresh auth token
await pb.collection('users').authRefresh();
// Error handling
try {
await pb.collection('posts').create(body: {'title': 'Test'});
} on ClientException catch (e) {
if (e.statusCode == 400) {
print('Validation error: ${e.response}');
} else if (e.statusCode == 401) {
print('Not authenticated');
}
}
Field Types Reference
text- Text inputnumber- Numeric valuebool- Booleanemail- Email addressurl- URLdate- Dateselect- Single selectjson- JSON datafile- File uploadrelation- Relation to another collectioneditor- Rich text editor
Best Practices
- Always handle errors: Wrap API calls in try-catch
- Check authentication: Verify
pb.authStore.isValidbefore operations - Use pagination: Don’t fetch all records at once for large collections
- Validate data: Ensure required fields are provided
- Use filters: Filter data on the server, not client-side
- Expand relations wisely: Only expand what you need
- Handle file uploads: Use MultipartRequest for file fields
- Refresh tokens: Use
authRefresh()to maintain sessions - Use async/await: Prefer async/await over Future.then() for better readability
- Type safety: Use proper type annotations for better IDE support
LangChaingo Examples
Completion
final result = await pb.langchaingo.completions(
LangChaingoCompletionRequest(
model: const LangChaingoModelConfig(provider: "openai", model: "gpt-4o-mini"),
messages: const [
LangChaingoCompletionMessage(role: "system", content: "Answer in one sentence."),
LangChaingoCompletionMessage(role: "user", content: "Give a fun Mars fact.")
],
),
);
print(result.content);
Retrieval-Augmented Answer
final rag = await pb.langchaingo.rag(
LangChaingoRAGRequest(
collection: "knowledge-base",
question: "Why is the sky blue?",
topK: 3,
returnSources: true,
),
);
print(rag.answer);
for (final source in rag.sources ?? const []) {
print("${source.score} ${source.metadata}");
}
This guide provides all essential operations for building applications with the BosBase Dart SDK. For more detailed information, refer to the specific API documentation files.