概要
この記事では、AWSのData APIを通じてDrizzle ORMでMySQLデータベースに接続し、サーバレス環境でデータ操作を行う方法を解説します。Drizzle ORMは型安全なORMで、AWSのData APIを活用することでサーバレス環境からMySQLデータベースへの接続が可能となり、効率的で安全なデータ管理が実現します。Data APIはクライアントライブラリなしでデータベース操作が行えるため、サーバレス環境でのデータベース利用に最適です。
AWSのData APIとDrizzle ORMの概要
AWS Data APIとは
AWS Data APIは、Amazon RDS上のMySQLやAurora ServerlessにアクセスできるAPIで、SQLクエリをHTTPリクエスト経由で実行する機能を提供します。以下の利点があります:
- サーバレス対応:クライアントライブラリ不要で、AWS Lambdaなどのサーバレス環境から直接データベースにアクセスできる。
- IAM認証でのセキュアなアクセス:安全な認証を提供し、アクセス制御が簡単。
- 高いスケーラビリティ:オンデマンドでスケールするため、大規模なデータ操作にも対応。
Drizzle ORMとは
Drizzle ORMは、JavaScriptとTypeScriptで使用できる軽量なORMで、型安全なデータベース操作が可能です。Drizzle ORMはSQLに近い記述でクエリを操作でき、型定義が明確なためエラーの少ないコードが書けます。Data APIと組み合わせることで、AWSのサーバレス環境でも簡単かつ効率的にデータ操作が可能です。
AWS Data APIとDrizzle ORMでMySQLに接続する手順
AWS RDSでのMySQLインスタンス作成とData APIの有効化
まず、AWS RDSでMySQLデータベースインスタンスを作成し、Data APIを有効にします。
- AWS Management Consoleにログインし、RDSサービスで「データベースを作成」します。
- データベースエンジンとしてMySQLを選択。
- 「サーバレス」設定でインスタンスを作成し、設定完了後にData APIを有効化します。
- AWSによって生成されたData APIのエンドポイントURLやARN(Amazon Resource Name)を控えておきます。 これにより、AWSのData API経由で、HTTPリクエストを使ってサーバレス環境からMySQLに接続できるようになります。
必要なパッケージのインストール
Drizzle ORMとAWSのData APIに接続するために必要なパッケージをインストールします。@aws-sdk/client-rds-dataはData APIへのアクセスに使います。
npm install drizzle-orm @aws-sdk/client-rds-data
- drizzle-orm:ORM本体で、型安全なクエリを提供します。
- @aws-sdk/client-rds-data:AWS Data APIに接続するためのSDKです。
Drizzle ORMとAWS Data APIの接続設定
Drizzle ORMを利用してAWS Data API経由でMySQLに接続するため、クライアント設定を行います。以下の設定例を参考にdatabase.tsファイルを作成します。
// database.ts
import { drizzle } from 'drizzle-orm/aws-data-api/pg'; // PostgreSQLの場合
// または MySQL用のドライバを使用
import { RDSDataClient } from '@aws-sdk/client-rds-data';
// AWS Data API クライアントの設定
const rdsClient = new RDSDataClient({
region: process.env.AWS_REGION || 'us-east-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
});
// Drizzle ORM の初期化
export const db = drizzle(rdsClient, {
database: process.env.DATABASE_NAME || 'mydb',
resourceArn: process.env.DB_RESOURCE_ARN!,
secretArn: process.env.DB_SECRET_ARN!,
});
// 環境変数の例(.env ファイル)
// AWS_REGION=us-east-1
// AWS_ACCESS_KEY_ID=your-access-key
// AWS_SECRET_ACCESS_KEY=your-secret-key
// DATABASE_NAME=employees_db
// DB_RESOURCE_ARN=arn:aws:rds:us-east-1:123456789012:cluster:my-cluster
// DB_SECRET_ARN=arn:aws:secretsmanager:us-east-1:123456789012:secret:rds-db-credentials-AbCdEf
接続情報の詳細:
- region: MySQLデータベースのAWSリージョン(例:
us-east-1,ap-northeast-1) - database: MySQLのデータベース名
- resourceArn: RDSクラスターまたはインスタンスのARN(AWS RDSコンソールから取得)
- secretArn: データベース認証情報が保存されているAWS Secrets ManagerのARN
これにより、Drizzle ORMを介してAWS Data APIでMySQLにアクセスできるようになります。
テーブルのスキーマ定義
Drizzle ORMを用いてテーブルのスキーマを定義します。例えば、従業員情報を格納するemployeesテーブルの例を以下に示します。
// schema.ts
import { mysqlTable, serial, varchar, int, timestamp } from 'drizzle-orm/mysql-core';
// employees テーブルのスキーマ
export const employees = mysqlTable('employees', {
id: serial('id').primaryKey(),
name: varchar('name', { length: 100 }).notNull(),
email: varchar('email', { length: 255 }).notNull().unique(),
role: varchar('role', { length: 50 }).notNull(),
salary: int('salary'),
department: varchar('department', { length: 50 }),
hire_date: timestamp('hire_date').defaultNow(),
created_at: timestamp('created_at').defaultNow().notNull(),
updated_at: timestamp('updated_at').defaultNow().onUpdateNow()
});
// 型定義の自動生成(TypeScript)
export type Employee = typeof employees.$inferSelect;
export type NewEmployee = typeof employees.$inferInsert;
このスキーマ定義では、以下の機能を実装しています:
serial('id'): 自動インクリメントの主キーvarchar(): 文字列型(長さ指定)int(): 整数型(給与)timestamp(): 日時型(自動更新機能付き)- 型推論による型安全性(
$inferSelect,$inferInsert)
データ操作 - 挿入、取得、更新、削除
Drizzle ORMを利用して、AWS Data API経由でMySQLに対するデータ操作を行います。以下に基本的なCRUD操作のサンプルコードを示します。
データの挿入
import { db } from './database';
import { employees, type NewEmployee } from './schema';
// 単一レコードの挿入
async function insertEmployee(data: NewEmployee) {
const result = await db.insert(employees).values(data);
return result;
}
// 使用例
await insertEmployee({
name: '田中太郎',
email: 'tanaka@example.com',
role: 'Engineer',
salary: 5000000,
department: 'Development'
});
// 複数レコードの一括挿入
async function insertMultipleEmployees(employeeList: NewEmployee[]) {
await db.insert(employees).values(employeeList);
}
await insertMultipleEmployees([
{ name: '佐藤花子', email: 'sato@example.com', role: 'Designer', salary: 4500000 },
{ name: '鈴木一郎', email: 'suzuki@example.com', role: 'Manager', salary: 6000000 }
]);
データの取得
import { eq, and, gte, like } from 'drizzle-orm';
// 全件取得
async function getAllEmployees() {
const result = await db.select().from(employees);
return result;
}
// 条件付き取得
async function getEmployeeById(id: number) {
const result = await db
.select()
.from(employees)
.where(eq(employees.id, id))
.limit(1);
return result[0];
}
// 複雑な条件での検索
async function searchEmployees(department: string, minSalary: number) {
return await db
.select()
.from(employees)
.where(
and(
eq(employees.department, department),
gte(employees.salary, minSalary)
)
)
.orderBy(employees.salary);
}
// 部分一致検索
async function findEmployeesByName(namePart: string) {
return await db
.select()
.from(employees)
.where(like(employees.name, `%${namePart}%`));
}
データの更新
// 単一レコードの更新
async function updateEmployeeRole(id: number, newRole: string) {
await db
.update(employees)
.set({ role: newRole })
.where(eq(employees.id, id));
}
// 複数フィールドの更新
async function updateEmployee(id: number, data: Partial<NewEmployee>) {
await db
.update(employees)
.set(data)
.where(eq(employees.id, id));
}
// 使用例
await updateEmployee(1, {
role: 'Senior Engineer',
salary: 5500000,
department: 'Architecture'
});
// 条件付き一括更新
async function giveRaiseByDepartment(department: string, raiseAmount: number) {
await db
.update(employees)
.set({ salary: sql`${employees.salary} + ${raiseAmount}` })
.where(eq(employees.department, department));
}
データの削除
// 単一レコードの削除
async function deleteEmployee(id: number) {
await db
.delete(employees)
.where(eq(employees.id, id));
}
// 条件付き削除
async function deleteInactiveEmployees(beforeDate: Date) {
await db
.delete(employees)
.where(and(
eq(employees.role, 'Inactive'),
gte(employees.updated_at, beforeDate)
));
}
AWS LambdaやAPI Gatewayとの統合
AWSのData APIはサーバレス環境での利用を前提としているため、Lambda関数でこれらのデータ操作を実行することが推奨されます。以下は、Lambda関数を用いたRESTful APIの実装例です。
// lambda-handler.ts
import { APIGatewayProxyHandler, APIGatewayProxyEvent } from 'aws-lambda';
import { db } from './database';
import { employees, type NewEmployee } from './schema';
import { eq } from 'drizzle-orm';
// GET /employees - 全従業員取得
export const listEmployees: APIGatewayProxyHandler = async () => {
try {
const employeesList = await db.select().from(employees);
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
success: true,
data: employeesList,
count: employeesList.length
}),
};
} catch (error) {
console.error('Error fetching employees:', error);
return {
statusCode: 500,
body: JSON.stringify({
success: false,
error: 'Failed to fetch employees'
}),
};
}
};
// GET /employees/{id} - 特定従業員取得
export const getEmployee: APIGatewayProxyHandler = async (event) => {
try {
const id = parseInt(event.pathParameters?.id || '0');
if (!id) {
return {
statusCode: 400,
body: JSON.stringify({ success: false, error: 'Invalid ID' }),
};
}
const employee = await db
.select()
.from(employees)
.where(eq(employees.id, id))
.limit(1);
if (employee.length === 0) {
return {
statusCode: 404,
body: JSON.stringify({ success: false, error: 'Employee not found' }),
};
}
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ success: true, data: employee[0] }),
};
} catch (error) {
console.error('Error fetching employee:', error);
return {
statusCode: 500,
body: JSON.stringify({ success: false, error: 'Internal server error' }),
};
}
};
// POST /employees - 新規従業員作成
export const createEmployee: APIGatewayProxyHandler = async (event) => {
try {
if (!event.body) {
return {
statusCode: 400,
body: JSON.stringify({ success: false, error: 'Request body is required' }),
};
}
const data: NewEmployee = JSON.parse(event.body);
// バリデーション
if (!data.name || !data.email || !data.role) {
return {
statusCode: 400,
body: JSON.stringify({
success: false,
error: 'Name, email, and role are required'
}),
};
}
const result = await db.insert(employees).values(data);
return {
statusCode: 201,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
success: true,
message: 'Employee created successfully',
data: result
}),
};
} catch (error) {
console.error('Error creating employee:', error);
return {
statusCode: 500,
body: JSON.stringify({ success: false, error: 'Failed to create employee' }),
};
}
};
// PUT /employees/{id} - 従業員情報更新
export const updateEmployee: APIGatewayProxyHandler = async (event) => {
try {
const id = parseInt(event.pathParameters?.id || '0');
if (!id || !event.body) {
return {
statusCode: 400,
body: JSON.stringify({ success: false, error: 'Invalid request' }),
};
}
const data: Partial<NewEmployee> = JSON.parse(event.body);
await db
.update(employees)
.set(data)
.where(eq(employees.id, id));
return {
statusCode: 200,
body: JSON.stringify({
success: true,
message: 'Employee updated successfully'
}),
};
} catch (error) {
console.error('Error updating employee:', error);
return {
statusCode: 500,
body: JSON.stringify({ success: false, error: 'Failed to update employee' }),
};
}
};
// DELETE /employees/{id} - 従業員削除
export const deleteEmployee: APIGatewayProxyHandler = async (event) => {
try {
const id = parseInt(event.pathParameters?.id || '0');
if (!id) {
return {
statusCode: 400,
body: JSON.stringify({ success: false, error: 'Invalid ID' }),
};
}
await db
.delete(employees)
.where(eq(employees.id, id));
return {
statusCode: 200,
body: JSON.stringify({
success: true,
message: 'Employee deleted successfully'
}),
};
} catch (error) {
console.error('Error deleting employee:', error);
return {
statusCode: 500,
body: JSON.stringify({ success: false, error: 'Failed to delete employee' }),
};
}
};
このコードをAWS Lambdaにデプロイし、API Gatewayを設定することで、HTTPリクエストを受けてMySQLデータベースにアクセスできるRESTful APIエンドポイントが完成します。
IAMポリシーの設定
AWS Data APIを使用するには、Lambda関数に適切なIAMロールとポリシーを設定する必要があります。以下は、必要な権限を含むIAMポリシーの例です。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"rds-data:ExecuteStatement",
"rds-data:BatchExecuteStatement",
"rds-data:BeginTransaction",
"rds-data:CommitTransaction",
"rds-data:RollbackTransaction"
],
"Resource": "arn:aws:rds:us-east-1:123456789012:cluster:my-cluster"
},
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:rds-db-credentials-*"
}
]
}
このポリシーをLambda実行ロールにアタッチすることで、以下の権限が付与されます:
- rds-data:ExecuteStatement: SQLクエリの実行
- rds-data:BatchExecuteStatement: バッチクエリの実行
- rds-data:BeginTransaction: トランザクションの開始
- rds-data:CommitTransaction: トランザクションのコミット
- rds-data:RollbackTransaction: トランザクションのロールバック
- secretsmanager:GetSecretValue: データベース認証情報の取得
serverless.ymlでのデプロイ設定例
Serverless Frameworkを使用したデプロイ設定の例です。
service: employee-api
provider:
name: aws
runtime: nodejs18.x
region: us-east-1
environment:
DATABASE_NAME: ${env:DATABASE_NAME}
DB_RESOURCE_ARN: ${env:DB_RESOURCE_ARN}
DB_SECRET_ARN: ${env:DB_SECRET_ARN}
iam:
role:
statements:
- Effect: Allow
Action:
- rds-data:ExecuteStatement
- rds-data:BatchExecuteStatement
- rds-data:BeginTransaction
- rds-data:CommitTransaction
- rds-data:RollbackTransaction
Resource: ${env:DB_RESOURCE_ARN}
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource: ${env:DB_SECRET_ARN}
functions:
listEmployees:
handler: src/lambda-handler.listEmployees
events:
- httpApi:
path: /employees
method: get
getEmployee:
handler: src/lambda-handler.getEmployee
events:
- httpApi:
path: /employees/{id}
method: get
createEmployee:
handler: src/lambda-handler.createEmployee
events:
- httpApi:
path: /employees
method: post
updateEmployee:
handler: src/lambda-handler.updateEmployee
events:
- httpApi:
path: /employees/{id}
method: put
deleteEmployee:
handler: src/lambda-handler.deleteEmployee
events:
- httpApi:
path: /employees/{id}
method: delete
エラー対処と注意点
1. Data API接続エラー
問題: ResourceNotFoundException や接続タイムアウトエラーが発生する
対処法:
- リージョン、データベース名、ARNが正しく設定されているか確認
- 環境変数が正しく設定されているか確認
- RDSクラスターがData APIを有効化しているか確認
// デバッグ用のログ出力
console.log('DB Resource ARN:', process.env.DB_RESOURCE_ARN);
console.log('DB Secret ARN:', process.env.DB_SECRET_ARN);
console.log('Database Name:', process.env.DATABASE_NAME);
2. IAMアクセス権限エラー
問題: AccessDeniedException が発生する
対処法:
- Lambda実行ロールに以下の権限が付与されているか確認:
rds-data:ExecuteStatementrds-data:BatchExecuteStatementsecretsmanager:GetSecretValue
- ポリシーのリソースARNが正しいか確認
- Secrets Managerへのアクセス権限が付与されているか確認
3. トランザクションのタイムアウト
問題: 長時間実行されるトランザクションがタイムアウトする
対処法:
// トランザクションタイムアウトの設定
import { db } from './database';
await db.transaction(async (tx) => {
// 複数の操作を実行
await tx.insert(employees).values({ /* ... */ });
await tx.update(employees).set({ /* ... */ });
}, {
timeout: 30000 // 30秒(デフォルトは10秒)
});
4. 型エラーとバリデーション
問題: スキーマに一致しないデータを挿入しようとしてエラーが発生
対処法:
import { z } from 'zod';
// Zodスキーマでバリデーション
const employeeSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email().max(255),
role: z.string().min(1).max(50),
salary: z.number().int().positive().optional(),
department: z.string().max(50).optional()
});
export const createEmployee: APIGatewayProxyHandler = async (event) => {
try {
const data = JSON.parse(event.body || '{}');
// バリデーション
const validated = employeeSchema.parse(data);
await db.insert(employees).values(validated);
return {
statusCode: 201,
body: JSON.stringify({ success: true })
};
} catch (error) {
if (error instanceof z.ZodError) {
return {
statusCode: 400,
body: JSON.stringify({
success: false,
error: 'Validation failed',
details: error.errors
})
};
}
// その他のエラー処理
}
};
5. コールドスタートとコネクション管理
注意点: Data APIはHTTPベースのため、従来のコネクションプールは不要ですが、Lambda関数のコールドスタートを考慮してください。
ベストプラクティス:
- データベース接続は関数の外部で初期化する
- Lambda関数のメモリとタイムアウトを適切に設定する
- 頻繁にアクセスされるエンドポイントにはProvisioned Concurrencyを検討する
まとめ
Drizzle ORMとAWSのData APIを組み合わせることで、型安全かつ効率的なサーバレス環境でのMySQLデータベース操作が可能になります。AWSのData APIはHTTPリクエストでデータベース操作が行えるため、LambdaやAPI Gatewayとの連携が容易で、スケーラブルでセキュアなデータ管理が実現します。Drizzle ORMを利用することで、シンプルで型安全なデータ操作を提供し、モダンなサーバレスアーキテクチャに最適な構成が可能です。
主な利点:
- 型安全性: TypeScriptとの完全な統合により、コンパイル時に型エラーを検出
- サーバレス対応: コネクションプール不要で、Lambda関数での利用に最適
- セキュリティ: IAM認証とSecrets Managerによる安全な認証情報管理
- スケーラビリティ: AWS Data APIの自動スケーリングにより、負荷に応じた処理が可能
- 開発効率: 直感的なAPIと充実した型推論により、開発速度が向上