Serverless ExpressからDSQLに接続する
はじめに
Amazon Aurora DSQL(以下、DSQL)は、サーバレスの分散データベースです。PostgreSQLと互換性があり、アプリケーションからは使い慣れたドライバーやフレームワーク、ORMを使用できます。この記事では、Serverless ExpressからDSQLクラスターに接続し、データの取得や登録ができることを確認します。
前提
- Node.jsがインストールされていること
- AWS CDK v2がインストールされていること
注意点
Serverless ExpressからDSQLに接続することに焦点を当てているため、アプリケーションコードは動かすための最低限の実装となっています。実運用で使用するアプリケーションを作成する際は、バリデーションチェックや型安全性、エラーハンドリング等の適切な実装が必要です。
ライブラリのインストール
プロジェクトフォルダを用意し、backendとinfraディレクトリを作成します。
dsql-lambda/
├── backend/
└── infra/
backendディレクトリに移動し、Serverless Expressをインストールします。
npm install @codegenie/serverless-express
その他必要なライブラリをインストールします。
npm install @aws-sdk/dsql-signer express pg
npm install -D @types/express @types/node @types/pg
backendディレクトリ直下のpackage.jsonは以下のようになります。
{
"dependencies": {
"@aws-sdk/dsql-signer": "^3.883.0",
"@codegenie/serverless-express": "^4.17.0",
"express": "^5.1.0",
"pg": "^8.16.3"
},
"devDependencies": {
"@types/express": "^5.0.3",
"@types/node": "^24.3.1",
"@types/pg": "^8.15.5"
}
}
backendディレクトリ直下にtsconfig.jsonを作成します。
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"types": ["node"]
},
"include": [
"**/*.ts"
],
"exclude": [
"node_modules",
"dist"
]
}
以上でbackendディレクトリへのインストールは完了です。
続いてinfraディレクトリに移動し、CDKプロジェクトのセットアップをします。
cdk init app --language typescript
インストール中に自動的にGitリポジトリの設定が行われますが、不要な場合はinfraディレクトリに作成された.gitディレクトリを削除します。
データベースクライアントの作成
pgライブラリを使用してDSQLクラスターに接続するためのモジュールを作成します。
backend/shared/db/client.tsファイルを作成します。
import { DsqlSigner } from "@aws-sdk/dsql-signer";
import { Pool, PoolClient, QueryResult } from "pg";
const region = process.env.REGION || "";
const clusterEndpoint = process.env.DSQL_CLUSTER_ENDPOINT || "";
if (!region) {
throw new Error("Region is not set.");
}
if (!clusterEndpoint) {
throw new Error("DSQL endpoint is not set.");
}
/**
* トークン取得
*/
const getToken = async (): Promise<string> => {
const signer = new DsqlSigner({ hostname: clusterEndpoint, region });
return signer.getDbConnectAuthToken();
};
const dsqlPool = new Pool({
host: clusterEndpoint,
port: 5432,
database: "postgres",
user: "api_executor",
password: getToken,
ssl: { rejectUnauthorized: true },
maxLifetimeSeconds: 60 * 50,
idleTimeoutMillis: 1000 * 60 * 50,
});
/**
* クエリ実行
*/
export const query = (
text: string,
params?: unknown[],
): Promise<QueryResult> => {
return dsqlPool.query(text, params);
};
/**
* クライアント取得(トランザクション用)
*/
export const getClient = async (): Promise<PoolClient> => {
return dsqlPool.connect();
};
export default dsqlPool;
以下の記事を参考に、最大接続時間超過エラーが発生しないよう、maxLifetimeSecondsで50分までしか接続が保持されないように設定しています。
DSQLの最大接続時間超過エラーが発生しないLambdaの実装について検証してみた | DevelopersIO
トランザクション処理の場合はトランザクション開始~クエリ実行~コミットが同一セッションである必要があるため、Poolのqueryではなく、取得した専用クライアントを使用するようにします。
DSQLはIAMベースの認証を使用します。接続時には以下の流れで認証が行われます。
- DsqlSignerを使用して一時的な認証トークンを取得
- このトークンをパスワードとして使用し、PostgreSQL接続を確立
- 接続プールが自動的にトークンを更新
トークンには有効期限があるため、pgのPoolのpasswordに関数を渡すことで、接続時に毎回新しいトークンを取得します。
アプリケーションの作成
この記事では、
- ユーザIDを元にユーザ名を取得する
- ユーザを登録する
という2つのAPIを作成します。
backend/service/user.tsを作成します。
import dsqlPool, { getClient } from "../shared/db/client";
export const getUserName = async (userId: number): Promise<string | null> => {
const result = await dsqlPool.query(`SELECT name from users where user_id = $1`, [userId]);
const rows = result.rows;
if (rows.length === 0) {
return null
}
return rows[0].name;
};
export const registerUser = async (userId: number, name: string): Promise<void> => {
const client = await getClient();
try{
await client.query('BEGIN')
await client.query(`INSERT INTO users(user_id, name) VALUES ($1, $2)`, [userId, name]);
await client.query('COMMIT')
} catch (error) {
await client.query('ROLLBACK');
} finally {
client.release()
}
};
ユーザ名を取得する処理は、Poolのqueryを使用します。一方で、ユーザを登録する処理は専用のクライアントを使用します。
続いてこの処理を呼び出すハンドラー、ルーターを作成していきます。
backend/handler/get-user.ts
import { getUserName } from '../service/user';
type GetUserHandlerProps = {
userId: number
}
export const handler = async (props: GetUserHandlerProps) => {
const name = await getUserName(props.userId);
return {
statusCode: 200,
body: {
userId: props.userId,
name: name ?? "Not Found",
timestamp: new Date().toISOString()
}
}
}
backend/handler/post-user.ts
import { registerUser } from '../service/user';
type PostUserHandlerProps = {
userId: number
name: string
}
export const handler = async (props: PostUserHandlerProps) => {
await registerUser(props.userId, props.name);
return {
statusCode: 201,
body: {}
}
}
backend/app.ts
import serverlessExpress from '@codegenie/serverless-express';
import express, { Request, Response } from 'express';
import { handler as getUserHandler } from './handler/get-user';
import { handler as postUserHandler } from './handler/post-user';
const app = express();
app.use(express.json());
app.get('/users/:userId', async (req: Request, res: Response): Promise<void> => {
const userId = Number(req.params.userId)
if (!Number.isNaN(userId)) {
const result = await getUserHandler({ userId: userId });
res.status(result.statusCode).send(result.body);
} else {
res.status(400).send({
message: "Validation error"
});
}
});
app.post('/users', async (req: Request, res: Response): Promise<void> => {
const userId = Number(req.body.userId)
const name = req.body.name
if(!Number.isNaN(userId)){
const result = await postUserHandler({ userId: Number(userId), name });
res.status(result.statusCode).send(result.body);
} else {
res.status(400).send({
message: "Validation error"
});
}
});
export const handler = serverlessExpress({ app });
AWSリソースの作成
CDKプロジェクトのセットアップをすると、デフォルトでinfra/lib/infra-stack.tsが作成されます。
ファイルの内容を以下のように変更し、DSQLクラスター、Lambda関数、API Gatewayを作成します。
import {
aws_lambda,
aws_lambda_nodejs,
aws_apigateway,
aws_iam,
Duration,
CfnOutput,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import * as dsql from "aws-cdk-lib/aws-dsql";
export class InfraStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const dsqlCluster = new dsql.CfnCluster(this, "DsqlCluster");
const apiFunction = new aws_lambda_nodejs.NodejsFunction(
this,
'BackendApiFunction',
{
runtime: aws_lambda.Runtime.NODEJS_22_X,
entry: "../backend/app.ts",
timeout: Duration.seconds(60),
environment: {
REGION: this.region,
DSQL_CLUSTER_ENDPOINT: `${dsqlCluster.attrIdentifier}.dsql.${this.region}.on.aws`
},
}
);
apiFunction.addToRolePolicy(
new aws_iam.PolicyStatement({
effect: aws_iam.Effect.ALLOW,
actions: [
'dsql:DbConnect'
],
resources: [dsqlCluster.attrResourceArn]
})
)
/**
* Create API
*/
const api = new aws_apigateway.LambdaRestApi(this, 'BackendApi', {
handler: apiFunction,
deployOptions: {
stageName: 'v1',
},
});
/**
* Create API Gateway Endpoint
*/
new CfnOutput(this, 'ApiEndpoint', {
value: api.deploymentStage.urlForPath(),
});
}
}
infraディレクトリに移動し、リソースをデプロイします。
cdk deploy
コンテナイメージのビルドが失敗する場合、esbuildをインストールします。
npm install esbuild
DSQLクラスターの設定
リソースのデプロイが完了したら、続いてDSQLクラスターを設定します。
DSQLのページでリソースが作成されていることを確認します。

クラスターIDのリンクをクリックし、「接続」⇒「CloudShellで開く」を選択します。

デフォルトのままで「CloudShellを起動」をクリックします。

CloudShellが起動したら、テーブルを作成します。
CREATE TABLE users (
user_id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
DSQLはIAMロールまたはIAMユーザを使用して認証します。今回は、Lambdaの実行ロールに対してデータベースへのアクセスを許可します。
なお、Lambdaの実行ロールはLambda関数の「設定」タブ⇒「アクセス権限」を選択し、記載されたリンクをクリックした先で確認できます。


まず、データベースロールを作成します。データベースロールの名前は、データベースクライアントでPoolを作成するときに指定したユーザ名です。
CREATE ROLE api_executor WITH LOGIN;
続いてデータベースロールとIAMロールを関連付けます。
AWS IAM GRANT api_executor TO 'Lambda実行ロールのARN';
最後に、データベースロールに対してテーブルの操作権限を与えます。
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO api_executor;
なお、将来作成されるテーブルに対しても操作権限を付与するALTER DEFAULT PRIVILEGESコマンドは、記事執筆時点ではサポートされていないのでご注意ください。
参考
データベースロールと IAM 認証の使用 - Amazon Aurora DSQL
なお、上記設定は、一般ユーザ向けのアクセス権限となっています。管理者向けの権限でアクセスしたい場合、接続ユーザ名をadmin、Lambda関数に付与するポリシーをdsql:DbConnectAdminに設定し、接続トークンを取得するためにDsqlSignerのgetDbConnectAdminAuthToken関数を使用します。
動作確認
実際にAPIを実行して動作確認します。
APIのエンドポイントはCloudFormationの「出力」タブで確認できます。

ユーザを登録するため、以下のcurlコマンドを実行します。
curl -X POST https://<APIエンドポイント>/users \
-H "Content-Type: application/json" \
-d '{"userId": 1, "name": "Ken"}'
登録したユーザを取得するため、以下のcurlコマンドを実行します。
curl https://<APIエンドポイント>/users/1
以下のように結果が取得でき、Serverless ExpressからDSQLにアクセスできていることが確認できました。
{"userId":1,"name":"Ken","timestamp":"2025-10-30T14:25:49.382Z"}
おわりに
この記事では、Serverless ExpressからAmazon Aurora DSQLへの接続方法を確認しました。
主なポイントは以下の通りです。
- DSQLはIAMベースの認証であり、一時的なトークンを取得して接続する
- IAMロールまたはIAMユーザとデータベースロールを関連付ける
ALTER DEFAULT PRIVILEGESコマンドがサポートされていないため、テーブル追加ごとに権限付与が必要
DSQLはインフラ管理不要を謳っているだけあり、設定項目も非常に少なく、サクッと動作確認ができました。
この記事がどなたかの参考になれば幸いです。
参考







