DSQL

Serverless Postgres on Aurora DSQL. IAM auth over TLS, schemas and roles declared in code, and optional multi-region clusters.

Database · anvil:aws:DSQL

Infrastructure

import { App } from '@anvil-cloud/sdk';
import * as anvil from '@anvil-cloud/sdk';

export default new App({
  run(ctx) {
    const db = new anvil.aws.DSQL('db', {
      schemas: [{
        name: 'app',
        tables: [{
          name: 'users',
          columns: [
            { name: 'id', type: 'uuid' },
            { name: 'email', type: 'text', notNull: true },
          ],
          primaryKey: ['id'],
        }],
        roles: [{ name: 'app_role', grants: ['SELECT', 'INSERT'] }],
      }],
    });

    const fn = new anvil.aws.Lambda('processor', {
      runtime: 'nodejs24.x',
      handler: 'functions/processor/index.handler',
      environment: {
        DSQL_ENDPOINT: db.endpoint,
      },
    });

    // Maps the Lambda's IAM role to the app_role Postgres role (dsql:DbConnect +
    // an AWS IAM GRANT mapping — see the architecture page for what this does).
    db.grantConnect(fn, { schemas: [{ schema: 'app', roleName: 'app_role' }] });

    ctx.export('endpoint', db.endpoint);
  },
});
import anvil_cloud as anvil

def infra(ctx: anvil.Context):
    db = anvil.aws.DSQL("db",
        schemas=[{
            "name": "app",
            "tables": [{
                "name": "users",
                "columns": [
                    {"name": "id", "type": "uuid"},
                    {"name": "email", "type": "text", "not_null": True},
                ],
                "primary_key": ["id"],
            }],
            "roles": [{"name": "app_role", "grants": ["SELECT", "INSERT"]}],
        }],
    )

    fn = anvil.aws.Lambda("processor",
        runtime="nodejs24.x",
        handler="functions/processor/index.handler",
        environment={"DSQL_ENDPOINT": db.endpoint},
    )

    # Maps the Lambda's IAM role to the app_role Postgres role (dsql:DbConnect +
    # an AWS IAM GRANT mapping — see the architecture page for what this does).
    db.grant_connect(fn, schemas=[{"schema": "app", "role_name": "app_role"}])

    ctx.export("endpoint", db.endpoint)

anvil.App(run=infra)
package main

import (
    "github.com/anvil-cloud/anvil/sdk/go/anvil"
    anvilaws "github.com/anvil-cloud/anvil/sdk/go/anvil/aws"
    "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
    anvil.Run(anvil.AppConfig{
        Run: func(ctx *anvil.Context) error {
            db, err := anvilaws.NewDSQL(ctx.PulumiCtx(), "db", &anvilaws.DSQLArgs{
                Schemas: anvilaws.DSQLSchemaArray{
                    anvilaws.DSQLSchemaArgs{
                        Name: pulumi.String("app"),
                        Tables: anvilaws.DSQLTableArray{
                            anvilaws.DSQLTableArgs{
                                Name: pulumi.String("users"),
                                Columns: anvilaws.DSQLColumnArray{
                                    anvilaws.DSQLColumnArgs{Name: pulumi.String("id"), Type: anvilaws.DSQLColumnType("uuid")},
                                    anvilaws.DSQLColumnArgs{Name: pulumi.String("email"), Type: anvilaws.DSQLColumnType("text"), NotNull: pulumi.Bool(true)},
                                },
                                PrimaryKey: pulumi.StringArray{pulumi.String("id")},
                            },
                        },
                        Roles: anvilaws.DSQLRoleArray{
                            anvilaws.DSQLRoleArgs{
                                Name:   pulumi.String("app_role"),
                                Grants: anvilaws.DSQLGrantArray{anvilaws.DSQLGrant("SELECT"), anvilaws.DSQLGrant("INSERT")},
                            },
                        },
                    },
                },
            })
            if err != nil {
                return err
            }

            fn, err := anvilaws.NewLambda(ctx.PulumiCtx(), "processor", &anvilaws.LambdaArgs{
                Runtime: anvilaws.LambdaRuntime_Nodejs24_x,
                Handler: pulumi.String("functions/processor/index.handler"),
                Environment: pulumi.StringMap{"DSQL_ENDPOINT": db.Endpoint},
            })
            if err != nil {
                return err
            }

            // No grantConnect convenience in Go yet — construct DSQLConnect directly.
            if _, err := anvilaws.NewDSQLConnect(ctx.PulumiCtx(), "db-processor-connect", &anvilaws.DSQLConnectArgs{
                ClusterArns:    db.ClusterArns,
                TargetRoleArn:  fn.RoleArn,
                TargetName:     pulumi.String("processor"),
                DbRoles:        pulumi.StringArray{pulumi.String("app_role")},
                RolesTableName: db.RolesTableName,
            }); err != nil {
                return err
            }

            ctx.Export("endpoint", db.Endpoint)
            return nil
        },
    })
}

Runtime code

Works from any compute whose IAM role was granted connect access: Lambda, ECS, EC2, or your machine with that role's credentials. It generates a dsql:DbConnect token (not the admin one) and logs in as app_role. Set DSQL_ENDPOINT and AWS_REGION in its environment.

// db.ts
import { DsqlSigner } from '@aws-sdk/dsql-signer';
import { Client } from 'pg';

const endpoint = process.env.DSQL_ENDPOINT!;
const region = process.env.AWS_REGION!;

// Tokens are short-lived and only used to open the connection, so make one per connect.
export async function connect(): Promise<Client> {
  const token = await new DsqlSigner({ hostname: endpoint, region }).getDbConnectAuthToken();

  const client = new Client({
    host: endpoint,
    port: 5432,
    user: 'app_role',
    password: token,
    database: 'postgres',
    ssl: { rejectUnauthorized: true },
  });
  await client.connect();
  return client;
}

// Anywhere in your app:
const client = await connect();
await client.query(
  'INSERT INTO app.users (id, email) VALUES ($1, $2) ON CONFLICT (id) DO NOTHING',
  [crypto.randomUUID(), '[email protected]'],
);
const { rows } = await client.query('SELECT id, email FROM app.users');
await client.end();
# db.py
import os
import uuid
import boto3
import psycopg2

ENDPOINT = os.environ["DSQL_ENDPOINT"]
REGION = os.environ["AWS_REGION"]

# Tokens are short-lived and only used to open the connection, so make one per connect.
def connect():
    token = boto3.client("dsql", region_name=REGION).generate_db_connect_auth_token(
        Hostname=ENDPOINT, Region=REGION,
    )
    return psycopg2.connect(
        host=ENDPOINT, port=5432, user="app_role",
        password=token, dbname="postgres", sslmode="require",
    )

# Anywhere in your app:
conn = connect()
with conn, conn.cursor() as cur:
    cur.execute(
        "INSERT INTO app.users (id, email) VALUES (%s, %s) ON CONFLICT (id) DO NOTHING",
        (str(uuid.uuid4()), "[email protected]"),
    )
    cur.execute("SELECT id, email FROM app.users")
    rows = cur.fetchall()
conn.close()
// db.go
package db

import (
    "context"
    "fmt"
    "os"

    "github.com/aws/aws-sdk-go-v2/config"
    dsqlauth "github.com/aws/aws-sdk-go-v2/feature/dsql/auth"
    "github.com/jackc/pgx/v5"
)

var (
    endpoint = os.Getenv("DSQL_ENDPOINT")
    region   = os.Getenv("AWS_REGION")
)

// Connect opens a connection as app_role. Tokens are short-lived and only used
// to open the connection, so make one per connect.
func Connect(ctx context.Context) (*pgx.Conn, error) {
    cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region))
    if err != nil {
        return nil, err
    }

    // GenerateDbConnectAuthToken, not the Admin variant.
    token, err := dsqlauth.GenerateDbConnectAuthToken(ctx, endpoint, region, cfg.Credentials)
    if err != nil {
        return nil, err
    }

    connConfig, err := pgx.ParseConfig(fmt.Sprintf(
        "host=%s port=5432 user=app_role dbname=postgres sslmode=verify-full", endpoint,
    ))
    if err != nil {
        return nil, err
    }
    connConfig.Password = token

    return pgx.ConnectConfig(ctx, connConfig)
}

// Anywhere in your app:
//
//     conn, err := db.Connect(ctx)
//     if err != nil { ... }
//     defer conn.Close(ctx)
//     rows, err := conn.Query(ctx, "SELECT id, email FROM app.users")

← Back to DSQL docs