Firebase SQL Connect client SDKs let you call your server-side queries and
mutations directly from a Firebase app. You generate a custom client SDK in
parallel as you design the schemas, queries and mutations you deploy to your
SQL Connect service. Then, you integrate methods from this SDK into
your client logic.

As we've mentioned elsewhere, it's important to note that SQL Connect
queries and mutations are not submitted by client code and executed on the
server. Instead, when deployed, SQL Connect operations are stored on
the server like Cloud Functions. This means you need to deploy corresponding
client-side changes to avoid breaking existing users (for example, on older app
versions).

That's why SQL Connect provides you with a developer environment and
tooling that lets you prototype your server-deployed schemas, queries and mutations.
It also generates client-side SDKs automatically, while you prototype.

When you've iterated updates to your service and client apps, both server- and
client-side updates are ready to deploy.

## What is the client development workflow?

If you followed the [Get started](https://firebase.google.com/docs/sql-connect/quickstart.md.txt), you were
introduced to the overall development flow for SQL Connect. In this
guide, you'll find more detailed information about generating Web SDKs from
your schema and working with client queries and mutations.

> [!NOTE]
> You can learn client development with a quickstart app repository and build a fully-featured SQL Connect app by following our [codelab for web](https://firebase.google.com/codelabs/firebase-dataconnect-web).

To summarize, to use generated Web SDKs in your client apps, you'll follow
these prerequisite steps:

1. Add Firebase to your [web](https://firebase.google.com/docs/web/setup.md.txt) app.

Then:

1. Develop your app schema.
2. Initialize your client code with the [JavaScript SDK](https://firebase.google.com/docs/sql-connect/web-sdk.md.txt#use-core).
3. Set up SDK generation:
   - With the **Add SDK to app** button in the SQL Connect VS Code extension.
   - By updating your `connector.yaml` for the [JavaScript SDK](https://firebase.google.com/docs/sql-connect/web-sdk.md.txt#generate-web).
4. Import libraries and generated code with [JavaScript SDK](https://firebase.google.com/docs/sql-connect/web-sdk.md.txt#import-web).
5. Implement calls to queries and mutations with the [JavaScript SDK](https://firebase.google.com/docs/sql-connect/web-sdk.md.txt#using-queries).
6. Test, by setting up the SQL Connect emulator with the [JavaScript SDK](https://firebase.google.com/docs/sql-connect/web-sdk.md.txt#emulator).

## Implement client code with the Firebase JavaScript SDK

This section covers how you can implement clients using the Firebase JavaScript SDK.

If you're using React or Angular, see alternate setup instructions and links to
additional documentation about [generating SQL Connect SDKs for frameworks](https://firebase.google.com/docs/sql-connect/web-sdk.md.txt#react-angular).

### Initialize your app

First, initialize your app using the
[standard Firebase sequence](https://firebase.google.com/docs/web/setup.md.txt).

```javascript
initializeApp({...});
```

### Install the generated JavaScript SDK

Use the Firebase CLI to set up SQL Connect generated SDKs in your apps.
The `init` command should detect all apps in the current folder and install
generated SDKs automatically.

```sql
firebase init dataconnect:sdk
```

Connect your app to the SQL Connect service.

```javascript
import { connectDataConnectEmulator } from 'firebase/data-connect';
import { connectorConfig } from '@dataconnect/generated';

const dataConnect = getDataConnect(connectorConfig);
// [Optionally] Configure the SDK to use Data Connect local emulator.
connectDataConnectEmulator(dataConnect, 'localhost', 9399);
```

### Update SDKs while prototyping

If you have SQL Connect VS Code extension installed, it will always keep generated
SDKs up to date.

If you don't use SQL Connect VS Code extension, you can use Firebase CLI
to keep generated SDKs up to date.

```bash
firebase dataconnect:sdk:generate --watch
```

### Generate SDKs in build pipelines

You can use the Firebase CLI to generate SQL Connect SDKs in CI/CD build processes.

```bash
firebase dataconnect:sdk:generate
```

#### Import libraries

There are two sets of imports needed to initialize your client code: general
SQL Connect imports and specific, generated SDK imports.

Note the `ConnectorConfig` object included in the general imports.

```javascript
// general imports
import { ConnectorConfig, DataConnect, getDataConnect, QueryRef, MutationRef, QueryPromise, MutationPromise } from 'firebase/data-connect';

// generated queries and mutations from SDK
import { listMovies, ListMoviesResponse, createMovie, connectorConfig } from '@dataconnect/generated';
```

> [!NOTE]
> **Note:** Generated JavaScript client SDKs use the same names for interfaces and operations as generated Node.js admin SDKs. Be sure to import the correct library for your platform. If you need to use both libraries in the same file, use an import alias or namespaced import for one or both of the libraries.  
>
> `import { listMovies as adminListMovies } from '@dataconnect/admin-generated';`   
> Or:   
> `import * as Admin from '@dataconnect/admin-generated';`

### Use queries from the JavaScript SDK

The generated code will already come with predefined Query Refs. All you need
to do is import and call execute on them.

```javascript
import { executeQuery } from 'firebase/data-connect';
import { listMoviesRef } from '@dataconnect/generated';

const ref = listMoviesRef();
const { data } = await executeQuery(ref);
console.log(data.movies);
```

#### Call SDK query methods

Here's an example using these action shortcut functions:

```javascript
import { listMovies } from '@dataconnect/generated';
function onBtnClick() {
// This will call the generated JS from the CLI and then make an HTTP request out
// to the server.
  listMovies().then(data => showInUI(data)); // == executeQuery(listMoviesRef);
}
```

#### Subscribe to changes

See [Get real-time updates from SQL Connect](https://firebase.google.com/docs/sql-connect/realtime.md.txt).

#### Handle changes to enumeration fields

An app's schema can [contain enumerations](https://firebase.google.com/docs/sql-connect/schemas-guide.md.txt#enumeration-fields),
which can be accessed by your [GraphQL queries](https://firebase.google.com/docs/sql-connect/queries-guide.md.txt#query-enumerations).

As an app's design changes, you may add new enum supported values. For example,
imagine that later in your application's lifecycle you decide to add a
FULLSCREEN value to the `AspectRatio` enum.

In the SQL Connect workflow, you can use local development tooling to
update your queries and SDKs.

However, before you release an updated version of your clients, older deployed
clients may break.

##### Example resilient implementation

Always add a `default` branch to a `switch` statement over the enum values, or
an `else` branch to an `if/else if` block comparing against the enum values.
This is not enforced by the JavaScript/TypeScript language, but is the way to
make client code robust in the case that new enum values are added.

```javascript
const queryResult = await getOldestMovie();

if (queryResult.data) {
  // we can use a switch statement's "default" case to check for unexpected values
  const oldestMovieAspectRatio = queryResult.data.originalAspectRatio;
  switch (oldestMovieAspectRatio) {
      case AspectRatio.ACADEMY:
      case AspectRatio.WIDESCREEN:
      case AspectRatio.ANAMORPHIC:
        console.log('This movie was filmed in Academy, widescreen or anamorphic aspect ratio!');
        break;
      default:
        // the default case will catch FULLSCREEN, UNAVAILABLE or _UNKNOWN
        // it will also catch unexpected values the SDK isn't aware of, such as CINEMASCOPE
        console.log('This movie was was NOT filmed in Academy, widescreen or anamorphic.');
        break;
  }

  // alternatively, we can check to see if the returned enum value is a known value
  if (!Object.values(AspectRatio).includes(oldestMovieAspectRatio)) {
    console.log(`Unrecognized aspect ratio: ${oldestAspectRatio}`);
  }
} else {
  console.log("no movies found!");
}
```

### Use mutations from the JavaScript SDK

Mutations are accessible the same way as queries.

```javascript
import { executeMutation } from 'firebase/data-connect';
import { createMovieRef } from '@dataconnect/generated';

const { data } = await executeMutation(createMovieRef({ movie: 'Empire Strikes Back' }));
```

### Connect to the SQL Connect emulator

Optionally, you can connect to the emulator by calling
`connectDataConnectEmulator` and then passing in the SQL Connect
instance, like so:

```javascript
import { connectDataConnectEmulator } from 'firebase/data-connect';
import { connectorConfig } from '@dataconnect/generated';

const dataConnect = getDataConnect(connectorConfig);
connectDataConnectEmulator(dataConnect, 'localhost', 9399);`

// Make calls from your app

```

To switch to production resources, comment out lines for connecting to the
emulator.

> [!NOTE]
> **Note:** Calling `getDataConnect` is only required if you'd like to connect to the SQL Connect emulator. Otherwise the generated SDK will automatically create an instance of the `DataConnect` object for you.

## Enable client-side caching

SQL Connect has an optional client-side caching feature, which you
can enable by editing the `connector.yaml` file. When this feature is enabled,
the generated client SDKs will locally cache query responses, which can reduce
the number of database requests your app makes and enables the
database-dependent portions of your app to work when network availability is
interrupted.

To enable client-side caching, add a client caching configuration to your
connector configuration:


```yaml
generate:
  javascriptSdk:
    outputDir: ../web/
    package: "@dataconnect/generated"
    clientCache:
maxAge: 5s
storage: memory
```

<br />

This configuration has two parameters, both optional:

- `maxAge`: The maximum age a cached response can be before the client SDK
  fetches fresh values. Examples: "0", "30s", "1h30m".

  The default value for `maxAge` is `0`, which means that responses are cached,
  but the client SDK will always fetch fresh values. The cached values will only
  be used when `CACHE_ONLY` is specified to `executeQuery()` and the initial result returned from `subscribe()`.
- `storage`: The client SDK can be configured to cache responses either in
  `persistent` storage or in `memory`. Results cached in `persistent` storage
  will persist across app restarts. In web SDKs, only `memory` storage is supported.

After you update your connector's caching configuration, regenerate your client
SDKs and rebuild your app. Once you have done so, `executeQuery()`
and `subscribe()` will cache
responses and use cached values according to the policy you configured. This
generally happens automatically, without any additional steps on your part;
however, note the following:

- The default behavior of `executeQuery()` is as described above: if a
  result is cached for a query and the cached value is not older than `maxAge`,
  then use the cached value. This default behavior is called the `PREFER_CACHE`
  policy.

  You can also specify to individual invocations of `executeQuery()` to
  either only serve cached values (`CACHE_ONLY`) or to unconditionally fetch
  fresh values from the server (`SERVER_ONLY`).


  ```typescript
  await executeQuery(queryRef, QueryFetchPolicy.CACHE_ONLY);
  ```

  ```typescript
  await executeQuery(queryRef, QueryFetchPolicy.SERVER_ONLY);
  ```
- When you call `subscribe()`, it will always immediately return the
  cached content if it exists, regardless of the `maxAge` setting. Subsequent
  calls to `executeQuery()` will notify listeners according to the
  configured `maxAge`.

## Data types in the SDK

The SQL Connect server represents common GraphQL data types. These
are represented in the SDK as follows.

| SQL Connect Type | TypeScript |
|---|---|
| Timestamp | string |
| Date | string |
| UUID | string |
| Int64 | string |
| Double | Number |
| Float | Number |

## Generate React and Angular SDKs with TanStack

Firebase SQL Connect provides a generated SDK with hooks for React and
Angular using libraries available from our partners at Invertase,
[TanStack Query Firebase](https://react-query-firebase.invertase.dev/).

This library provides a set of hooks that greatly ease handling of asynchronous
tasks with Firebase in your applications.

TanStack comes with its own implementation of client-side caching
and real-time subscriptions, which can work alongside of
SQL Connect's built-in real-time support, but only with some
difficulty. We recommend that you use either the TanStack-based bindings or
SQL Connect's built-in real time support, but **not both**.

Note that SQL Connect's own real-time implementation has some
advantages over the TanStack bindings:

- Normalized caching: SQL Connect implements *normalized caching*, which improves data consistency as well as memory and network efficiency compared to query-level caching. With normalized caching, if an entity updates in one area of your app, it will also update in other areas that use that entity.
- Remote invalidation: SQL Connect can remotely invalidate cached entities on all subscribed devices.

### Initialize your app

First, as with any Firebase web app, initialize your app using the
[standard Firebase sequence](https://firebase.google.com/docs/web/setup.md.txt).

```javascript
initializeApp({...});
```

### Install TanStack Query Firebase packages

install packages for TanStack Query in your project.

### React

```jsx
npm i --save @tanstack/react-query @tanstack-query-firebase/react
npm i --save firebase@latest # Note: React has a peer dependency on ^11.3.0
```

### Angular

```sql
ng add @angular/fire
```

### Generate your React or Angular SDK

As with the standard web SDK, as described [earlier](https://firebase.google.com/docs/sql-connect/web-sdk.md.txt#generate-web), Firebase
tooling handles automatic generation of SDKs based on your schema and operations.

If you just added React or Angular to your project, re-run `firebase init dataconnect:sdk`
to re-configure the generated SDKs to include the extra framework bindings.

### Import libraries

There are four sets of imports needed to initialize your React or Angular
client code: general SQL Connect imports, general TanStack imports,
and specific imports for your JS and React generated SDKs.

Note the `ConnectorConfig` type included in the general imports.

### React

```javascript
// general imports
import { ConnectorConfig, DataConnect, getDataConnect, QueryRef, MutationRef, QueryPromise, MutationPromise } from 'firebase/data-connect';

// TanStack Query-related functions
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

// generated queries and mutations from SDK
import { ListMoviesResponse, connectorConfig } from '@dataconnect/generated';

// generated React hooks from SDK
import { useListAllMovies, useCreateMovie } from "@dataconnect/generated/react";
```

### Angular

```javascript
// general imports
import { ConnectorConfig, DataConnect, getDataConnect, QueryRef, MutationRef, QueryPromise, MutationPromise } from 'firebase/data-connect';

// TanStack Query-related functions
import { provideTanStackQuery, QueryClient } from "@tanstack/angular-query-experimental";

// generated queries and mutations from SDK
import { ListMoviesResponse, connectorConfig } from '@dataconnect/generated';

// generated React hooks from SDK
import { injectListAllMovies, injectCreateMovie } from "@dataconnect/generated/angular";
```

### Use queries and mutations in your React or Angular client

With setup complete, you can incorporate methods from the generated SDK.

In the following snippet, notice the `use`-prefixed method `useListAllMovies` for
React and the `inject`-prefixed method `injectListAllMovies` for Angular, both
from the generated SDK.

### React

All such operations in the generated SDK, both queries and mutations, call TanStackQuery bindings:

- Queries call and return the[TanStack `useDataConnectQuery` hook](https://react-query-firebase.invertase.dev/react/data-connect/querying)
- Mutations call and return the [TanStack `useDataConnectMutation` hook](https://react-query-firebase.invertase.dev/react/data-connect/mutations)

```javascript
import { useListAllMovies } from '@dataconnect/generated/react';

function MyComponent() {
  const { isLoading, data, error } = useListAllMovies();
  if(isLoading) {
    return <div>Loading...</div>
  }
  if(error) {
    return <div> An Error Occurred: {error} </div>
  }
}

// App.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import MyComponent from './my-component';

function App() {
  const queryClient = new QueryClient();
  return <QueryClientProvider client={queryClient}>
    <MyComponent />
  </QueryClientProvider>
}
```

### Angular

> [!NOTE]
> **Note:** `ng add @angular/fire` will automatically do this for you, but if you choose to manually set it up, you can do so with the following code:

```javascript
import { injectAllMovies, connectorConfig } from '@dataconnect/generated/angular';
import { provideDataConnect, getDataConnect } from '@angular/fire/data-connect';
import { provideTanStackQuery, QueryClient } from "@tanstack/angular-query-experimental";
const queryClient = new QueryClient();
...
providers: [
  ...
  provideTanStackQuery(queryClient),
  provideDataConnect(() => {
    const dc = getDataConnect(connectorConfig);
    return dc;
  })
]
```

### Use auto reload queries with React and Angular

You can configure queries to automatically reload when data changes.

### React

```javascript
export class MovieListComponent {
  movies = useListAllMovies();
}

export class AddPostComponent {
  const mutation = useCreateMovie({ invalidate: [listAllMoviesRef()] });
  addMovie() {
    // The following will automatically cause TanStack to reload its listAllMovies query
    mutation.mutate({ title: 'The Matrix });
  }
}
```

### Angular

```javascript
// class
export class MovieListComponent {
  movies = injectListAllMovies();
}

// template
@if (movies.isPending()) {
    Loading...
}
@if (movies.error()) {
    An error has occurred: {{  movies.error() }}
}
@if (movies.data(); as data) {
    @for (movie of data.movies; track movie.id) {
    <mat-card appearance="outlined">
        <mat-card-content>{{movie.description}}</mat-card-content>
    </mat-card>
    } @empty {
        <h2>No items!</h2>
    }
}
```

### Connect to the SQL Connect emulator

Optionally, you can connect to the emulator by calling
`connectDataConnectEmulator` and then passing in the SQL Connect
instance to your generated hook, like so:

### React

```javascript
import { getDataConnect, connectDataConnectEmulator } from 'firebase/data-connect';
import { connectorConfig } from '@dataconnect/generated';
import { useListAllMovies } from '@dataconnect/generated/react';

const dc = getDataConnect(connectorConfig);
connectDataConnectEmulator(dc, 'localhost', 9399);

class AppComponent() {
  ...
  const { isLoading, data, error } = useListAllMovies(dc);
  ...
}
```

### Angular

```javascript
// app.config.ts
import { provideDataConnect } from '@angular/fire/data-connect';
import { getDataConnect, connectDataConnectEmulator } from 'firebase/data-connect';
provideDataConnect(() => {
  const dc = getDataConnect(connectorConfig);
  connectDataConnectEmulator(dc, 'localhost', 9399);
  return dc;
}),
```

To switch to production resources, comment out lines for connecting to the
emulator.

> [!NOTE]
> **Note:** Calling `getDataConnect` is only required if you'd like to connect to the SQL Connect emulator. Otherwise the generated SDK will automatically create an instance of the `DataConnect` object for you.

### Update SDKs while prototyping

If you have SQL Connect VS Code extension installed, it will always keep generated
SDKs up to date.

If you don't use SQL Connect VS Code extension, you can use Firebase CLI
to keep generated SDKs up to date.

```bash
firebase dataconnect:sdk:generate --watch
```

### Generate SDKs in build pipelines

You can use the Firebase CLI to generate SQL Connect SDKs in CI/CD build processes.

```bash
firebase dataconnect:sdk:generate
```