Sembast
Sembast stands for "Simple Embedded Application Store" and is a lightweight yet powerful NoSQL database library for Dart and Flutter. Designed for single-process applications as a persistent NoSQL store, the entire document-based database resides in a single file and is loaded into memory when opened. Being 100% Dart implementation with no plugins required, it works on all platforms (Android/iOS/macOS/Linux/Windows/Web) and provides encryption support, high-performance read/write operations, and an intuitive API, making it an ideal modern database solution for mobile and desktop app development.
GitHub Overview
Topics
Star History
Library
Sembast
Overview
Sembast stands for "Simple Embedded Application Store" and is a lightweight yet powerful NoSQL database library for Dart and Flutter. Designed for single-process applications as a persistent NoSQL store, the entire document-based database resides in a single file and is loaded into memory when opened. Being 100% Dart implementation with no plugins required, it works on all platforms (Android/iOS/macOS/Linux/Windows/Web) and provides encryption support, high-performance read/write operations, and an intuitive API, making it an ideal modern database solution for mobile and desktop app development.
Details
Sembast 2025 edition has established itself as one of the most trusted local database solutions in the Flutter ecosystem. As a JSON-like document-oriented database, it efficiently handles denormalized data and provides complex querying, indexing, and transaction capabilities. Integration with BLoC patterns makes collaboration with state management easy. Despite its simple file-based structure, it supports ACID-compliant transactions, reactive APIs, and large data processing, covering everything from small to medium-scale apps to full-scale production applications.
Key Features
- Complete Cross-Platform: Works on all Flutter-supported platforms
- Encryption Support: Strong encryption through user-defined codecs
- High Performance: Memory-based processing with automatic file optimization
- Simple API: Intuitive interface with low learning curve
- Denormalized Data Support: Flexible JSON-based data structures
- Reactive Programming: Stream-based real-time updates
Pros and Cons
Pros
- No plugins required with 100% Dart code, cross-platform support improves development efficiency
- Extremely easy setup with no boilerplate code required
- Lightweight design perfect for small to medium-scale apps, fast read/write performance
- Strong encryption features enable secure storage of sensitive data
- File-based simple structure makes debugging and data verification easy
- Excellent integration with state management libraries like BLoC
Cons
- Single-process design makes it unsuitable for large-scale multi-user applications
- Complex relational queries are difficult compared to SQL-based RDBMS
- Memory-based processing has constraints for ultra-large data volumes
- Transaction functionality is provided but not as robust as distributed databases
- Limited interoperability with other database systems
- Advanced enterprise-level features (replication, etc.) are not supported
Reference Pages
Code Examples
Setup and Installation
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
sembast: ^3.4.9
path_provider: ^2.1.1 # For app directory access
path: ^1.8.3
dev_dependencies:
flutter_test:
sdk: flutter
// Package imports
import 'package:sembast/sembast.dart';
import 'package:sembast/sembast_io.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';
Basic Database Operations
import 'package:sembast/sembast.dart';
import 'package:sembast/sembast_io.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';
class DatabaseService {
static Database? _database;
static final _userStore = intMapStoreFactory.store('users');
static final _postStore = intMapStoreFactory.store('posts');
// Get database instance
static Future<Database> get database async {
if (_database != null) return _database!;
// Get app directory
final appDocumentDir = await getApplicationDocumentsDirectory();
final dbPath = join(appDocumentDir.path, 'my_app.db');
// Open database
_database = await databaseFactoryIo.openDatabase(dbPath);
return _database!;
}
// Close database
static Future<void> close() async {
if (_database != null) {
await _database!.close();
_database = null;
}
}
// Add record
static Future<int> addUser(Map<String, dynamic> userData) async {
final db = await database;
return await _userStore.add(db, userData);
}
// Get all users
static Future<List<Map<String, dynamic>>> getAllUsers() async {
final db = await database;
final records = await _userStore.find(db);
return records.map((record) => {
'id': record.key,
...record.value,
}).toList();
}
// Search user by ID
static Future<Map<String, dynamic>?> getUserById(int id) async {
final db = await database;
final record = await _userStore.record(id).get(db);
if (record != null) {
return {'id': id, ...record};
}
return null;
}
// Update user
static Future<void> updateUser(int id, Map<String, dynamic> userData) async {
final db = await database;
await _userStore.record(id).update(db, userData);
}
// Delete user
static Future<void> deleteUser(int id) async {
final db = await database;
await _userStore.record(id).delete(db);
}
}
// Usage example
void main() async {
// Add user
final userId = await DatabaseService.addUser({
'name': 'John Doe',
'email': '[email protected]',
'age': 30,
'createdAt': DateTime.now().toIso8601String(),
});
print('Added user ID: $userId');
// Get all users
final users = await DatabaseService.getAllUsers();
print('All users: $users');
// Update user
await DatabaseService.updateUser(userId, {
'name': 'John Doe (Updated)',
'age': 31,
'updatedAt': DateTime.now().toIso8601String(),
});
// Close database
await DatabaseService.close();
}
Filtering and Query Operations
import 'package:sembast/sembast.dart';
class AdvancedQueryService {
static final _userStore = intMapStoreFactory.store('users');
static final _productStore = intMapStoreFactory.store('products');
// Conditional search
static Future<List<Map<String, dynamic>>> getUsersByAge(int minAge, int maxAge) async {
final db = await DatabaseService.database;
// Range filter by age
final finder = Finder(
filter: Filter.and([
Filter.greaterThanOrEquals('age', minAge),
Filter.lessThanOrEquals('age', maxAge),
]),
sortOrders: [SortOrder('age')],
);
final records = await _userStore.find(db, finder: finder);
return records.map((record) => {
'id': record.key,
...record.value,
}).toList();
}
// Complex filtering
static Future<List<Map<String, dynamic>>> searchUsers({
String? namePattern,
String? emailDomain,
bool? isActive,
int? limit,
int? offset,
}) async {
final db = await DatabaseService.database;
List<Filter> filters = [];
// Name partial match
if (namePattern != null) {
filters.add(Filter.matches('name', namePattern, anyInList: true));
}
// Email domain filter
if (emailDomain != null) {
filters.add(Filter.matches('email', '.*@$emailDomain'));
}
// Active user filter
if (isActive != null) {
filters.add(Filter.equals('isActive', isActive));
}
final finder = Finder(
filter: filters.isNotEmpty ? Filter.and(filters) : null,
sortOrders: [SortOrder('createdAt', false)], // Newest first
limit: limit,
offset: offset,
);
final records = await _userStore.find(db, finder: finder);
return records.map((record) => {
'id': record.key,
...record.value,
}).toList();
}
// Custom queries
static Future<Map<String, dynamic>> getUserStats() async {
final db = await DatabaseService.database;
// Total user count
final totalUsers = await _userStore.count(db);
// Active user count
final activeUsers = await _userStore.count(
db,
filter: Filter.equals('isActive', true),
);
// Age group statistics
final youngUsers = await _userStore.count(
db,
filter: Filter.lessThan('age', 30),
);
final middleAgedUsers = await _userStore.count(
db,
filter: Filter.and([
Filter.greaterThanOrEquals('age', 30),
Filter.lessThan('age', 50),
]),
);
final seniorUsers = await _userStore.count(
db,
filter: Filter.greaterThanOrEquals('age', 50),
);
return {
'totalUsers': totalUsers,
'activeUsers': activeUsers,
'inactiveUsers': totalUsers - activeUsers,
'ageDistribution': {
'young': youngUsers, // Under 30
'middleAged': middleAgedUsers, // 30-49
'senior': seniorUsers, // 50+
},
};
}
// Pagination
static Future<Map<String, dynamic>> getUsersPage({
int page = 1,
int itemsPerPage = 20,
String? sortBy = 'createdAt',
bool ascending = false,
}) async {
final db = await DatabaseService.database;
final offset = (page - 1) * itemsPerPage;
final finder = Finder(
sortOrders: [SortOrder(sortBy!, ascending)],
limit: itemsPerPage,
offset: offset,
);
final records = await _userStore.find(db, finder: finder);
final totalCount = await _userStore.count(db);
return {
'data': records.map((record) => {
'id': record.key,
...record.value,
}).toList(),
'pagination': {
'currentPage': page,
'itemsPerPage': itemsPerPage,
'totalItems': totalCount,
'totalPages': (totalCount / itemsPerPage).ceil(),
'hasNextPage': offset + itemsPerPage < totalCount,
'hasPreviousPage': page > 1,
},
};
}
}
Transactions and Batch Operations
import 'package:sembast/sembast.dart';
class TransactionService {
static final _userStore = intMapStoreFactory.store('users');
static final _orderStore = intMapStoreFactory.store('orders');
static final _productStore = intMapStoreFactory.store('products');
// Basic transaction
static Future<void> transferUserData(
int fromUserId,
int toUserId,
Map<String, dynamic> transferData,
) async {
final db = await DatabaseService.database;
await db.transaction((txn) async {
// Read data from fromUser
final fromUser = await _userStore.record(fromUserId).get(txn);
if (fromUser == null) {
throw Exception('Source user not found');
}
// Read data from toUser
final toUser = await _userStore.record(toUserId).get(txn);
if (toUser == null) {
throw Exception('Target user not found');
}
// Remove/update data from fromUser
final updatedFromUser = Map<String, dynamic>.from(fromUser);
transferData.forEach((key, value) {
if (updatedFromUser.containsKey(key)) {
updatedFromUser.remove(key);
}
});
// Add/update data to toUser
final updatedToUser = Map<String, dynamic>.from(toUser);
updatedToUser.addAll(transferData);
updatedToUser['updatedAt'] = DateTime.now().toIso8601String();
// Update both users
await _userStore.record(fromUserId).put(txn, updatedFromUser);
await _userStore.record(toUserId).put(txn, updatedToUser);
print('Data transfer completed successfully');
});
}
// Order processing transaction
static Future<int> createOrderWithItems(
int userId,
List<Map<String, dynamic>> orderItems,
) async {
final db = await DatabaseService.database;
return await db.transaction<int>((txn) async {
// Check user existence
final user = await _userStore.record(userId).get(txn);
if (user == null) {
throw Exception('User not found');
}
// Check product inventory
for (final item in orderItems) {
final productId = item['productId'] as int;
final quantity = item['quantity'] as int;
final product = await _productStore.record(productId).get(txn);
if (product == null) {
throw Exception('Product ID $productId not found');
}
final currentStock = product['stock'] as int;
if (currentStock < quantity) {
throw Exception('Insufficient stock for product ID $productId');
}
}
// Create order
final orderId = await _orderStore.add(txn, {
'userId': userId,
'items': orderItems,
'totalAmount': _calculateTotal(orderItems),
'status': 'pending',
'createdAt': DateTime.now().toIso8601String(),
});
// Reduce inventory
for (final item in orderItems) {
final productId = item['productId'] as int;
final quantity = item['quantity'] as int;
final product = await _productStore.record(productId).get(txn);
final updatedProduct = Map<String, dynamic>.from(product!);
updatedProduct['stock'] = (updatedProduct['stock'] as int) - quantity;
updatedProduct['updatedAt'] = DateTime.now().toIso8601String();
await _productStore.record(productId).put(txn, updatedProduct);
}
print('Order $orderId created successfully');
return orderId;
});
}
// Batch operations
static Future<void> batchUpdateUsers(
List<Map<String, dynamic>> updates,
) async {
final db = await DatabaseService.database;
await db.transaction((txn) async {
for (final update in updates) {
final userId = update['id'] as int;
final userData = Map<String, dynamic>.from(update);
userData.remove('id'); // Remove ID
userData['updatedAt'] = DateTime.now().toIso8601String();
await _userStore.record(userId).update(txn, userData);
}
print('${updates.length} users updated in batch');
});
}
// Data cleanup
static Future<void> cleanupOldData(Duration maxAge) async {
final db = await DatabaseService.database;
await db.transaction((txn) async {
final cutoffDate = DateTime.now().subtract(maxAge);
final cutoffString = cutoffDate.toIso8601String();
// Delete old users
final oldUserFinder = Finder(
filter: Filter.lessThan('createdAt', cutoffString),
);
final deletedUserCount = await _userStore.delete(txn, finder: oldUserFinder);
// Delete old orders
final oldOrderFinder = Finder(
filter: Filter.and([
Filter.lessThan('createdAt', cutoffString),
Filter.equals('status', 'completed'),
]),
);
final deletedOrderCount = await _orderStore.delete(txn, finder: oldOrderFinder);
print('Cleanup completed: deleted $deletedUserCount users, $deletedOrderCount orders');
});
}
static double _calculateTotal(List<Map<String, dynamic>> items) {
return items.fold(0.0, (total, item) {
final price = item['price'] as double;
final quantity = item['quantity'] as int;
return total + (price * quantity);
});
}
}
Encryption and Security
import 'package:sembast/sembast.dart';
import 'package:sembast/sembast_io.dart';
import 'package:crypto/crypto.dart';
import 'dart:convert';
import 'dart:typed_data';
// Custom encryption codec
class EncryptionCodec extends SembastCodec {
final String _password;
EncryptionCodec(this._password);
@override
String get signature => 'encrypt_v1';
@override
Uint8List encode(Object? input) {
if (input == null) return Uint8List(0);
// JSON encode data
final jsonString = jsonEncode(input);
final bytes = utf8.encode(jsonString);
// Simple encryption (use proper encryption library in production)
final key = sha256.convert(utf8.encode(_password)).bytes;
final encrypted = _xorEncrypt(bytes, key);
return Uint8List.fromList(encrypted);
}
@override
Object? decode(Uint8List encoded) {
if (encoded.isEmpty) return null;
// Decrypt
final key = sha256.convert(utf8.encode(_password)).bytes;
final decrypted = _xorEncrypt(encoded, key);
// JSON decode
final jsonString = utf8.decode(decrypted);
return jsonDecode(jsonString);
}
List<int> _xorEncrypt(List<int> data, List<int> key) {
final result = <int>[];
for (int i = 0; i < data.length; i++) {
result.add(data[i] ^ key[i % key.length]);
}
return result;
}
}
class SecureDatabaseService {
static Database? _database;
static final _userStore = intMapStoreFactory.store('users');
static final _sensitiveStore = intMapStoreFactory.store('sensitive_data');
// Initialize encrypted database
static Future<Database> initializeSecureDatabase(String password) async {
if (_database != null) return _database!;
final appDocumentDir = await getApplicationDocumentsDirectory();
final dbPath = join(appDocumentDir.path, 'secure_app.db');
// Create encryption codec
final codec = EncryptionCodec(password);
// Open encrypted database
_database = await databaseFactoryIo.openDatabase(
dbPath,
codec: codec,
);
return _database!;
}
// Store sensitive data
static Future<int> storeSensitiveData(Map<String, dynamic> data) async {
final db = await _database!;
// Save data with timestamp
final secureData = {
...data,
'encryptedAt': DateTime.now().toIso8601String(),
'hash': _generateDataHash(data),
};
return await _sensitiveStore.add(db, secureData);
}
// Retrieve and verify sensitive data
static Future<Map<String, dynamic>?> getSensitiveData(int id) async {
final db = await _database!;
final record = await _sensitiveStore.record(id).get(db);
if (record == null) return null;
// Verify data integrity
final storedHash = record['hash'] as String;
final dataWithoutHash = Map<String, dynamic>.from(record);
dataWithoutHash.remove('hash');
dataWithoutHash.remove('encryptedAt');
final calculatedHash = _generateDataHash(dataWithoutHash);
if (storedHash != calculatedHash) {
throw Exception('Data may have been tampered with');
}
return {
'id': id,
...record,
};
}
static String _generateDataHash(Map<String, dynamic> data) {
final jsonString = jsonEncode(data);
final bytes = utf8.encode(jsonString);
final digest = sha256.convert(bytes);
return digest.toString();
}
// Create secure backup
static Future<void> createSecureBackup(String backupPassword) async {
final db = await _database!;
final appDocumentDir = await getApplicationDocumentsDirectory();
final backupPath = join(appDocumentDir.path, 'backup_${DateTime.now().millisecondsSinceEpoch}.db');
// New codec for backup
final backupCodec = EncryptionCodec(backupPassword);
// Create backup database
final backupDb = await databaseFactoryIo.openDatabase(
backupPath,
codec: backupCodec,
);
try {
// Export all data
final users = await _userStore.find(db);
final sensitiveData = await _sensitiveStore.find(db);
final backupUserStore = intMapStoreFactory.store('users');
final backupSensitiveStore = intMapStoreFactory.store('sensitive_data');
// Restore data to backup database
await backupDb.transaction((txn) async {
for (final record in users) {
await backupUserStore.record(record.key).put(txn, record.value);
}
for (final record in sensitiveData) {
await backupSensitiveStore.record(record.key).put(txn, record.value);
}
});
print('Secure backup created: $backupPath');
} finally {
await backupDb.close();
}
}
}
// Usage example
void main() async {
try {
// Initialize secure database
await SecureDatabaseService.initializeSecureDatabase('my_secret_password_123');
// Store sensitive data
final sensitiveId = await SecureDatabaseService.storeSensitiveData({
'creditCardNumber': '1234-5678-9012-3456',
'expiryDate': '12/25',
'holderName': 'John Doe',
'notes': 'Confidential information',
});
// Retrieve sensitive data
final retrievedData = await SecureDatabaseService.getSensitiveData(sensitiveId);
print('Retrieved data: $retrievedData');
// Create secure backup
await SecureDatabaseService.createSecureBackup('backup_password_456');
} catch (e) {
print('Error: $e');
}
}
Reactive Programming and Streams
import 'package:sembast/sembast.dart';
import 'dart:async';
class ReactiveDataService {
static final _userStore = intMapStoreFactory.store('users');
static final _chatStore = intMapStoreFactory.store('chat_messages');
// Watch user changes
static Stream<List<Map<String, dynamic>>> watchUsers() async* {
final db = await DatabaseService.database;
await for (final snapshot in _userStore.query().onSnapshots(db)) {
final users = snapshot.map((record) => {
'id': record.key,
...record.value,
}).toList();
yield users;
}
}
// Watch specific condition users
static Stream<List<Map<String, dynamic>>> watchActiveUsers() async* {
final db = await DatabaseService.database;
final query = _userStore.query(
finder: Finder(
filter: Filter.equals('isActive', true),
sortOrders: [SortOrder('lastLoginAt', false)],
),
);
await for (final snapshot in query.onSnapshots(db)) {
final activeUsers = snapshot.map((record) => {
'id': record.key,
...record.value,
}).toList();
yield activeUsers;
}
}
// Watch chat messages in real-time
static Stream<List<Map<String, dynamic>>> watchChatMessages(int chatRoomId) async* {
final db = await DatabaseService.database;
final query = _chatStore.query(
finder: Finder(
filter: Filter.equals('chatRoomId', chatRoomId),
sortOrders: [SortOrder('timestamp')],
),
);
await for (final snapshot in query.onSnapshots(db)) {
final messages = snapshot.map((record) => {
'id': record.key,
...record.value,
}).toList();
yield messages;
}
}
// Watch count changes
static Stream<int> watchUserCount() async* {
final db = await DatabaseService.database;
await for (final _ in _userStore.query().onSnapshots(db)) {
final count = await _userStore.count(db);
yield count;
}
}
// Watch complex conditions
static Stream<Map<String, dynamic>> watchUserStatistics() async* {
final db = await DatabaseService.database;
await for (final _ in _userStore.query().onSnapshots(db)) {
final totalCount = await _userStore.count(db);
final activeCount = await _userStore.count(
db,
filter: Filter.equals('isActive', true),
);
final newUsersToday = await _userStore.count(
db,
filter: Filter.greaterThan(
'createdAt',
DateTime.now().subtract(Duration(days: 1)).toIso8601String(),
),
);
yield {
'totalUsers': totalCount,
'activeUsers': activeCount,
'inactiveUsers': totalCount - activeCount,
'newUsersToday': newUsersToday,
'lastUpdated': DateTime.now().toIso8601String(),
};
}
}
// Send message with real-time updates
static Future<void> sendChatMessage(
int chatRoomId,
int senderId,
String message,
) async {
final db = await DatabaseService.database;
await _chatStore.add(db, {
'chatRoomId': chatRoomId,
'senderId': senderId,
'message': message,
'timestamp': DateTime.now().toIso8601String(),
'isRead': false,
});
// This addition automatically notifies all listeners
// watching watchChatMessages() of the new message
}
// Batch operations with real-time updates
static Future<void> markMessagesAsRead(
int chatRoomId,
int userId,
) async {
final db = await DatabaseService.database;
await db.transaction((txn) async {
final finder = Finder(
filter: Filter.and([
Filter.equals('chatRoomId', chatRoomId),
Filter.notEquals('senderId', userId),
Filter.equals('isRead', false),
]),
);
await _chatStore.update(
txn,
{'isRead': true, 'readAt': DateTime.now().toIso8601String()},
finder: finder,
);
});
// This update automatically triggers real-time monitoring
}
}
// Flutter Widget usage example
class UserListWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder<List<Map<String, dynamic>>>(
stream: ReactiveDataService.watchUsers(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
final users = snapshot.data ?? [];
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
title: Text(user['name'] ?? ''),
subtitle: Text(user['email'] ?? ''),
trailing: user['isActive'] == true
? Icon(Icons.circle, color: Colors.green, size: 12)
: Icon(Icons.circle, color: Colors.grey, size: 12),
);
},
);
},
);
}
}
// Chat UI example
class ChatWidget extends StatelessWidget {
final int chatRoomId;
const ChatWidget({Key? key, required this.chatRoomId}) : super(key: key);
@override
Widget build(BuildContext context) {
return StreamBuilder<List<Map<String, dynamic>>>(
stream: ReactiveDataService.watchChatMessages(chatRoomId),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
final messages = snapshot.data!;
return ListView.builder(
itemCount: messages.length,
itemBuilder: (context, index) {
final message = messages[index];
return ListTile(
title: Text(message['message'] ?? ''),
subtitle: Text(message['timestamp'] ?? ''),
leading: CircleAvatar(
child: Text('${message['senderId']}'),
),
trailing: message['isRead'] == true
? Icon(Icons.done_all, color: Colors.blue)
: Icon(Icons.done, color: Colors.grey),
);
},
);
},
);
}
}