AWS Lambda(Node.js)において、middyを使ってAmazon RDSに接続する方法を紹介します。
はじめに
データベースエンジン
この記事ではデータベースエンジンとしてAurora MySQLを使用している場合の例を示しますが、他のデータベースエンジンの場合も基本的な実装は変わりません。
プログラミング言語
この記事ではソースコードをTypeScriptで書いているので型定義をインストールしたり型アノテーションを記述したりしていますが、JavaScriptで書く場合は不要です。
ミドルウェア
middyでRDSに接続するために、@middy/db-managerを使います。
@middy/db-managerでは、データベースクライアントとしてKnex.jsが使われます。
接続情報をAWS Secrets Managerで管理していない場合
接続情報をAWS Secrets Managerで管理していない場合、ミドルウェアとして@middy/db-managerのみを使います。
パッケージをインストール
middyを使うために、@middy/coreパッケージをインストールします。
$ npm i -d @middy/core
middyでRDSに接続するために、@middy/db-managerパッケージをインストールします。
$ npm i -d @middy/db-manager
また、データベースエンジンに応じたパッケージをインストールします。
今回はAurora MySQLなので、mysqlパッケージをインストールします。
$ npm i -d mysql
Lambdaの型定義として、@types/aws-lambdaパッケージをインストールします。
$ npm i -D @types/aws-lambda
ハンドラーを作成
middyを用いて、ハンドラーをつくります。
import lambda from 'aws-lambda'
import Knex from 'knex'
import middy from '@middy/core'
import dbManager from '@middy/db-manager'
type Event = any
interface Context extends lambda.Context {
db: Knex
}
const handler = middy(async (
event: Event,
context: Context
): Promise<void> => {
const users = await context.db.select('*').from('users')
console.log(users)
})
handler.use(dbManager({
config: {
client: 'mysql',
connection: {
host: '127.0.0.1',
port: 3306,
user: 'your_database_user',
password: 'your_database_password',
database: 'myapp_test'
}
}
}))
export { handler }
詳しく解説していきます。
interface Context extends lambda.Context {
db: Knex
}
@middy/db-managerを使うとコンテキストのdb
プロパティにKnexインスタンスが割り当てられるので、db
プロパティを持つContext
を定義しています。
const handler = middy(async (
event: Event,
context: Context
): Promise<void> => {
const users = await context.db.select('*').from('users')
console.log(users)
})
ハンドラーをmiddy
関数でラップすることで、middyfy(middy化)しています。
ハンドラー内では、context.db
からKnex.jsのAPIを用いてデータベース操作が行なえます。
handler.use(dbManager({
config: {
client: 'mysql',
connection: {
host: '127.0.0.1',
port: 3306,
user: 'your_database_user',
password: 'your_database_password',
database: 'myapp_test'
}
}
}))
ハンドラーで、ミドルウェアとしてdbManager
を使っています。
config
には、Knex.jsの設定オブジェクトを渡します。
ここでは例示のために接続情報をべた書きしていますが、実際には環境変数やAWS Secrets Managerで管理すべきでしょう。
接続情報をAWS Secrets Managerで管理している場合
接続情報をAWS Secrets Managerで管理している場合、ミドルウェアとして@middy/db-managerと@middy/secrets-managerを使います。
パッケージをインストール
middyを使うために、@middy/coreパッケージをインストールします。
$ npm i -d @middy/core
middyでRDSに接続するために、@middy/db-managerパッケージをインストールします。
$ npm i -d @middy/db-manager
また、データベースエンジンに応じたパッケージをインストールします。
今回はAurora MySQLなので、mysqlパッケージをインストールします。
$ npm i -d mysql
middyでSecrets Managerに接続するために、@middy/secrets-managerパッケージをインストールします。
$ npm i -d @middy/secrets-manager
Lambdaの型定義として、@types/aws-lambdaパッケージをインストールします。
$ npm i -D @types/aws-lambda
ハンドラーを作成
middyを用いて、ハンドラーをつくります。
import lambda from 'aws-lambda'
import Knex from 'knex'
import middy from '@middy/core'
import dbManager from '@middy/db-manager'
import secretsManager from '@middy/secrets-manager'
type Event = any
interface Context extends lambda.Context {
db: Knex
}
const MIDDY_RDS_SECRET_KEY = 'MIDDY_RDS_SECRET'
const handler = middy(async (
event: Event,
context: Context
): Promise<void> => {
const users = await context.db.select('*').from('users')
console.log(users)
})
handler.use(secretsManager({
secrets: {
[MIDDY_RDS_SECRET_KEY]: 'secret_name'
},
cache: true,
throwOnFailedCall: true
}))
handler.use({
before: (handler, next) => {
interface Secret {
host: string
port: number
username: string
password: string
dbname: string
}
interface Connection {
host: string
port: number
user: string
password: string
database: string
}
interface Context extends lambda.Context {
[MIDDY_RDS_SECRET_KEY]: Secret | Connection
}
const context = handler.context as Context
const secret = context[MIDDY_RDS_SECRET_KEY] as Secret
context[MIDDY_RDS_SECRET_KEY] = {
host: secret.host,
port: secret.port,
user: secret.username,
password: secret.password,
database: secret.dbname
}
return next()
}
})
handler.use(dbManager({
config: {
client: 'mysql'
},
secretsPath: MIDDY_RDS_SECRET_KEY,
removeSecrets: true
}))
export { handler }
詳しく解説していきます。
interface Context extends lambda.Context {
db: Knex
}
@middy/db-managerを使うとコンテキストのdb
プロパティにKnexインスタンスが割り当てられるので、db
プロパティを持つContext
を定義しています。
const handler = middy(async (
event: Event,
context: Context
): Promise<void> => {
const users = await context.db.select('*').from('users')
console.log(users)
})
ハンドラーをmiddy
関数でラップすることで、middyfy(middy化)しています。
ハンドラー内では、context.db
からKnex.jsのAPIを用いてデータベース操作が行なえます。
handler.use(secretsManager({
secrets: {
[MIDDY_RDS_SECRET_KEY]: 'secret_name'
},
cache: true,
throwOnFailedCall: true
}))
ハンドラーで、ミドルウェアとしてsecretsManager
を使っています。
secrets
では、コンテキストのどのプロパティに対してSecrets Managerのどのシークレットを割り当てるかを指定します。
cache
をtrue
にすることで、キャッシュを有効化しています。
throwOnFailedCall
をtrue
にすることで、シークレットの取得に失敗した場合にエラーをスローするようにしています。
handler.use({
before: (handler, next) => {
interface Secret {
host: string
port: number
username: string
password: string
dbname: string
}
interface Connection {
host: string
port: number
user: string
password: string
database: string
}
interface Context extends lambda.Context {
[MIDDY_RDS_SECRET_KEY]: Secret | Connection
}
const context = handler.context as Context
const secret = context[MIDDY_RDS_SECRET_KEY] as Secret
context[MIDDY_RDS_SECRET_KEY] = {
host: secret.host,
port: secret.port,
user: secret.username,
password: secret.password,
database: secret.dbname
}
return next()
}
})
Secrets Managerに保存されているシークレットを、Knex.jsに渡す形式に変換しています。
シークレットの形式は、データベースエンジンによって異なります。1
handler.use(dbManager({
config: {
client: 'mysql'
},
secretsPath: MIDDY_RDS_SECRET_KEY,
removeSecrets: true
}))
ハンドラーで、ミドルウェアとしてdbManager
を使っています。
secretsPath
には、Secrets Managerのシークレットが割り当てられているコンテキストのプロパティを指定します。
removeSecrets
をtrue
にすることで、データベースに接続後にコンテキストからシークレットを削除しています。