# Getting started (/docs/framework/getting-started)



This guide takes you from a new TypeScript project to a typed Salesforce query using Kysoql's native REST transport.

Kysoql is ESM-only and currently requires **Node.js `>=22.12.0`**.

## 1. Install Kysoql [#1-install-kysoql]

<Tabs items="[&#x22;npm&#x22;, &#x22;yarn&#x22;, &#x22;pnpm&#x22;, &#x22;bun&#x22;]" defaultIndex="2">
  <Tab value="npm">
    ```bash
    npm install @kysoql/core @kysoql/rest @kysoql/auth
    npm install --save-dev @kysoql/codegen
    ```
  </Tab>

  <Tab value="yarn">
    ```bash
    yarn add @kysoql/core @kysoql/rest @kysoql/auth
    yarn add --dev @kysoql/codegen
    ```
  </Tab>

  <Tab value="pnpm">
    ```bash
    pnpm add @kysoql/core @kysoql/rest @kysoql/auth
    pnpm add -D @kysoql/codegen
    ```
  </Tab>

  <Tab value="bun">
    ```bash
    bun add @kysoql/core @kysoql/rest @kysoql/auth
    bun add --dev @kysoql/codegen
    ```
  </Tab>
</Tabs>

## 2. Configure Salesforce authentication [#2-configure-salesforce-authentication]

For a server-to-server integration, JWT bearer is a good default when your application can protect a private key.

```ts title="salesforce-auth.ts"
import { SalesforceAuth } from "@kysoql/auth";

const auth = new SalesforceAuth({
  loginUrl: "https://login.salesforce.com",
  clientId: "external-client-app-id",
});

export const getSalesforceSession = () =>
  auth.jwtBearer({
    username: "integration@example.com",
    privateKey: { type: "file", path: "./salesforce-auth-key.pem" },
  });
```

If the PEM comes from a secret manager, use `{ type: "pem", value }`. If your application already has an RS256 `CryptoKey`, use `{ type: "crypto-key", key }`.

See [JWT bearer authentication](/docs/auth/flows/jwt-bearer) for the Salesforce external client app setup.

## 3. Generate your Salesforce schema [#3-generate-your-salesforce-schema]

Create `kysoql.config.ts` and list the objects your application will query:

```ts title="kysoql.config.ts"
import { defineConfig } from "@kysoql/codegen";
import { getSalesforceSession } from "./salesforce-auth";

export default defineConfig({
  auth: getSalesforceSession,
  objects: ["Account", "Contact"],
  schemaName: "SalesforceSchema",
});
```

Run the generator:

<Tabs items="[&#x22;npm&#x22;, &#x22;yarn&#x22;, &#x22;pnpm&#x22;, &#x22;bun&#x22;]" defaultIndex="2">
  <Tab value="npm">
    ```bash
    npx kysoql generate
    ```
  </Tab>

  <Tab value="yarn">
    ```bash
    yarn exec kysoql generate
    ```
  </Tab>

  <Tab value="pnpm">
    ```bash
    pnpm exec kysoql generate
    ```
  </Tab>

  <Tab value="bun">
    ```bash
    bunx kysoql generate
    ```
  </Tab>
</Tabs>

If the project has a `src` directory, the generated file goes to `src/kysoql/salesforce.generated.ts` by default. Otherwise it goes to `kysoql/salesforce.generated.ts`.

The generated schema reflects the objects, fields, relationships, and capabilities visible to the Salesforce user you authenticated as.

## 4. Create a REST-backed database [#4-create-a-rest-backed-database]

Use the same authentication function as an access-token provider. The REST client keeps the current access token in memory and asks for a replacement if Salesforce reports that the session is invalid.

```ts title="db.ts"
import { Kysoql } from "@kysoql/core";
import { createRestExecutor } from "@kysoql/rest";
import { getSalesforceSession } from "./salesforce-auth";
import {
  salesforceSchemaMetadata,
  type SalesforceSchema,
} from "./kysoql/salesforce.generated";

let session = await getSalesforceSession();
const instanceUrl = session.instanceUrl;

const executor = createRestExecutor({
  instanceUrl,
  apiVersion: "65.0",
  accessToken: async ({ refresh }) => {
    if (refresh) {
      const next = await getSalesforceSession();

      if (next.instanceUrl !== instanceUrl) {
        throw new Error("Salesforce instance changed. Recreate the REST client.");
      }

      session = next;
    }

    return session.accessToken;
  },
});

export const db = new Kysoql<SalesforceSchema>({
  executor,
  schemaMetadata: salesforceSchemaMetadata,
});
```

JWT bearer does not use refresh tokens. When the access token expires, the provider performs another JWT exchange.

## 5. Run a typed query [#5-run-a-typed-query]

```ts title="query.ts"
import { db } from "./db";

const accounts = await db
  .selectFrom("Account")
  .select(["Id", "Name"])
  .where("Name", "like", "Acme%")
  .orderBy("Name")
  .limit(25)
  .execute();

for (const account of accounts) {
  console.log(account.Id, account.Name);
}
```

Kysoql compiles the builder to:

```sql
SELECT Id, Name FROM Account WHERE Name LIKE 'Acme%' ORDER BY Name LIMIT 25
```

The result type follows the generated Salesforce schema and the fields you selected. No matching rows returns an empty array.

<Accordions type="single">
  <Accordion title="Do I need JSforce?">
    No. This getting started guide uses `@kysoql/rest`, which sends queries with native `fetch`. Use `@kysoql/jsforce` only when your application already has a JSforce connection you want to reuse.
  </Accordion>

  <Accordion title="Do generated types replace Salesforce runtime checks?">
    No. The generated schema catches many mistakes while you write the query, but Salesforce still applies permissions, API-version behaviour, enabled features, query limits, and runtime validation.
  </Accordion>
</Accordions>

## Next steps [#next-steps]

* [Selecting fields](/docs/framework/guides/selecting)
* [Filtering records](/docs/framework/guides/filtering)
* [Relationships](/docs/framework/guides/relationships)
* [Counts and aggregate reports](/docs/framework/guides/aggregates)
* [Native REST execution](/docs/rest/execution)
