Skip to content

Commit 583e3d6

Browse files
mitchell-elholmMitchell Elholm
andauthored
Add Postgres.js sample with token refresh and non-admin (#127)
* Add PostgresJS sample with token refresh and non-admin * Fix files to PostgresJS --------- Co-authored-by: Mitchell Elholm <elholmit@amazon.com>
1 parent aa08d7c commit 583e3d6

5 files changed

Lines changed: 227 additions & 0 deletions

File tree

javascript/postgres-js/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/node_modules
2+
/build
3+
/dist
4+
/coverage

javascript/postgres-js/README.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Postgres.js with Aurora DSQL
2+
3+
## Overview
4+
5+
This code example demonstrates how to use `Postgres.js` with Amazon Aurora DSQL.
6+
The example shows you how to connect to an Aurora DSQL cluster and perform basic database operations.
7+
8+
Aurora DSQL is a distributed SQL database service that provides high availability and scalability for
9+
your PostgreSQL-compatible applications. `Postgres.js` is a lightweight PostgreSQL client for Node.js that allows
10+
you to interact with PostgreSQL databases using JavaScript code.
11+
12+
## About the code example
13+
14+
The example demonstrates a flexible connection approach that works for both admin and non-admin users:
15+
16+
* When connecting as an **admin user**, the example uses the `public` schema and generates an admin authentication
17+
token.
18+
* When connecting as a **non-admin user**, the example uses a custom `myschema` schema and generates a standard
19+
authentication token.
20+
21+
The code automatically detects the user type and adjusts its behavior accordingly.
22+
23+
## ⚠️ Important
24+
25+
* Running this code might result in charges to your AWS account.
26+
* We recommend that you grant your code least privilege. At most, grant only the
27+
minimum permissions required to perform the task. For more information, see
28+
[Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege).
29+
* This code is not tested in every AWS Region. For more information, see
30+
[AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services).
31+
32+
## Run the example
33+
34+
### Prerequisites
35+
36+
* You must have an AWS account, and have your default credentials and AWS Region
37+
configured as described in the
38+
[Globally configuring AWS SDKs and tools](https://docs.aws.amazon.com/credref/latest/refdocs/creds-config-files.html)
39+
guide.
40+
* Node.js: Ensure you have Node.js 18+ installed.
41+
42+
```bash
43+
node --version
44+
```
45+
46+
It should output something similar to `v18.x` or higher.
47+
48+
* You must have an Aurora DSQL cluster. For information about creating an Aurora DSQL cluster, see the
49+
[Getting started with Aurora DSQL](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/getting-started.html)
50+
guide.
51+
* If connecting as a non-admin user, ensure the user is linked to an IAM role and is granted access to the `myschema`
52+
schema. See the
53+
[Using database roles with IAM roles](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/using-database-and-iam-roles.html)
54+
guide.
55+
56+
### Run the code
57+
58+
The example demonstrates the following operations:
59+
60+
- Opening a connection to an Aurora DSQL cluster
61+
- Creating a table
62+
- Inserting and querying data
63+
64+
The example is designed to work with both admin and non-admin users:
65+
66+
- When run as an admin user, it uses the `public` schema
67+
- When run as a non-admin user, it uses the `myschema` schema
68+
69+
**Note:** running the example will use actual resources in your AWS account and may incur charges.
70+
71+
Set environment variables for your cluster details:
72+
73+
```bash
74+
# e.g. "admin"
75+
export CLUSTER_USER="<your user>"
76+
77+
# e.g. "foo0bar1baz2quux3quuux4.dsql.us-east-1.on.aws"
78+
export CLUSTER_ENDPOINT="<your endpoint>"
79+
80+
# e.g. "us-east-1"
81+
export REGION="<your region>"
82+
```
83+
84+
Run the example:
85+
86+
```bash
87+
npm install
88+
npm test
89+
```
90+
91+
The example contains comments explaining the code and the operations being performed.
92+
93+
### Connection pooling
94+
95+
Postgres.js uses connection pooling by default. The maximum pool size, and maximum lifespan of connections is configurable
96+
when the client is created using the options `max` and `max_lifetime` respectively. Note that connections are created lazily only
97+
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)
98+
for more information. There are no guarantees as to which connection will be used when executing a command, except within a single transaction.
99+
This means users cannot rely on commands like `SET SESSION search_path=schema` to be applied correctly across multiple database
100+
interactions.
101+
102+
## Additional resources
103+
104+
* [Amazon Aurora DSQL Documentation](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/what-is-aurora-dsql.html)
105+
* [Postgres.js Documentation](https://github.com/porsager/postgres)
106+
* [AWS SDK for JavaScript Documentation](https://docs.aws.amazon.com/sdk-for-javascript/)
107+
108+
---
109+
110+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
111+
112+
SPDX-License-Identifier: MIT-0
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "dsql_postgres_howto",
3+
"version": "1.0.0",
4+
"description": "How To",
5+
"main": "index.js",
6+
"type": "module",
7+
"scripts": {
8+
"test": "node --experimental-vm-modules node_modules/.bin/jest --testPathPattern='test/smoke.test.js' --runInBand --detectOpenHandles --forceExit"
9+
},
10+
"author": "",
11+
"license": "ISC",
12+
"dependencies": {
13+
"@aws-sdk/dsql-signer": "^3.758.0",
14+
"assert": "2.1.0",
15+
"postgres": "^3.4.5",
16+
"uuid": "^11.0.2"
17+
},
18+
"devDependencies": {
19+
"jest": "^29.7.0"
20+
}
21+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { DsqlSigner } from "@aws-sdk/dsql-signer";
2+
import postgres from "postgres"
3+
4+
import assert from "node:assert";
5+
6+
const ADMIN = "admin";
7+
const PUBLIC = "public";
8+
const NON_ADMIN_SCHEMA = "myschema";
9+
10+
async function getConnection(clusterEndpoint, user, region) {
11+
12+
let client = postgres({
13+
host: clusterEndpoint,
14+
user: user,
15+
// We can pass a function to password instead of a value, which will be triggered whenever
16+
// connections are opened.
17+
password: async () => await getPasswordToken(clusterEndpoint, user, region),
18+
database: "postgres",
19+
port: 5432,
20+
idle_timeout: 2,
21+
ssl: true,
22+
// max: 1, // Optionally set maximum connection pool size
23+
})
24+
25+
return client;
26+
}
27+
28+
async function getPasswordToken(clusterEndpoint, user, region) {
29+
const signer = new DsqlSigner({
30+
hostname: clusterEndpoint,
31+
region,
32+
});
33+
if (user === ADMIN) {
34+
return await signer.getDbConnectAdminAuthToken();
35+
}
36+
else {
37+
signer.user = user;
38+
return await signer.getDbConnectAuthToken()
39+
}
40+
}
41+
42+
async function example() {
43+
let client;
44+
45+
const clusterEndpoint = process.env.CLUSTER_ENDPOINT;
46+
assert(clusterEndpoint);
47+
const user = process.env.CLUSTER_USER;
48+
assert(user);
49+
const region = process.env.REGION;
50+
assert(region);
51+
52+
try {
53+
54+
client = await getConnection(clusterEndpoint, user, region)
55+
let schema = user === ADMIN ? PUBLIC : NON_ADMIN_SCHEMA;
56+
57+
// Note that due to connection pooling, we cannot execute 'set search_path=myschema'
58+
// because we cannot assume the same connection will be used.
59+
await client`CREATE TABLE IF NOT EXISTS ${client(schema)}.owner (
60+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
61+
name VARCHAR(30) NOT NULL,
62+
city VARCHAR(80) NOT NULL,
63+
telephone VARCHAR(20)
64+
)`;
65+
66+
// Insert some data
67+
await client`INSERT INTO ${client(schema)}.owner(name, city, telephone) VALUES('John Doe', 'Anytown', '555-555-0150')`
68+
69+
// Check that data is inserted by reading it back
70+
const result = await client`SELECT id, city FROM ${client(schema)}.owner where name='John Doe'`;
71+
assert.deepEqual(result[0].city, "Anytown")
72+
assert.notEqual(result[0].id, null)
73+
74+
// Delete data we just inserted
75+
await client`DELETE FROM ${client(schema)}.owner where name='John Doe'`
76+
77+
} catch (error) {
78+
console.error(error);
79+
throw error;
80+
} finally {
81+
await client?.end();
82+
}
83+
}
84+
85+
export { example }
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { example } from '../src/index.js';
2+
3+
test('Smoke test', async () => {
4+
await example();
5+
}, 20000);

0 commit comments

Comments
 (0)