Cloudflare WorkersからPostgreSQLに接続すると「write CONNECTION_ENDED」になった話

2024年12月10日 火曜日 09時00分

Cloudflare WorkersからPostgres.jsを使って、PostgreSQLに接続する際に、エラーになりましたので、自分用のメモとして、残しておきます。

各ドキュメントのサンプルコード

Cloudflareのドキュメントは以下の通りです。

Connect to a PostgreSQL database with Cloudflare Workers · Cloudflare Workers docs

import postgres from "postgres";

export default {
  async fetch(request, env, ctx): Promise<Response> {
    const sql = postgres(env.DB_URL);

    // Query the products table
    const result = await sql`SELECT * FROM products;`;

    // Return the result as JSON
    const resp = new Response(JSON.stringify(result), {
      headers: { "Content-Type": "application/json" },
    });

    return resp;
  },
} satisfies ExportedHandler<Env>;

Postgres.jsのGitHubのコードは以下の通りです。

porsager/postgres: Postgres.js - The Fastest full featured PostgreSQL client for Node.js, Deno, Bun and CloudFlare

// db.js
import postgres from 'postgres'

const sql = postgres({ /* options */ }) // will use psql environment variables

export default sql
// users.js
import sql from './db.js'

async function getUsersOver(age) {
  const users = await sql`
    select
      name,
      age
    from users
    where age > ${ age }
  `
  // users = Result [{ name: "Walter", age: 80 }, { name: 'Murray', age: 68 }, ...]
  return users
}

最初に書いたコード

以下のコードをデプロイしましたら、1回目のリクエスト時は成功しますが、数秒後に、改めてリクエストすると、エラーになりました。

// db.service.ts
import postgres from 'postgres';

export const sql = postgres(
	{
		host: 'db.example.com',
		port: 5432,
		database: 'my_database',
		username: 'my_username',
		password: 'my_password',
	});
// getCustomerById.controller.ts
import {Context, Next} from "hono";
import {Customer} from "../../type/shared.type";
import {RowList} from "postgres";
import {sql} from "../../service/db.service";

export async function getCustomerByIdController(c: Context, next: Next): Promise<Response> {
    try {
        const customerId = c.req.param('customerId')

        const customers: RowList<[Customer]> = await sql`
            select *
            from my_database.customer
            where id = ${customerId};
        `;
        console.log(customers);

        await sql.end();

        return c.text(`ok`, 200)
    } catch (error) {
        console.error(error)
        return c.text(`error`, 400)
    }
}
// index.ts
import {Hono, Context, Next} from 'hono'
import {getCustomerByIdController} from "./controller/customer/getCustomerById.controller"

const app = new Hono<{ Bindings: Bindings }>()

app.get('/customer/:customerId', getCustomerByIdController)

export default app

確認するために、wrangler tailでログを確認しました。

wrangler tail <WORKER> [OPTIONS]

Commands - Wrangler · Cloudflare Workers docs

1回目のログ

OPTIONS https://api.example.com/customer/1 - Ok @ 2024/12/10 15:37:27
GET https://api.example.com/customer/1 - Ok @ 2024/12/10 15:37:27
  (log) getCustomerByIdController
  (log) [
  {
    name: 'すずき',
    created_at: '2024-12-05T07:00:28.441Z',
    updated_at: '2024-12-09T11:42:24.947Z'
  }
]

連続した2回目以降のログ

OPTIONS https://api.example.com/customer/1 - Ok @ 2024/12/10 15:37:27
GET https://api.example.com/customer/1 - Ok @ 2024/12/10 15:37:27
  (log) getCustomerByIdController
  (error) Error: write CONNECTION_ENDED api.example.com:5432

私の力が足りないので、修正内容が合っているか自信はありませんが、コードの一部を修正しました。

詳しい方がいらっしゃいましたら、原因や、もっと良い書き方を教えて頂ければと思います。

修正したコード

// db.service.ts
import postgres from 'postgres';
import {Context} from "hono";

export function createSql(c: Context) {
	return postgres(
		{
			host: c.env.DB_HOST,
			port: c.env.DB_PORT,
			database: c.env.DB_DATABASE,
			username: c.env.DB_USERNAME,
			password: c.env.DB_PASSWORD,
		});
}
// getCustomerById.controller.ts
import {Context, Next} from "hono";
import {Customer} from "../../type/shared.type";
import {RowList} from "postgres";
import {createSql} from "../../service/db.service";

export async function getCustomerByIdController(c: Context, next: Next): Promise<Response> {
    try {
        const customerId = c.req.param('customerId')

        const sql = createSql(c)
        const customers: RowList<[Customer]> = await sql`
            select *
            from my_database.customer
            where id = ${customerId};
        `;
        console.log(customers);

        await sql.end();

        return c.text(`ok`, 200)
    } catch (error) {
        console.error(error)
        return c.text(`error`, 400)
    }
}