diff --git a/tutorials/client/sdks/node/node-js.mdx b/tutorials/client/sdks/node/node-js.mdx new file mode 100644 index 0000000..324088b --- /dev/null +++ b/tutorials/client/sdks/node/node-js.mdx @@ -0,0 +1,507 @@ +--- +title: "Node.js + PowerSync" +description: "A guide for creating a Node.js application with PowerSync for offline/local first functionality." +keywords: ["node.js", "typescript", "clientsdks"] +--- + +## Introduction + +In this tutorial, you'll set up a Node.js application with PowerSync. +In the following sections, we’ll walk through the process of integrating PowerSync into a Node.js application, setting up local-first storage, and handling synchronization. + +## Prerequisites + +Before you begin, you'll need to have [Node.js](https://nodejs.org/en/download/current) set up. + +And a running PowerSync instance, with an authentication backend - for a quick setup checkout the [Supabase Guide](https://docs.powersync.com/integration-guides/supabase-+-powersync#supabase-powersync) + +### Step 1 - Initializing the project + +Create a new directory for your Node.js application and navigate into it: + +```shell +mkdir my-powersync-app +cd my-powersync-app +``` + +Once you are in the directory, create a new `package.json`. This will be where you manage your dependencies. If you are unfamiliar, you can create a new `package.json` file by running the following command:: + + + + ```shell npm + npm init + ``` + + ```shell yarn + yarn init + ``` + + ```shell pnpm + pnpm init + ``` + + + +Set the type to `module` in your `package.json` file to enable ES module support: + +```json package.json {6} +{ + "name": "my-powersync-app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} +``` + +Install the PowerSync Node.js package. This package provides the core functionality for integrating PowerSync into your Node.js application. + + + +```shell npm +npm install @powersync/node +``` + +```shell yarn +yarn add @powersync/node +``` + +```shell pnpm +pnpm add @powersync/node +``` + + + +For `@powersync/node@0.1.1` or earlier, also install `@powersync/better-sqlite3` as a peer dependency. + + + +```shell npm +npm install @powersync/better-sqlite3 +``` + +```shell yarn +yarn add @powersync/better-sqlite3 +``` + +```shell pnpm +pnpm add @powersync/better-sqlite3 +``` + + + +### Step 2 - Set up TypeScript (Optional) + +To use [TypeScript](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html) in your Node.js application, you need to install TypeScript and the Node.js type definitions. + + + + ```shell npm + npm add -D typescript ts-node @types/node + ``` + + ```shell yarn + yarn add -D typescript ts-node @types/node + ``` + + ```shell pnpm + pnpm add -D typescript ts-node @types/node + ``` + + + +Create a `tsconfig.json` file in the root of your project directory to configure TypeScript. This file will define how TypeScript compiles your code and where it outputs the compiled files. + +```json tsconfig.json +{ + "compilerOptions": { + "module": "nodenext", + "target": "esnext", + "outDir": "./dist", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules"] +} +``` + +### Step 3 - Create the Application Files + +Create the PowerSync application files in a `src` directory. This will help keep your project organized. + +```shell +mkdir src +``` + +#### Schema + +This file contains the schema definitions for your local SQLite database. It defines the structure of the data you will be working with, including tables and their columns. + + + + ```shell javascript + nano src/AppSchema.js + ``` + + ```shell typescript + nano src/AppSchema.ts + ``` + + + +These should map directly to the values produced by the [SyncRules](https://docs.powersync.com/usage/sync-rules). If a value doesn’t match, it is cast automatically. For details on how database types are mapped to the types below, see the section on [Types](https://docs.powersync.com/usage/sync-rules/types). + + + + ```javascript src/AppSchema.js + import { + Schema, + Table, + column + } from '@powersync/node'; + + const todos = new Table( + { + list_id: column.text, + created_at: column.text, + completed_at: column.text, + description: column.text, + created_by: column.text, + completed_by: column.text, + completed: column.integer, + photo_id: column.text + }, + { indexes: { list: ['list_id'] } } + ); + + const lists = new Table({ + created_at: column.text, + name: column.text, + owner_id: column.text + }); + + export const AppSchema = new Schema({ + lists, + todos + }); + ``` + + ```typescript src/AppSchema.ts + import { + Schema, + Table, + column + } from '@powersync/node'; + + const todos = new Table( + { + list_id: column.text, + created_at: column.text, + completed_at: column.text, + description: column.text, + created_by: column.text, + completed_by: column.text, + completed: column.integer, + photo_id: column.text + }, + { indexes: { list: ['list_id'] } } + ); + const lists = new Table({ + created_at: column.text, + name: column.text, + owner_id: column.text + }); + + export const AppSchema = new Schema({ + lists, + todos + }); + ``` + + + +#### Connector + +This file contains the connector for your PowerSync instance. The connector is responsible for fetching authentication credentials and handling data uploads to your backend service. + +If you are using [Supabase](https://docs.powersync.com/integration-guides/supabase-+-powersync#supabase-powersync) you can use the [Supabase Connector](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-native-supabase-todolist/library/supabase/SupabaseConnector.ts) class instead of implementing your own connector. + + + + ```shell javascript + nano src/Connector.js + ``` + + ```shell typescript + nano src/Connector.ts + ``` + + + + + + ```javascript src/Connector.js + + /** + * @implements {import('@powersync/node').PowerSyncConnector} + */ + export class Connector { + async fetchCredentials() { + // Implement fetchCredentials to obtain a JWT from your authentication service. + // See https://docs.powersync.com/installation/authentication-setup + // If you're using Supabase or Firebase, you can re-use the JWT from those clients, see + // - https://docs.powersync.com/installation/authentication-setup/supabase-auth + // - https://docs.powersync.com/installation/authentication-setup/firebase-auth + return { + endpoint: '[Your PowerSync instance URL or self-hosted endpoint]', + // Use a development token (see Authentication Setup https://docs.powersync.com/installation/authentication-setup/development-tokens) to get up and running quickly + token: 'An authentication token' + }; + } + + /** + * @param {import('@powersync/node').AbstractPowerSyncDatabase} database + * @returns {Promise} + */ + async uploadData(database) { + // Implement uploadData to send local changes to your backend service. + // You can omit this method if you only want to sync data from the database to the client + + // See example implementation here: https://docs.powersync.com/client-sdk-references/javascript-web#3-integrate-with-your-backend + } + } + + ``` + + ```typescript src/Connector.ts + import { + PowerSyncBackendConnector, + AbstractPowerSyncDatabase + } from '@powersync/node'; + + export class Connector implements PowerSyncBackendConnector { + async fetchCredentials() { + // Implement fetchCredentials to obtain a JWT from your authentication service. + // See https://docs.powersync.com/installation/authentication-setup + // If you're using Supabase or Firebase, you can re-use the JWT from those clients, see + // - https://docs.powersync.com/installation/authentication-setup/supabase-auth + // - https://docs.powersync.com/installation/authentication-setup/firebase-auth + return { + endpoint: '[Your PowerSync instance URL or self-hosted endpoint]', + // Use a development token (see Authentication Setup https://docs.powersync.com/installation/authentication-setup/development-tokens) to get up and running quickly + token: 'An authentication token' + }; + } + + async uploadData(database: AbstractPowerSyncDatabase) { + // Implement uploadData to send local changes to your backend service. + // You can omit this method if you only want to sync data from the database to the client + + // See example implementation here: https://docs.powersync.com/client-sdk-references/javascript-web#3-integrate-with-your-backend + } + } + ``` + + + +- `fetchCredentials` - This is called every couple of minutes and is used to obtain credentials for your app backend API. -> See [Authentication Setup](https://docs.powersync.com/installation/authentication-setup) for instructions on how the credentials should be generated. +- `uploadData` - Use this to upload client-side changes to your app backend. -> See [Writing Client Changes](https://docs.powersync.com/installation/app-backend-setup/writing-client-changes) for considerations on the app backend implementation. + +#### Index + +The main application file used in this guide to show you how to set up and initialize PowerSync. + + + + ```shell javascript + nano src/index.js + ``` + + ```shell typescript + nano src/index.ts + ``` + + + +This code initializes the PowerSync database, connects to the backend, and logs the current status of the database. + + + + ```javascript src/index.js + import { PowerSyncDatabase } from '@powersync/node'; + import { Connector } from './Connector.js'; + import { AppSchema } from './AppSchema.js'; + export const db = new PowerSyncDatabase({ + schema: AppSchema, + database: { + dbFilename: 'powersync.db' + }, + }); + await db.connect(new Connector()); + console.log(db.currentStatus); + ``` + + ```typescript src/index.ts + import { PowerSyncDatabase } from '@powersync/node'; + import { Connector } from './Connector.js'; + import { AppSchema } from './AppSchema.js'; + + export const db = new PowerSyncDatabase({ + schema: AppSchema, + database: { + dbFilename: 'powersync.db' + }, + }); + + await db.connect(new Connector()); + console.log(db.currentStatus); + ``` + + + +If you are using [Supabase](https://docs.powersync.com/integration-guides/supabase-+-powersync#supabase-powersync), replace the `index.js` or `index.ts` file with the following code: + + + + ```javascript src/index.js [expandable] + import 'dotenv/config' + import { PowerSyncDatabase } from '@powersync/node'; + import * as readline from 'node:readline/promises'; + import { stdin as input, stdout as output } from 'node:process'; + import { AppSchema } from './AppSchema.js'; + import { SupabaseConnector } from './SupabaseConnector.js'; + + export const db = new PowerSyncDatabase({ + schema: AppSchema, + database: { + dbFilename: 'powersync.db' + }, + }); + + const connector = new SupabaseConnector(); + db.init(); + connector.registerListener({ + initialized: () => { }, + sessionStarted: () => { + db.connect(connector); + console.log("status", db.currentStatus); + } + }); + + connector.init() + + const readlineInstance = readline.createInterface({ input, output }); + const isLogin = await readlineInstance.question('Is this a login? (y/n): '); + const email = await readlineInstance.question('Enter your email: '); + const password = await readlineInstance.question('Enter your password: ') + + if (isLogin.toLowerCase() === 'y') { + console.log('Logging in...'); + await connector.login(email, password); + } else { + console.log('Creating a new account...'); + const { + data: { session }, + error + } = await connector.client.auth.signUp({ + email, + password + }); + + if (error) { + console.error('Error creating account:', error.message); + process.exit(1); + } + connector.updateSession(session); + } + + const todos = await db.execute(`SELECT * FROM todos`); + console.log('Todos:', todos.rows?._array); + ``` + + ```typescript src/index.ts [expandable] + import 'dotenv/config' + import { PowerSyncDatabase } from '@powersync/node'; + import * as readline from 'node:readline/promises'; + import { stdin as input, stdout as output } from 'node:process'; + import { AppSchema } from './AppSchema.js'; + import { SupabaseConnector } from './SupabaseConnector.js'; + + export const db = new PowerSyncDatabase({ + schema: AppSchema, + database: { + dbFilename: 'powersync.db' + }, + }); + + const connector = new SupabaseConnector(); + db.init(); + connector.registerListener({ + initialized: () => { }, + sessionStarted: () => { + db.connect(connector); + console.log("status", db.currentStatus); + } + }); + + connector.init() + + const readlineInstance = readline.createInterface({ input, output }); + const isLogin = await readlineInstance.question('Is this a login? (y/n): '); + const email = await readlineInstance.question('Enter your email: '); + const password = await readlineInstance.question('Enter your password: ') + + if (isLogin.toLowerCase() === 'y') { + console.log('Logging in...'); + await connector.login(email, password); + } else { + console.log('Creating a new account...'); + const { + data: { session }, + error + } = await connector.client.auth.signUp({ + email, + password + }); + + if (error) { + console.error('Error creating account:', error.message); + process.exit(1); + } + connector.updateSession(session); + } + + const todos = await db.execute(`SELECT * FROM todos`); + console.log('Todos:', todos.rows?._array); + ``` + + + +### Step 4 - Running the Application + +JavaScript users can run the application using Node.js directly: + + + ```shell javascript + node src/index.js + ``` + + ```shell typescript + node --loader ts-node/esm -r dotenv/config src/index.ts + ``` + + + +This will start the app, initialize the database and connect to the PowerSync instance. + + + Depending on what you set for `dbFilename`, the app start (and all is working correctly) three files will be created; `.db`, `.db-shm` and `.db-wal`. You should exclude these from version control when creating the project so make sure to update your `.gitignore` accordingly. + diff --git a/tutorials/client/sdks/overview.mdx b/tutorials/client/sdks/overview.mdx index cf56e91..e6ac029 100644 --- a/tutorials/client/sdks/overview.mdx +++ b/tutorials/client/sdks/overview.mdx @@ -4,5 +4,5 @@ description: "A collection of tutorials on how to use PowerSync in supported cli --- - +