Snowflake App Runtime SDK for Node.js¶
The Snowflake App Runtime SDK for Node.js,
@snowflake/sar-sdk, gives
the server code of a Snowflake App Runtime app access to Snowflake. It runs
queries with owner’s rights or caller’s rights, handles large and long-running
results, reads secrets, and calls the account’s REST APIs. In a deployed app it
uses the credentials Snowflake provides; on your machine it uses a connection
configured in connections.toml or config.toml. The SDK works with any Node.js web framework and is built on the
Snowflake Node.js driver, which it
installs as a dependency.
The Next.js template that the
snowflake-apps
skill scaffolds is built on the SDK: its lib/snowflake.ts creates the client
for you. For how to query from that template, see
Query Snowflake.
The SDK runs in Node.js server code only. Don’t import it from code that runs in the browser. Browser code calls your app’s own server endpoints, and those endpoints query Snowflake.
Requirements¶
- Node.js 22 or later.
- For TypeScript, set
moduleResolutiontoNodeNextorBundlerand include the Node.js type declarations. The SDK is tested with TypeScript 5.9.
The package includes both ES module and CommonJS builds.
Install the SDK¶
Create a client¶
Create one client per server process, reuse it across requests, and close it
when the process shuts down. Storing the client on globalThis keeps a single
client when a development server reloads your modules:
fromDefault() reads the
session token
that Snowflake writes for a deployed app, and the default connection in
connections.toml or config.toml when that token isn’t there. The client reads credentials only when a
query or request needs them, and reads the session token again for each new
session, so token rotation needs no restart. For callerAsOwner, see
Run queries with caller’s rights.
To stop the client, call await snowflake.close(). It stops new work, gives
running queries a grace period (10 seconds by default), and then releases its
sessions.
Run queries¶
query() runs a statement with owner’s rights, as the app’s service identity,
and returns its rows:
- Each row is an object keyed by column name. Unquoted column names are
uppercase, such as
NAMEandPRICE. NUMBERvalues are returned as strings, which keeps their full precision. To get JavaScript numbers, passnumbers: "number". A value that a JavaScript number can’t represent exactly fails withRESULT_CONVERSION_UNSUPPORTEDinstead of being rounded.- The result also contains
queryId,columns,rowCount(the number of rows returned), and, for DML statements,affectedRows.
Each query runs in its own session. For session state that spans statements, see Run a transaction. For owner’s rights and caller’s rights, bind variables, and choosing a warehouse, see Query Snowflake.
Run queries with caller’s rights¶
asCaller() returns a caller view of the client that runs queries as the
signed-in user who sent the request. Create it from the request’s headers:
- The caller view reads the caller’s token from the
Sf-Context-Current-User-Tokenheader that Snowflake adds to each request. Use the caller view only within the request it was created for. - A caller view created without caller headers throws
CALLER_CONTEXT_REQUIRED. It never falls back to owner’s rights. - Caller queries use the user’s default role and warehouse. For users without a
default warehouse, pass
warehousewith each query, or set theSNOWFLAKE_WAREHOUSEenvironment variable for the app. - Caller queries need caller grants on the data and the warehouses they use. See Scoping caller’s rights.
On your machine, requests don’t carry a caller token. When you create the client
with callerAsOwner: true, as in the
client example, caller views
run with the app’s own credentials instead of failing. Set callerAsOwner from
a condition that’s true only during development, so a deployed app keeps the
caller’s identity.
Stream large results¶
By default, query() fails with RESULT_LIMIT_EXCEEDED when a result exceeds
100,000 rows or 32 MiB. For larger results, use stream() and read the rows as
they arrive:
A stream has one reader. Leaving the loop early, or calling stream.close(),
releases its session.
Run a transaction¶
transaction() runs its callback in one session. It commits when the callback
finishes and rolls back when the callback throws:
Await every statement in the callback. Keep DDL statements, such as
CREATE TEMPORARY TABLE, out of the callback, because DDL commits an open
transaction in Snowflake. See DDL.
For other work that spans statements, such as temporary tables or session
variables, use withConnection(). It passes your callback a Snowflake Node.js
driver connection and releases the connection when the callback’s promise
settles.
Run long-running queries¶
A query that can outlast an HTTP request, or the server instance that started it, can run as a detached query. One request submits the query and returns right away. Later requests check on it, and any instance of the app can handle them:
In these examples, jobs stands for storage that your app provides and that all
of its instances share.
pollQuery()checks the query once and returnspendingwhile it runs. The browser decides when to ask again.- The reference holds the query ID and the identity that ran it, but no
credentials. It isn’t proof of access: store it on the server with the user it
belongs to, and never accept a reference from the browser. Each lookup checks
that the current identity matches the reference, and fails with
QUERY_IDENTITY_MISMATCHotherwise. - For results larger than the row and size limits saved with the reference, use
streamQuery(). To check status without reading results, usegetQueryStatus(), and to stop a query, usecancelQuery(). Closing the browser doesn’t cancel the query. - Submitting a detached query requires
ABORT_DETACHED_QUERY
set to
FALSEand AUTOCOMMIT set toTRUE. Otherwise the SDK throwsDETACHED_QUERY_NOT_SUPPORTED.
Caller views have the same methods. To look up a caller’s query from a later
request, the owner role needs the CALLER MONITOR caller grant on the
warehouse; without it, the lookup fails with QUERY_UNAVAILABLE. To cancel a
caller’s query, it needs CALLER OPERATE; without it, the cancel request fails
with Snowflake error 003001, and the query keeps running.
To wait for a query within one process instead, use submit(). It returns a
handle with status(), result(), and cancel(). The handle exists only in the
process that created it.
Read secrets¶
To read a secret that you declare under
secrets
in app.yml, pass its name:
In a deployed app, the SDK reads the
secret files under
/secrets/<name>/ on every call, so a rotated secret takes effect without a
restart. On your machine, it reads environment variables instead, such as
SNOWFLAKE_SECRET_API_KEY_SECRET_STRING, or SNOWFLAKE_SECRET_DB_CREDS_USERNAME
and SNOWFLAKE_SECRET_DB_CREDS_PASSWORD. Reading secrets doesn’t open a
Snowflake session.
Call account REST APIs¶
fetch() sends an authenticated request to one of the account’s
REST APIs with the same
identity as the client’s queries, and returns a standard Response:
- Pass a path on the account, not a full URL.
- To call the API as the signed-in user, use
snowflake.asCaller({ headers: request.headers }).fetch(path). fetch()needs an OAuth or programmatic access token connection. A deployed app’s session token is an OAuth token. On your machine, a connection that signs in with a password, a key pair, or a browser can run queries but can’t callfetch().- Each request times out after 120 seconds by default. Pass
timeoutMsfor longer requests, such as streaming responses. - Read or cancel every response body. HTTP error statuses aren’t thrown or retried.
Connect from your machine¶
On your machine, fromDefault() reads the default connection from
connections.toml or config.toml, the configuration files that the Snowflake
CLI and other Snowflake tools share. For where those files live and how to
define a connection, see
Managing Snowflake connections. To use
another connection, pass fromToml({ name: "my_connection" }) to
createConnectionOptions() instead of fromDefault().
- On macOS and Linux, the configuration file must be readable only by you
(
chmod 0600). - For a connection that signs in with a browser (
EXTERNALBROWSER), the SDK turns on the driver’s token cache, so you aren’t asked to sign in for every query. The account must allow it with ALLOW_ID_TOKEN.
Handle errors¶
Errors that the SDK throws have a stable code. When Snowflake rejects a
statement, details.errorCode holds Snowflake’s error code:
- The SDK never retries SQL. Before you retry a write yourself, check
details.completion:"succeeded"means the statement ran even though reading its result failed, and"unknown"means it might have run. - Snowflake’s error message can quote values from the SQL, so it’s kept out of
the error’s
messageandtoJSON()and appears only in itscause. Log errors on the server and return a generic message to the user. - Configuration errors include a
details.nextStephint.
The following table lists common error codes:
| Code | Meaning |
|---|---|
AUTH_FAILED | Signing in to Snowflake failed. |
CALLER_CONTEXT_REQUIRED | A caller view was created without the caller’s token. |
CONFIG_NOT_FOUND | The connection or configuration file wasn’t found. |
QUERY_FAILED | Snowflake rejected or failed the statement. See details.errorCode. |
QUERY_UNAVAILABLE | A long-running query’s status or results aren’t available: it expired, the identity can’t see it, or it’s unknown. |
RESULT_LIMIT_EXCEEDED | The result exceeds the |
TIMEOUT | An operation didn’t finish before its deadline. |
API reference¶
The package README is the full reference. It covers every option, default, and error code, along with connection pool settings, logging, and direct use of the Snowflake Node.js driver. The examples show complete programs, and the changelog lists the changes in each release. To report a problem, open an issue in the snowflakedb/sar-sdk repository.