node-mysql2 icon indicating copy to clipboard operation
node-mysql2 copied to clipboard

Fails to run on Cloudflare Pages/Workers

Open alexcroox opened this issue 9 months ago • 9 comments

I was very excited to try this on Cloudflare pages/workers, unfortunately it fails to run with the following:

⎔ Starting local server...
[wrangler:inf] Ready on http://localhost:8788
✘ [ERROR] [request error] [unhandled] [GET] http://localhost:8788/health-check

   TypeError: buffer.hasOwnProperty is not a function
      at ../.wrangler/tmp/pages-qV1hY7/chunks/routes/index.get.mjs
  (file:///Users/alex/Projects/cloudflare-mysql2/.wrangler/tmp/pages-qV1hY7/chunks/routes/index.get.mjs:6727:15)
      ... 7 lines matching cause stack trace ...
      at async drainBody
  (file:///Users/alex/Projects/cloudflare-mysql2/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts:5:10)
  {
    cause: TypeError: buffer.hasOwnProperty is not a function
        at ../.wrangler/tmp/pages-qV1hY7/chunks/routes/index.get.mjs
  (file:///Users/alex/Projects/cloudflare-mysql2/.wrangler/tmp/pages-qV1hY7/chunks/routes/index.get.mjs:6727:15)
        at __init
  (file:///Users/alex/Projects/cloudflare-mysql2/dist/.wrangler/tmp/dev-IG5qan/zr2n0f0w3i.js:6:56)
        at null.<anonymous>
  (file:///Users/alex/Projects/cloudflare-mysql2/.wrangler/tmp/pages-qV1hY7/chunks/_/nitro.mjs:3097:28)
        at async Object.handler
  (file:///Users/alex/Projects/cloudflare-mysql2/.wrangler/tmp/pages-qV1hY7/chunks/_/nitro.mjs:2134:19)
        at async toNodeHandle
  (file:///Users/alex/Projects/cloudflare-mysql2/.wrangler/tmp/pages-qV1hY7/chunks/_/nitro.mjs:2405:7)
        at async b
  (file:///Users/alex/Projects/cloudflare-mysql2/.wrangler/tmp/pages-qV1hY7/chunks/_/nitro.mjs:1183:6887)
        at async O
  (file:///Users/alex/Projects/cloudflare-mysql2/.wrangler/tmp/pages-qV1hY7/chunks/_/nitro.mjs:1183:7170)
        at async jsonError
  (file:///Users/alex/Projects/cloudflare-mysql2/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts:22:10)
        at async drainBody
  (file:///Users/alex/Projects/cloudflare-mysql2/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts:5:10),
    statusCode: 500,
    fatal: false,
    unhandled: true,
    statusMessage: undefined,
    data: undefined
  }

I've tracked it down to the safe-buffer dependancy.

mysql2 3.13.0 └─┬ iconv-lite 0.6.3 └── safer-buffer 2.1.2

It looks like this was reported a couple of years ago: https://github.com/ChALkeR/safer-buffer/issues/7

I've narrowed down my project and it only triggers this error when the 'mysql2/promise' is imported.

It runs fine locally with node just not with wrangler locally or on CF remotely. I've tried deploying to both pages and workers with Nitro.js

alexcroox avatar Mar 06 '25 15:03 alexcroox

Hi, @alexcroox! Could you share a self-contained reproduction of this error using wrangler? As it's mostly free, I can create a private application to test with related issues from MySQL2.

wellwelwel avatar Mar 06 '25 16:03 wellwelwel

Regardless the dependency you mentioned, there was a similar situation when we replaced a critical dependency of MySQL2 (#2988). But to elaborate on that (as it no longer seems to be maintained), we'd first need to have the error in hands.

wellwelwel avatar Mar 06 '25 17:03 wellwelwel

I've tracked it down to the safe-buffer dependancy.

I couldn't find this dependency:

Image

wellwelwel avatar Mar 06 '25 18:03 wellwelwel

Apologies it must have been a red herring that including mysql2 caused the error. Perhaps it activated some bundling in Nitro that triggered the error

Image

Either way if it's not used by this lib then nothing you can do. Thanks for looking and pointing me towards npm ls!

Edit: safe-buffer is used by iconv-lite which is a dependancy of mysql2. https://github.com/ashtuchkin/iconv-lite/blob/master/package.json#L42

alexcroox avatar Mar 06 '25 18:03 alexcroox

I've opened an issue here for anyone else that might be experiencing the same troubles: https://github.com/nitrojs/nitro/issues/3170

alexcroox avatar Mar 06 '25 19:03 alexcroox

Workaround for Nitro users:

cloudflare: {
    nodeCompat: true,
    deployConfig: true
  },
  unenv: {
    alias: {
      "safer-buffer": "node:buffer"
    }
  }

In a future release unenv will handle this automatically

alexcroox avatar Mar 09 '25 20:03 alexcroox

@alexcroox, thanks for your dedication in bringing the solution here too 🤝

wellwelwel avatar Mar 09 '25 21:03 wellwelwel

Reopening because I think it's worth mentioning it in the documentation. Please, feel free to contribute 🙋🏻‍♂️

wellwelwel avatar Mar 11 '25 23:03 wellwelwel

Anyone know is this bug or wrong configuration? Sometime works perfectly fine and sometime got 500 with:

Cannot perform I/O on behalf of a different request. I/O objects (such as streams, request/response bodies, and others) created in the context of one request handler cannot be accessed from a different request's handler. This is a limitation of Cloudflare Workers which allows us to improve overall performance. (I/O type: Writable)

oritwoen avatar Mar 13 '25 10:03 oritwoen

Anyone know is this bug or wrong configuration? Sometime works perfectly fine and sometime got 500 with:

Cannot perform I/O on behalf of a different request. I/O objects (such as streams, request/response bodies, and others) created in the context of one request handler cannot be accessed from a different request's handler. This is a limitation of Cloudflare Workers which allows us to improve overall performance. (I/O type: Writable)

I think this is due to reuse of the db connection on a second request. I got the same error but I could fix it by initializing my db client on every request instead of having it globally initialized (and therefore reused on requests)

prometixX avatar Jul 06 '25 10:07 prometixX

Anyone know is this bug or wrong configuration? Sometime works perfectly fine and sometime got 500 with:

Cannot perform I/O on behalf of a different request. I/O objects (such as streams, request/response bodies, and others) created in the context of one request handler cannot be accessed from a different request's handler. This is a limitation of Cloudflare Workers which allows us to improve overall performance. (I/O type: Writable)

That's a common issue with db utils in Cloudflare. If you want to re-use the connection while avoiding the global reference that CF doesn't allow, you can use a class based utility:

import { drizzle, type MySql2Client } from 'drizzle-orm/mysql2';
import mysql from 'mysql2/promise';

import { relations } from '~/database/legacy/schema/relations';

export class Database {
  private connection: MySql2Client | null = null;

  constructor() {
    if (!this.connection) {
      this.connection = mysql.createPool({...});
    }

    return drizzle({
      client: this.connection,
      relations,
    });
  }

  closeConnection() {
    if (this.connection) {
      this.connection.end()
      this.connection = null;
    }
  }
}

export const useDB = () => new Database();

Now useDB() can be used everywhere and re-use the connection if there is one already.

alexcroox avatar Jul 06 '25 17:07 alexcroox