Databases

Managed Postgres with auto-provisioning and credentials API

Overview

Every project can have managed Postgres databases. The SDK provides methods to list, create, and retrieve credentials for databases. The ensure method makes database provisioning idempotent — call it on every deploy and it will only create the database if it does not already exist.

All database methods are available on r.databases.

Methods

MethodDescription
list(params)List all databases for a project.
create(input)Create a new database.
ensure(input)Ensure a database exists (create if not). Idempotent.
getCredentials(params)Get connection credentials for a database.
query(input)Execute a SQL query against a project’s database.

List databases

1const { data: databases } = await r.databases.list({
2 project_id: 'proj_123',
3});
4
5for (const db of databases) {
6 console.log(`${db.name} — ${db.status}`);
7}

Parameters:

ParameterTypeRequiredDescription
project_idstringYesThe project to list databases for.

Returns: SingleResponse<Database[]>

1interface Database {
2 id: string;
3 name: string;
4 status: string; // 'provisioning' | 'ready' | 'error'
5 project_id: string;
6 created_at: string;
7}

Create a database

1const { data: db } = await r.databases.create({
2 project_id: 'proj_123',
3 name: 'main',
4});
5
6console.log(db.id); // 'db_abc...'
7console.log(db.name); // 'main'
8console.log(db.status); // 'provisioning'

Input fields:

FieldTypeRequiredDescription
project_idstringYesProject to create the database in.
namestringYesDatabase name. Must be unique within the project.

Ensure a database (idempotent)

The ensure method creates the database if it does not exist, or returns the existing one. This is the recommended approach for deployment scripts and CI/CD pipelines.

1const { data: db } = await r.databases.ensure({
2 project_id: 'proj_123',
3 name: 'main',
4});
5
6console.log(db.name); // 'main'
7console.log(db.status); // 'ready' (if already provisioned)

Input fields:

FieldTypeRequiredDescription
project_idstringYesProject to ensure the database in.
namestring?NoDatabase name. Defaults to 'default' if omitted.

ensure is idempotent. Calling it multiple times with the same project and name will always return the same database without creating duplicates.

Get credentials

Retrieve the connection credentials for a database. Credentials are returned as individual fields and a ready-to-use connection string.

1const { data: creds } = await r.databases.getCredentials({
2 project_id: 'proj_123',
3 name: 'main',
4});
5
6console.log(creds.host);
7console.log(creds.port);
8console.log(creds.database);
9console.log(creds.username);
10console.log(creds.password);
11console.log(creds.connection_string);
12// postgres://user:pass@host:port/database

Parameters:

ParameterTypeRequiredDescription
project_idstringYesThe project that owns the database.
namestringYesThe database name (not the database ID).

getCredentials takes project_id and name — not a database ID. This is intentional: the database name is the stable identifier you use in your code.

Returns: SingleResponse<DatabaseCredentials>

1interface DatabaseCredentials {
2 host: string;
3 port: number;
4 database: string;
5 username: string;
6 password: string;
7 connection_string: string;
8}

Query a database

Execute SQL queries directly through the SDK — no need to manage database connections yourself.

1// Create a table
2await r.databases.query({
3 project_id: 'proj_123',
4 sql: `CREATE TABLE IF NOT EXISTS orders (
5 id SERIAL PRIMARY KEY,
6 user_id TEXT NOT NULL,
7 amount INTEGER NOT NULL,
8 created_at TIMESTAMP DEFAULT NOW()
9 )`,
10});
11
12// Insert data with parameterized queries (prevents SQL injection)
13await r.databases.query({
14 project_id: 'proj_123',
15 sql: 'INSERT INTO orders (user_id, amount) VALUES ($1, $2)',
16 params: ['user_abc', 4999],
17});
18
19// Read data
20const { data: result } = await r.databases.query({
21 project_id: 'proj_123',
22 sql: 'SELECT * FROM orders WHERE user_id = $1 ORDER BY created_at DESC',
23 params: ['user_abc'],
24});
25
26console.log(result.columns); // ['id', 'user_id', 'amount', 'created_at']
27console.log(result.rows); // [{ id: 1, user_id: 'user_abc', amount: 4999, ... }]
28console.log(result.rowCount); // 1

Parameters:

ParameterTypeRequiredDescription
project_idstringYesThe project that owns the database.
sqlstringYesSQL query to execute (max 10,000 chars).
paramsunknown[]NoParameterized query values (use $1, $2, etc.).
database_namestringNoDatabase name (defaults to 'default').

Returns: SingleResponse<QueryResult>

1interface QueryResult {
2 columns: string[];
3 rows: Record<string, unknown>[];
4 rowCount: number;
5}

Always use parameterized queries ($1, $2) instead of string interpolation to prevent SQL injection.

Full example: provision and migrate

This example shows a complete workflow: ensure a database exists, get credentials, and run a migration via sandbox code execution.

1import { Recursiv } from '@recursiv/sdk';
2
3const r = new Recursiv();
4
5const PROJECT_ID = 'proj_123';
6
7// 1. Ensure the database exists
8const { data: db } = await r.databases.ensure({
9 project_id: PROJECT_ID,
10 name: 'main',
11});
12console.log(`Database "${db.name}" status: ${db.status}`);
13
14// 2. Get connection credentials
15const { data: creds } = await r.databases.getCredentials({
16 project_id: PROJECT_ID,
17 name: 'main',
18});
19console.log('Connection string:', creds.connection_string);
20
21// 3. Run a migration in the sandbox
22const { data: result } = await r.projects.executeCode(PROJECT_ID, {
23 code: `
24 import pg from 'pg';
25
26 const client = new pg.Client({
27 connectionString: '${creds.connection_string}',
28 });
29 await client.connect();
30
31 await client.query(\`
32 CREATE TABLE IF NOT EXISTS users (
33 id SERIAL PRIMARY KEY,
34 email TEXT UNIQUE NOT NULL,
35 name TEXT NOT NULL,
36 created_at TIMESTAMPTZ DEFAULT NOW()
37 );
38 \`);
39
40 const result = await client.query('SELECT COUNT(*) FROM users');
41 console.log('Users table ready. Row count:', result.rows[0].count);
42
43 await client.end();
44 `,
45 language: 'typescript',
46});
47
48console.log('Migration output:', result.output);

Using credentials with ORMs

The credentials object works directly with popular Node.js database libraries:

With Drizzle

1import { drizzle } from 'drizzle-orm/node-postgres';
2import pg from 'pg';
3
4const { data: creds } = await r.databases.getCredentials({
5 project_id: 'proj_123',
6 name: 'main',
7});
8
9const pool = new pg.Pool({
10 connectionString: creds.connection_string,
11});
12
13const db = drizzle(pool);

With Prisma

1// Use the connection string as DATABASE_URL in your .env
2const { data: creds } = await r.databases.getCredentials({
3 project_id: 'proj_123',
4 name: 'main',
5});
6
7process.env.DATABASE_URL = creds.connection_string;
8// Then initialize Prisma normally