DEV Community

Frank
Frank

Posted on

How to Provision Neon PostgreSQL with the New TypeScript SDK

I saw Neon’s announcement of the @neon/sdk TypeScript client this morning and instantly thought about the countless scripts I’ve written to spin up test databases for CI pipelines. Until now, I’ve been hammering the Neon REST endpoints with curl or axios, manually handling the “operation‑in‑progress” polling loop that the OpenAPI spec requires. The new SDK promises a first‑class, typed wrapper around those endpoints, handling the async lifecycle for us. For a developer who lives in the Node.js/TypeScript ecosystem, that feels like a productivity win the moment I can replace a dozen lines of boilerplate with a clean, promise‑based call.

Below I walk through the parts of the SDK that matter most to a day‑to‑day dev: authenticating, provisioning a new project, creating a branch, and finally pulling the connection string to run migrations or tests. I’ll also touch on the built‑in polling helpers that the SDK adds, so you don’t have to write your own setInterval loops.


Why a Typed SDK Matters Right Now

Neon’s API is already OpenAPI‑compliant, which means you can generate client code in any language. However, the generated code is often generic, missing the ergonomics you expect from a hand‑crafted library: proper TypeScript types, sensible defaults, and higher‑level helpers. The @neon/sdk fills that gap for us JavaScript/TypeScript developers.

  • Typed request/response objects – No more “any” creeping into your CI scripts.
  • Built‑in operation polling – Neon creates resources asynchronously; the SDK abstracts the GET /operations/{id} loop.
  • Zero‑config auth – It reads the NEON_API_KEY env var automatically, falling back to a token you pass in.
  • Consistent error handling – Errors surface as native Error objects with the API’s code and message fields attached.

All of that translates to fewer runtime bugs and a smoother developer experience when you need to spin up a fresh PostgreSQL instance on the fly.


Getting Started: Install and Authenticate

The SDK is published to npm, so adding it to a project is as simple as:

npm i @neon/sdk
Enter fullscreen mode Exit fullscreen mode

Once installed, the client reads the NEON_API_KEY environment variable. If you prefer to pass the token explicitly, you can do that too:

import { NeonClient } from '@neon/sdk';

// Option 1: env var (recommended for CI)
process.env.NEON_API_KEY = 'your‑neon‑api‑key';

// Option 2: explicit token
const client = new NeonClient({ apiKey: 'your‑neon‑api‑key' });
Enter fullscreen mode Exit fullscreen mode

The client constructor also accepts optional configuration like a custom API base URL (useful for testing against a mock server) and request timeout settings.


Provisioning a New Project in One Call

Creating a Neon project involves two steps behind the scenes: a POST /projects request and then waiting for the asynchronous operation to finish. The SDK collapses that into a single createProject method that returns the fully provisioned project object.

import { NeonClient } from '@neon/sdk';

async function provisionProject() {
  const client = new NeonClient();

  // Define the project you want – region, name, and optional settings
  const project = await client.createProject({
    name: 'ci‑test‑project',
    region_id: 'aws-us-east-2', // example region identifier
    // You can also pass initial database settings here if you like
  });

  console.log('Project ready:', project.id);
  return project;
}
Enter fullscreen mode Exit fullscreen mode

The SDK internally sends the POST /projects request, receives an operation ID, and then polls GET /operations/{id} until the status is succeeded. If the operation fails, it throws an error with the API’s diagnostic payload.


Adding a Branch (Database Instance) Quickly

Neon treats each branch as a separate PostgreSQL endpoint, perfect for isolated test environments. The SDK’s createBranch method mirrors the same “create‑and‑wait” pattern:

async function createTestBranch(projectId: string) {
  const client = new NeonClient();

  const branch = await client.createBranch({
    project_id: projectId,
    name: `test‑branch-${Date.now()}`,
    // You can optionally clone from another branch or snapshot
  });

  console.log('Branch ready:', branch.id);
  return branch;
}
Enter fullscreen mode Exit fullscreen mode

Again, you get a fully ready branch object without manually checking the operation status.


Pulling the Connection String for Migrations

Once the branch is up, you’ll need its connection string to run migrations or spin up a Prisma client. The SDK provides a convenience method that resolves the URL directly:

async function getConnectionUrl(branchId: string) {
  const client = new NeonClient();

  const connection = await client.getBranchConnectionUrl({
    branch_id: branchId,
    // optional: include password in the URL (default is true)
    include_password: true,
  });

  console.log('Connection URL:', connection.url);
  return connection.url;
}
Enter fullscreen mode Exit fullscreen mode

You can now feed that URL into any migration tool (e.g., drizzle-kit, Prisma Migrate, or raw psql scripts) without any extra lookups.


Putting It All Together: A One‑Click Test Database

Here’s a minimal script that ties the previous snippets into a reusable helper for CI pipelines:

import { NeonClient } from '@neon/sdk';

export async function createCiDatabase() {
  const client = new NeonClient();

  // 1️⃣ Create a fresh project
  const project = await client.createProject({
    name: `ci‑project-${Date.now()}`,
    region_id: 'aws-us-east-2',
  });

  // 2️⃣ Spin up a branch for this test run
  const branch = await client.createBranch({
    project_id: project.id,
    name: `ci‑branch-${Date.now()}`,
  });

  // 3️⃣ Grab the connection string
  const { url } = await client.getBranchConnectionUrl({
    branch_id: branch.id,
  });

  // Return everything you might need later (e.g., for cleanup)
  return {
    projectId: project.id,
    branchId: branch.id,
    connectionUrl: url,
  };
}

// Example usage in a Jest globalSetup
/*
module.exports = async () => {
  const db = await createCiDatabase();
  process.env.DATABASE_URL = db.connectionUrl;
};
*/
Enter fullscreen mode Exit fullscreen mode

The script is under 30 lines, yet it replaces the dozens of fetch calls and manual polling loops I previously maintained. Because the SDK is typed, my editor now warns me if I pass an invalid region ID or forget a required field.


My Take: Is It Worth Adding to Your Stack?

The @neon/sdk feels like a natural evolution for anyone already using Neon in development or CI. The biggest win is the removal of boilerplate around asynchronous provisioning—no more “while (operation.status !== 'succeeded') …” code littered across repos. The typed API also reduces friction when onboarding new team members; they can explore

Top comments (4)

Collapse
 
topstar_ai profile image
Luis Cruz

I appreciate how the @neon/sdk simplifies provisioning Neon PostgreSQL instances, especially with its built-in polling helpers that handle the async lifecycle for us. The example of collapsing the POST /projects request and waiting for the operation to finish into a single createProject method is particularly useful, as it eliminates the need for manual setInterval loops. I've had similar experiences with other APIs where a well-designed SDK can significantly reduce boilerplate code and improve overall productivity. Have you explored using this SDK in conjunction with other CI/CD tools to further automate your testing pipelines?

Collapse
 
frank_signorini profile image
Frank

Thanks for highlighting the polling helpers and createProject example - they really do simplify things! We've started exploring using the SDK with CI/CD tools for ephemeral test environments, and it shows great promise.

Collapse
 
topstar_ai profile image
Comment deleted
Thread Thread
 
frank_signorini profile image
Frank

Thanks for the thoughtful feedback! I completely agree that moving provisioning into a developer workflow abstraction is key. Exploring

Some comments may only be visible to logged-in visitors. Sign in to view all comments.