Web
You can experimentally use drift in Dart webapps. Drift web supports Flutter Web, AngularDart, plain dart:html
or any other web framework.
Getting started
From a perspective of the Dart code used, drift on the web is similar to drift on other platforms. You can follow the getting started guide for general information on using drift.
Instead of using a NativeDatabase
in your database classes, you can use a WebDatabase
executor:
import 'package:drift/web.dart';
@DriftDatabase(tables: [Todos, Categories])
class MyDatabase extends _$MyDatabase {
// here, "app" is the name of the database - you can choose any name you want
MyDatabase() : super(WebDatabase('app'));
Drift web is built on top of the sql.js library, which you need to include:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script defer src="sql-wasm.js"></script>
<script defer src="main.dart.js" type="application/javascript"></script>
</head>
<body></body>
</html>
You can grab the latest version of sql-wasm.js
and sql-wasm.wasm
here and copy them into your web
folder.
A full example that works on the web (and all other platforms) is available here.
Gotchas
The database implementation uses WebAssembly, which needs to be supported by your browser. Also, make sure that your webserver serves the .wasm
file as application/wasm
, browsers won't accept it otherwise.
Sharing code between native apps and web
If you want to share your database code between native applications and webapps, just import the basic drift/drift.dart
library into your database file. And instead of passing a NativeDatabase
or WebDatabase
to the super
constructor, make the QueryExecutor
customizable:
// don't import drift/web.dart or drift/native.dart in shared code
import 'package:drift/drift.dart';
@DriftDatabase(/* ... */)
class SharedDatabase extends _$MyDatabase {
SharedDatabase(QueryExecutor e): super(e);
}
In native Flutter apps, you can create an instance of your database with
// native.dart
import 'package:drift/native.dart';
SharedDatabase constructDb() {
final db = LazyDatabase(() async {
final dbFolder = await getApplicationDocumentsDirectory();
final file = File(p.join(dbFolder.path, 'db.sqlite'));
return NativeDatabase(file);
});
return SharedDatabase(db);
}
On the web, you can use
// web.dart
import 'package:drift/web.dart';
SharedDatabase constructDb() {
return SharedDatabase(WebDatabase('db'));
}
Finally, we can use conditional imports to automatically pick the right constructDb
function based on where your app runs. To do this, first create a unsupported.dart
default implementation:
// unsupported.dart
SharedDatabase constructDb() => throw UnimplementedError();
Now, we can use conditional imports in a shared.dart
file to export the correct function:
// shared.dart
export 'unsupported.dart'
if (dart.library.ffi) 'native.dart'
if (dart.library.html) 'web.dart';
A ready example of this construct can also be found here.
Debugging
You can see all queries sent from drift to the underlying database engine by enabling the logStatements
parameter on the WebDatabase
- they will appear in the console. When you have assertions enabled (e.g. in debug mode), drift will expose the underlying database object via window.db
. If you need to quickly run a query to check the state of the database, you can use db.exec(sql)
. If you need to delete your databases, there stored using local storage. You can clear all your data with localStorage.clear()
.
Web support is experimental at the moment, so please report all issues you find.
Using IndexedDb
The default WebDatabase
uses local storage to store the raw sqlite database file. On browsers that support it, you can also use IndexedDb
to store that blob. In general, browsers allow a larger size for IndexedDb
. The implementation is also more performant, since we don't have to encode binary blobs as strings.
To use this implementation on browsers that support it, replace WebDatabase(name)
with:
WebDatabase.withStorage(await DriftWebStorage.indexedDbIfSupported(name))
Drift will automatically migrate data from local storage to IndexedDb
when it is available.
Using web workers
You can offload the database to a background thread by using Web Workers. Drift also supports shared workers, which allows you to seamlessly synchronize query-streams and updates across multiple tabs!
Since web workers can't use local storage, you need to use DriftWebStorage.indexedDb
instead of the regular implementation.
The following example is meant to be used with a regular Dart web app, compiled using build_web_compilers.
To write a web worker that will serve requests for drift, create a file called worker.dart
in the web/
folder of your app. It could have the following content:
import 'dart:html';
import 'package:drift/drift.dart';
import 'package:drift/web.dart';
import 'package:drift/remote.dart';
void main() {
final self = SharedWorkerGlobalScope.instance;
self.importScripts('sql-wasm.js');
final db = WebDatabase.withStorage(DriftWebStorage.indexedDb('worker',
migrateFromLocalStorage: false, inWebWorker: true));
final server = DriftServer(DatabaseConnection(db));
self.onConnect.listen((event) {
final msg = event as MessageEvent;
server.serve(msg.ports.first.channel());
});
}
For more information on this api, see the remote API.
Connecting to that worker is very simple with drift's web and remote apis. In your regular app code (outside of the worker), you can connect like this:
import 'dart:html';
import 'package:drift/remote.dart';
import 'package:drift/web.dart';
import 'package:web_worker_example/database.dart';
DatabaseConnection connectToWorker() {
final worker = SharedWorker('worker.dart.js');
return remote(worker.port!.channel());
}
You can then open a drift database with that connection. For more information on the DatabaseConnection
class, see the documentation on isolates.
A small, but working example is available under examples/web_worker_example in the drift repository.
Flutter
Flutter users will have to use a different approach to compile service workers. Flutter web doesn't compile .dart
files in web folder and won't use .js
files generated by build_web_compilers
either. Instead, we'll use Dart's build system to manually compile the worker to a JavaScript file before using Flutter-specific tooling.
Example is available under examples/flutter_web_worker_example in the drift repository.
First, add build_web_compilers to the project:
dev_dependencies:
build_web_compilers: ^3.2.1
Inside a build.yaml
file, the compilers can be configured to always use dart2js
(a good choice for web workers). Add these lines to build.yaml
:
targets:
$default:
builders:
build_web_compilers:entrypoint:
generate_for:
- web/**.dart
options:
compiler: dart2js
dev_options:
dart2js_args:
- --no-minify
release_options:
dart2js_args:
- -O4
Nowm, run the compiler and copy the compiled worker JS files to web/
:
#Debug mode
flutter pub run build_runner build --delete-conflicting-outputs -o web:build/web/
cp -f build/web/worker.dart.js web/worker.dart.js
#Release mode
flutter pub run build_runner build --release --delete-conflicting-outputs -o web:build/web/
cp -f build/web/worker.dart.js web/worker.dart.min.js
Finally, use this to connect to a worker:
import 'dart:html';
import 'package:drift/drift.dart';
import 'package:drift/remote.dart';
import 'package:drift/web.dart';
import 'package:flutter/foundation.dart';
DatabaseConnection connectToWorker(String databaseName) {
final worker = SharedWorker(
kReleaseMode ? 'worker.dart.min.js' : 'worker.dart.js', databaseName);
return remote(worker.port!.channel());
}
You can pass that DatabaseConnection to your database by enabling the generate_connect_constructor
build option.
New web backend
In recent versions, drift added support for a new backend exposed by the package:drift/wasm.dart
library. Unlike sql.js or the official sqlite3 WASM edition which both use Emscripten, this backend does not need any external JavaScript sources. All bindings, including a virtual filesystem implementation used to store your databases, are implemented in Dart instead.
This approach enables optimizations making this backend more efficient that the existing web version of drift. However, it should be noted that this backend is much newer and may potentially be less stable at the moment. We encourage you to use it and report any issues you may find, but please keep in mind that it may have some rough edges.
As this version of sqlite3 was compiled with a custom VFS, you can't re-use the WebAssembly module from sql.js. Instead, grab a sqlite3.wasm file from the releases of the sqlite3
pub package and put this file in your web/
folder.
With this setup, sqlite3 can be used on the web without an external library:
import 'package:drift/drift.dart';
import 'package:drift/wasm.dart';
import 'package:http/http.dart' as http;
import 'package:sqlite3/wasm.dart';
QueryExecutor connect() {
return LazyDatabase(() async {
// Create virtual filesystem for sqlite3 implemented over blobs stored in an
// IndexedDB database (named `my_app` here).
final fs = await IndexedDbFileSystem.open(dbName: 'my_app');
// Load wasm bundle for sqlite3
final response = await http.get(Uri.parse('sqlite3.wasm'));
final sqlite3 = await WasmSqlite3.load(
response.bodyBytes,
SqliteEnvironment(fileSystem: fs),
);
// Then, open a database:
return WasmDatabase(sqlite3: sqlite3, path: '/app.db');
});
}
This snippet also works in a service worker.
If you're running into any issues with the new backend, please post them here.