Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions javascript/postgres-js/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/node_modules
/build
/dist
/coverage
112 changes: 112 additions & 0 deletions javascript/postgres-js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Postgres.js with Aurora DSQL

## Overview

This code example demonstrates how to use `Postgres.js` with Amazon Aurora DSQL.
The example shows you how to connect to an Aurora DSQL cluster and perform basic database operations.

Aurora DSQL is a distributed SQL database service that provides high availability and scalability for
your PostgreSQL-compatible applications. `Postgres.js` is a lightweight PostgreSQL client for Node.js that allows
you to interact with PostgreSQL databases using JavaScript code.

## About the code example

The example demonstrates a flexible connection approach that works for both admin and non-admin users:

* When connecting as an **admin user**, the example uses the `public` schema and generates an admin authentication
token.
* When connecting as a **non-admin user**, the example uses a custom `myschema` schema and generates a standard
authentication token.

The code automatically detects the user type and adjusts its behavior accordingly.

## ⚠️ Important

* Running this code might result in charges to your AWS account.
* We recommend that you grant your code least privilege. At most, grant only the
minimum permissions required to perform the task. For more information, see
[Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege).
* This code is not tested in every AWS Region. For more information, see
[AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services).

## Run the example

### Prerequisites

* You must have an AWS account, and have your default credentials and AWS Region
configured as described in the
[Globally configuring AWS SDKs and tools](https://docs.aws.amazon.com/credref/latest/refdocs/creds-config-files.html)
guide.
* Node.js: Ensure you have Node.js 18+ installed.

```bash
node --version
```

It should output something similar to `v18.x` or higher.

* You must have an Aurora DSQL cluster. For information about creating an Aurora DSQL cluster, see the
[Getting started with Aurora DSQL](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/getting-started.html)
guide.
* If connecting as a non-admin user, ensure the user is linked to an IAM role and is granted access to the `myschema`
schema. See the
[Using database roles with IAM roles](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/using-database-and-iam-roles.html)
guide.

### Run the code

The example demonstrates the following operations:

- Opening a connection to an Aurora DSQL cluster
- Creating a table
- Inserting and querying data

The example is designed to work with both admin and non-admin users:

- When run as an admin user, it uses the `public` schema
- When run as a non-admin user, it uses the `myschema` schema

**Note:** running the example will use actual resources in your AWS account and may incur charges.

Set environment variables for your cluster details:

```bash
# e.g. "admin"
export CLUSTER_USER="<your user>"

# e.g. "foo0bar1baz2quux3quuux4.dsql.us-east-1.on.aws"
export CLUSTER_ENDPOINT="<your endpoint>"

# e.g. "us-east-1"
export REGION="<your region>"
```

Run the example:

```bash
npm install
npm test
```

The example contains comments explaining the code and the operations being performed.

### Connection pooling

Postgres.js uses connection pooling by default. The maximum pool size, and maximum lifespan of connections is configurable
when the client is created using the options `max` and `max_lifetime` respectively. Note that connections are created lazily only
when a database call occurs, not when the client is created. See [Postgres.js documentation here](https://github.com/porsager/postgres?tab=readme-ov-file#the-connection-pool)
for more information. There are no guarantees as to which connection will be used when executing a command, except within a single transaction.
This means users cannot rely on commands like `SET SESSION search_path=schema` to be applied correctly across multiple database
interactions.

## Additional resources

* [Amazon Aurora DSQL Documentation](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/what-is-aurora-dsql.html)
* [Postgres.js Documentation](https://github.com/porsager/postgres)
* [AWS SDK for JavaScript Documentation](https://docs.aws.amazon.com/sdk-for-javascript/)

---

Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
21 changes: 21 additions & 0 deletions javascript/postgres-js/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "dsql_postgres_howto",
"version": "1.0.0",
"description": "How To",
"main": "index.js",
"type": "module",
"scripts": {
"test": "node --experimental-vm-modules node_modules/.bin/jest --testPathPattern='test/smoke.test.js' --runInBand --detectOpenHandles --forceExit"
},
"author": "",
"license": "ISC",
"dependencies": {
"@aws-sdk/dsql-signer": "^3.758.0",
"assert": "2.1.0",
"postgres": "^3.4.5",
"uuid": "^11.0.2"
},
"devDependencies": {
"jest": "^29.7.0"
}
}
85 changes: 85 additions & 0 deletions javascript/postgres-js/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { DsqlSigner } from "@aws-sdk/dsql-signer";
import postgres from "postgres"

import assert from "node:assert";

const ADMIN = "admin";
const PUBLIC = "public";
const NON_ADMIN_SCHEMA = "myschema";

async function getConnection(clusterEndpoint, user, region) {

let client = postgres({
host: clusterEndpoint,
user: user,
// We can pass a function to password instead of a value, which will be triggered whenever
// connections are opened.
password: async () => await getPasswordToken(clusterEndpoint, user, region),
database: "postgres",
port: 5432,
idle_timeout: 2,
ssl: true,
// max: 1, // Optionally set maximum connection pool size
})

return client;
}

async function getPasswordToken(clusterEndpoint, user, region) {
const signer = new DsqlSigner({
hostname: clusterEndpoint,
region,
});
if (user === ADMIN) {
return await signer.getDbConnectAdminAuthToken();
}
else {
signer.user = user;
return await signer.getDbConnectAuthToken()
}
}

async function example() {
let client;

const clusterEndpoint = process.env.CLUSTER_ENDPOINT;
assert(clusterEndpoint);
const user = process.env.CLUSTER_USER;
assert(user);
const region = process.env.REGION;
assert(region);

try {

client = await getConnection(clusterEndpoint, user, region)
let schema = user === ADMIN ? PUBLIC : NON_ADMIN_SCHEMA;

// Note that due to connection pooling, we cannot execute 'set search_path=myschema'
// because we cannot assume the same connection will be used.
await client`CREATE TABLE IF NOT EXISTS ${client(schema)}.owner (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(30) NOT NULL,
city VARCHAR(80) NOT NULL,
telephone VARCHAR(20)
)`;

// Insert some data
await client`INSERT INTO ${client(schema)}.owner(name, city, telephone) VALUES('John Doe', 'Anytown', '555-555-0150')`

// Check that data is inserted by reading it back
const result = await client`SELECT id, city FROM ${client(schema)}.owner where name='John Doe'`;
assert.deepEqual(result[0].city, "Anytown")
assert.notEqual(result[0].id, null)

// Delete data we just inserted
await client`DELETE FROM ${client(schema)}.owner where name='John Doe'`

} catch (error) {
console.error(error);
throw error;
} finally {
await client?.end();
}
}

export { example }
5 changes: 5 additions & 0 deletions javascript/postgres-js/test/smoke.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { example } from '../src/index.js';

test('Smoke test', async () => {
await example();
}, 20000);
Loading