Overview
PowerSync works by:- Automatically streaming changes from your Postgres backend source database into a SQLite database on the client.
- Collecting local writes that users have performed on the SQLite database, and allowing you to upload those writes to your backend.
See Architecture Overview for a full overview.
Authentication
To ensure each user only has access to the data they’re supposed to see, Serverpod
authenticates users against PowerSync.
notes project using the Serverpod CLI:
Database Setup
Begin by configuring your Postgres database for PowerSync. PowerSync requires logical replication to be enabled. With thedocker-compose.yaml file generated by Serverpod, add a command to the postgres
service to enable this option.
This is also a good opportunity to add a health check, which helps PowerSync connect at the right time later:
notes_server/lib/src/greeting.spy.yaml:
PowerSync works best when ids are stable. And since clients can also create rows locally, using
randomized ids reduces the chance of collisions. This is why we prefer UUIDs over the default
incrementing key.
serverpod generate and ignore the issues in greeting_endpoint.dart for now.
Instead, run serverpod create-migration and note the generated path:
greeting table to also configure a replication that PowerSync will hook into.
For that, edit notes_server/migrations/<migration id>/migration.sql
At the end of that file, after COMMIT;, add this:
SELECT privilege, and for the publication mentioned in the next step (as well as for any other publications that may exist).
This is also a good place to set up a Postgres publication that a PowerSync Service will subscribe to:
migration.sql, also add them to definition.sql. The reason is that Serverpod
runs that file when instantiating the database from scratch, migration.sql would be ignored in that case.
PowerSync Configuration
PowerSync requires a service to process Postgres writes into a form that can be synced to clients. Additionally, your Serverpod backend will be responsible for generating JWTs to authenticate clients as they connect to this service. To set that up, begin by generating an RSA key to sign these JWTs. In the server project, rundart pub add jose to add a package supporting JWTs in Dart.
Then, create a tool/generate_keys.dart that prints a new key pair when run:
Run dart run tool/generate_jwt.dart and save its output, it’s needed for the next step as well.
For development, you can add the PowerSync Service to the compose file.
It needs access to the source database, a Postgres database to store intermediate data,
and the public half of the generated signing key.
service.yaml next to the compose file.
This file configures how PowerSync connects to the source database, how to authenticate users,
and which data to sync:
Authentication
PowerSync processes the entire source database into buckets, an efficient representation for sync. With the configuration shown here, there is one such bucket per user storing allgreetings owned by that user.
For security, it is crucial each user only has access to their own bucket. This is why PowerSync gives you full access control:
- When a client connects to PowerSync, it fetches an authentication token from your Serverpod instance.
- Your Dart backend logic returns a JWT describing what data the user should have access to.
- In the
sync_rulessection, you reference properties of the created JWTs to control data visible to the connecting clients.
notes_server/lib/src/powersync_endpoint.dart, create those endpoints:
greeting_endpoint.dart file, it’s not necessary since PowerSync is used to fetch data from your server.
Also remove invocations related to future calls in lib/server.dart.
Don’t forget to run serverpod generate afterwards.
Data Sync
With all services, configured, it’s time to spin up development services:_flutter project generated by Serverpod and run dart pub add powersync path path_provider.
Next, replace main.dart with this demo:
docker compose up), start your backend dart run bin/main.dart in notes_server
and finally launch your app.
When the app is loaded, you should see a greeting synced from the server. To verify PowerSync is working,
here are some things to try:
- Update in the source database: Connect to the Postgres database again (
psql -h 127.0.0.1 -p 8090 -U postgres) and run a query likeupdate greeting set message = upper(message);. Note how the app’s UI reflects these changes without you having to write any code for these updates. - Click on a delete icon to see local writes automatically being uploaded to the backend.
- Add new items to the database and stop your backend to simulate being offline. Deleting items still updates the client immediately, changes will be written to Postgres as your backend comes back online.
Next Steps
This guide demonstrated a minimal setup with PowerSync and Serverpod. To expand on this, you could explore:- Web support: PowerSync supports Flutter web, but needs additional assets.
- Authentication: If you already have an existing backend that is publicly-reachable, serving a JWKS URL would be safer than using pre-shared keys.
- Deploying: The easiest way to run PowerSync is to let us host it for you (you still have full control over your source database and backend). You can also explore self-hosting the PowerSync Service.