The Firebase Admin SDK is a set of server libraries that lets you interact with
Firebase from privileged environments to perform actions like performing queries
and mutations on a Firebase SQL Connect service for bulk data management
and other operations with elevated privileges and impersonated credentials.

The Admin SDK provides you with an API to call operations in both
read/write and read-only modes. With the read-only operations, you have the
peace of mind of implementing administrative functions that cannot modify data
in your databases.

## Admin SDK Setup

To get started using the with Firebase SQL Connect on your server, you'll
first need to [install and set up the Admin SDK for
Node.js](https://firebase.google.com/docs/admin/setup).

## Initialize the Admin SDK in your scripts

To initialize the SDK, import the SQL Connect extensions and
declare your project service ID and location.

```javascript

import { initializeApp } from 'firebase-admin/app';
import { getDataConnect } from 'firebase-admin/data-connect';

// If you'd like to use OAuth2 flows and other credentials to log in,
// visit https://firebase.google.com/docs/admin/setup#initialize-sdk
// for alternative ways to initialize the SDK.

const app = initializeApp();

const dataConnect = getDataConnect({
    serviceId: 'serviceId',
    location: 'us-west2'
});

```

## Design queries and mutations to use with the Admin SDK

The Admin SDK is useful for running SQL Connect operations, given
the following considerations.

### Understand the SDK and `@auth(level: NO_ACCESS)` operation directive

Since the Admin SDK operates with privileges, it can execute any of your
queries and mutations regardless of access levels set using [`@auth` directives](https://firebase.google.com/docs/sql-connect/authorization-and-security.md.txt), including the `NO_ACCESS` level.

> [!NOTE]
> **Note:** If you do not set an access level for an operation, it is `NO_ACCESS` by default.

If alongside your client operations, you organize your administrative queries
and mutations in `.gql` source files for import into administrative scripts,
Firebase recommends that you mark the administrative operations without any
authorization access level, or perhaps be more explicit and set them as
`NO_ACCESS`. Either way, this prevents such operations from being executed from
clients or in other non-privileged contexts.

### Use the SDK with the SQL Connect emulator

In prototype and test environments, it can be useful to perform data seeding and
other operations on local data. The Admin SDK lets you simplify your
workflows since it can ignore authentication and authorization for local flows.
(You can also explicitly opt in to complying with your operations'
authentication and authorization configuration with user impersonation.)

The Firebase Admin SDKs automatically connects to the SQL Connect
emulator when the `DATA_CONNECT_EMULATOR_HOST` environment variable is set:

```bash
export DATA_CONNECT_EMULATOR_HOST="127.0.0.1:9399"
```

For more information, see:

- The [guide for data seeding in local development](https://firebase.google.com/docs/sql-connect/data-seeding-bulk-operations.md.txt)
- The [SQL Connect emulator documentation](https://firebase.google.com/docs/sql-connect/data-connect-emulator-suite.md.txt).

## Run admin operations

The Admin SDK is provided for privileged operations on your critical data.

The Admin SDK provides three sets of APIs:

- Generated admin SDKs, which are type-safe SDKs generated from your `gql` definitions in the same way that you generate client SDKs.
- A general interface for running arbitrary GraphQL operations, in which your code implements queries and mutations and passes them to the read-write `executeGraphql` method or the read-only `executeGraphqlRead` method.
- A specialized interface for bulk data operations, which instead of generic `executeGraphql` methods, exposes dedicated methods for mutation operations: `insert`, `insertMany`, `upsert`, and `upsertMany`.

### Manage data with generated SDKs

You [generate admin SDKs](https://firebase.google.com/docs/sql-connect/generate-admin-sdk.md.txt#generate-admin-sdk)
from your `gql` definitions in the same way that you generate client SDKs.

The generated admin SDK contains interfaces and functions that correspond with
your `gql` definitions, which you can use to perform operations on your
database. For example, suppose you generated an SDK for a database of songs,
along with a query, `getSongs`:

```typescript
import { initializeApp } from "firebase-admin/app";
import { getSongs } from "@dataconnect/admin-generated";

const adminApp = initializeApp();

const songs = await getSongs(
  { limit: 4 },
  { impersonate: { unauthenticated: true } }
);
```

Or, to specify a connector configuration:

```typescript
import { initializeApp } from "firebase-admin/app";
import { getDataConnect } from "firebase-admin/data-connect";
import {
  connectorConfig,
  getSongs,
} from "@dataconnect/admin-generated";

const adminApp = initializeApp();
const adminDc = getDataConnect(connectorConfig);

const songs = await getSongs(
  adminDc,
  { limit: 4 },
  { impersonate: { unauthenticated: true } }
);
```

> [!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';`

#### Impersonating an unauthenticated user

Admin SDKs are intended to be run from trusted environments, and therefore have
unrestricted access to your databases.

When you run public operations with the admin SDK, you should avoid running the
operation with full administrator privileges (following the principle of least
privilege). Instead, you should run the operation either as an impersonated user
(see the next section), or as an impersonated unauthenticated user.
Unauthenticated users can only run operations marked as `PUBLIC`.

In the example above, the `getSongs` query is executed as an unauthenticated
user.

#### Impersonating a user

You can also perform operations on behalf of specific users by passing part or
all of a Firebase Authentication token in the `impersonate` option; at a minimum, you
must specify the user's user ID in the sub claim. (This is the same value as the
[`auth.uid` server value](https://firebase.google.com/docs/sql-connect/mutations-guide.md.txt#use_check_checkmessage_and_redact)
you can reference in SQL Connect GraphQL operations.)

When you impersonate a user, the operation will succeed only if the user data
you provided passes the authentication checks specified in your GraphQL
definition.

If you're calling the generated SDK from a publicly accessible endpoint, it is
crucial that the endpoint require authentication and that you validate the
integrity of the authentication token before you use it to impersonate a user.

When using callable Cloud Functions, the authentication token is
automatically verified and you can use it as in the following example:

```typescript
import { HttpsError, onCall } from "firebase-functions/https";

export const callableExample = onCall(async (req) => {
    const authClaims = req.auth?.token;
    if (!authClaims) {
        throw new HttpsError("unauthenticated", "Unauthorized");
    }

    const favoriteSongs = await getMyFavoriteSongs(
        undefined,
        { impersonate: { authClaims } }
    );

    // ...
});
```

Otherwise, use the Admin SDK's `verifyIdToken` method to validate and decode
the authentication token. For example, suppose your endpoint is implemented as a
plain HTTP function and you have passed the Firebase Authentication token
to your endpoint using the `authorization` header, as is standard:

```typescript
import { getAuth } from "firebase-admin/auth";
import { onRequest } from "firebase-functions/https";

const auth = getAuth();

export const httpExample = onRequest(async (req, res) => {
    const token = req.header("authorization")?.replace(/^bearer\s+/i, "");
    if (!token) {
        res.sendStatus(401);
        return;
    }
    let authClaims;
    try {
        authClaims = await auth.verifyIdToken(token);
    } catch {
        res.sendStatus(401);
        return;
    }

    const favoriteSongs = await getMyFavoriteSongs(
        undefined,
        { impersonate: { authClaims } }
    );

    // ...
});
```

Only when performing true administrative tasks, such as data migration, from a
secure, non-publicly-accessible environment, should you specify a user ID that
did not originate from a verifiable source:

```typescript
// Never do this if end users can initiate execution of the code!
const favoriteSongs = await getMyFavoriteSongs(
  undefined,
  { impersonate: { authClaims } }
);
```

#### Running with unrestricted access

If you're performing an operation that requires admin level permissions, omit
the impersonate parameter from the call:

```typescript
await upsertSong(adminDc, {
  title: songTitle_one,
  instrumentsUsed: [Instrument.VOCAL],
});
```

An operation called in this manner has complete access to the database. If you
have queries or mutations intended only to be used for administration purposes,
you should define them with the `@auth(level: NO_ACCESS)` directive. Doing so
ensures that only admin-level callers can execute these operations.

### Manage data with `executeGraphql` methods

If you need to execute one-off operations for which you have not defined `gql`
mutations or queries, you can use the `executeGraphql` method or the read-only
`executeGraphqlRead` method.

#### Perform custom nested relational operations (one-to-many and one-to-one) using `executeGraphql`

When calling operations from the Admin SDK in a privileged environment, you
don't need to specify the `@allow` directive on your variables for standard flat
(non-nested) insertions (such as inserting individual Movie or Actor records).

However, for nested relational inserts (such as inserting a movie along with its
initial reviews, or a user and their profile), you must still declare the
`@allow` directive on your variable definition to statically allow the nested
relational fields inside the compiled variable schema.

> [!NOTE]
> **Note:** In the nested child payload, don't specify the parent foreign key (for example, `movieId` in nested reviews), as the executor resolves and assigns this automatically. For one-to-many relationships, the nested payload takes an array of child objects. For one-to-one relationships, it takes a single object.

```javascript
// Custom GraphQL mutation with nested relational insert (one-to-many)
const customMutation = `
  mutation CustomBatchInsert(
    $data: [Movie_Data!]!
    @allow(fields: "title genre reviews_on_movie { rating reviewText }")
  ) {
    movie_insertMany(data: $data)
  }
`;
const variables = {
  data: [
    {
      title: "Interstellar",
      genre: "Sci-Fi",
      reviews_on_movie: [
        {
          rating: 5,
          reviewText: "Visually stunning and emotionally powerful.",
          user: { id: "user-789" }
        }
      ]
    }
  ]
};
const response = await dataConnect.executeGraphql(customMutation, { variables });
```

```javascript
// Custom GraphQL mutation with a one-to-one nested relational insert
const customMutationOneToOne = `
  mutation CustomBatchInsertOneToOne(
    $data: [User_Data!]!
    @allow(fields: "name profile_on_user { bio }")
  ) {
    user_insertMany(data: $data)
  }
`;
const variablesOneToOne = {
  data: [
    {
      name: "Alice",
      profile_on_user: {
        bio: "Avid moviegoer and critic."
      }
    }
  ]
};
const responseOneToOne = await dataConnect.executeGraphql(customMutationOneToOne, { variables: variablesOneToOne });
```

#### Impersonating an unauthenticated user

When you run public operations with the admin SDK, you should avoid running the
operation with full administrator privileges (following the principle of least
privilege). Instead, you should run the operation either as an impersonated user
(see the [next section](https://firebase.google.com/docs/sql-connect/admin-sdk.md.txt#impersonate-user)), or as an impersonated
unauthenticated user. Unauthenticated users can only run operations marked as
`PUBLIC`.

```javascript
// Query to get posts, with authentication level PUBLIC
const queryGetPostsImpersonation = `
    query getPosts @auth(level: PUBLIC) {
        posts {
          description
        }
    }`;

// Attempt to access data as an unauthenticated user
const optionsUnauthenticated: GraphqlOptions<undefined> = {
    impersonate: {
        unauthenticated: true
    }
};

// executeGraphql with impersonated unauthenticated user scope
const gqlResponse = await dataConnect.executeGraphql<UserData, undefined>(queryGetPostsImpersonation, optionsUnauthenticated);
```

#### Impersonating a user

There are also use cases where you want your scripts to modify user data based
on limited credentials, on behalf of a specific user. This approach honors the
principle of least privilege.

To use this interface, gather information from a customized JWT auth token that
follows the [Authentication token format](https://firebase.google.com/docs/sql-connect/cel-reference.md.txt#auth-token-contents).
Also see the [custom tokens guide](https://firebase.google.com/docs/auth/admin/create-custom-tokens.md.txt).

```javascript
// Get the current user's data
const queryGetUserImpersonation = `
    query getUser @auth(level: USER) {
        user(key: {uid_expr: "auth.uid"}) {
            id,
            name
        }
    }`;

// Impersonate a user with the specified auth claims
const optionsAuthenticated: GraphqlOptions<undefined> = {
    impersonate: {
        authClaims: {
            sub: 'QVBJcy5ndXJ1'
        }
    }
};

// executeGraphql with impersonated authenticated user scope
const gqlResponse = await dataConnect.executeGraphql<UserData, undefined>(queryGetUserImpersonation, optionsAuthenticated);

// gqlResponse -> { "data": { "user": { "id": "QVBJcy5ndXJ1", "name": "Fred" } } }

```

#### Use administrative credentials

If you're performing an operation that requires admin level permissions, omit
the impersonate parameter from the call:

```javascript
// User can be publicly accessible, or restricted to admins
const query = "query getProfile(id: AuthID) { user(id: $id) { id name } }";

interface UserData {
  user: {
    id: string;
    name: string;
  };
}

export interface UserVariables {
  id: string;
}

const options:GraphqlOptions<UserVariables> = { variables: { id: "QVBJcy5ndXJ1" } };

// executeGraphql
const gqlResponse = await dataConnect.executeGraphql<UserData, UserVariables>(query, options);

// executeGraphqlRead (similar to previous sample but only for read operations)
const gqlResponse = await dataConnect.executeGraphqlRead<UserData, UserVariables>(query, options);

// gqlResponse -> { "data": { "user": { "id": "QVBJcy5ndXJ1", "name": "Fred" } } }
```

An operation called in this manner has complete access to the database. If you
have queries or mutations intended only to be used for administration purposes,
you should define them with the `@auth(level: NO_ACCESS)` directive. Doing so
ensures that only admin-level callers can execute these operations.

### Perform bulk data insert operations

Firebase recommends you use the Admin SDK for bulk data operations on
production databases.

The SDK provides the following methods for working with bulk data. From the
provided arguments, each method constructs and executes a GraphQL mutation.

> [!NOTE]
> **Note:** Specific workflows are described in more detail in the [Data seeding and bulk data management guide](https://firebase.google.com/docs/sql-connect/data-seeding-bulk-operations.md.txt#bulk-data-adminsdk).

```javascript

// Methods of the bulk operations API
// dc is a SQL Connect admin instance from getDataConnect

const resp = await dc.insert("movie" /*table name*/, data[0]);
const resp = await dc.insertMany("movie" /*table name*/, data);
const resp = await dc.upsert("movie" /*table name*/, data[0]);
const resp = await dc.upsertMany("movie" /*table name*/, data);
```

#### Nested relational operations (one-to-many and one-to-one)

The Admin SDK supports relational data in bulk operations. Just like with
custom mutations, use an array of objects for one-to-many relationships and a
single object for one-to-one relationships:

```javascript
// Example of inserting a movie with nested reviews (one-to-many)
const moviesData = [
  {
    title: "Interstellar",
    genre: "Sci-Fi",
    reviews_on_movie: [
      {
        rating: 5,
        reviewText: "Visually stunning and emotionally powerful.",
        user: { id: "user-789" }
      }
    ]
  }
];

// Atomically insert movies and their reviews in a single database round-trip.
const response = await dc.insertMany("movie", moviesData);
```

```javascript
// Example of inserting a user with a nested profile (one-to-one)
const usersData = [
  {
    name: "Alice",
    profile_on_user: {
      bio: "Avid moviegoer and critic."
    }
  }
];

// Atomically insert users and their profiles.
const userResponse = await dc.insertMany("user", usersData);
```

#### Performance notes for bulk operations

Each request to the backend will incur one round trip to Cloud SQL,
so the
more you batch, the higher the throughput is.

However, the larger the batch size, the longer the generated SQL statement is.
When the PostgreSQL SQL statement length limit is reached, you will encounter an
error.

In practice, experiment to find the appropriate batch size for your workload.

## What's next?

- Learn about [seeding your databases with data using the Admin SDK](https://firebase.google.com/docs/sql-connect/data-seeding-bulk-operations.md.txt)
- Review the [API for the Admin SDK](https://firebase.google.com/docs/reference/admin/node/firebase-admin.data-connect.md.txt).
- Use the Firebase CLI and Google Cloud console for other project management operations, like [managing schemas and connectors](https://firebase.google.com/docs/sql-connect/manage-schemas-and-connectors.md.txt) and [managing services and databases](https://firebase.google.com/docs/sql-connect/manage-services-and-databases.md.txt).