node-mysql2
node-mysql2 copied to clipboard
Fails to run on Cloudflare Pages/Workers
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
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.
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.
I've tracked it down to the
safe-bufferdependancy.
I couldn't find this dependency:
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
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
I've opened an issue here for anyone else that might be experiencing the same troubles: https://github.com/nitrojs/nitro/issues/3170
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, thanks for your dedication in bringing the solution here too 🤝
Reopening because I think it's worth mentioning it in the documentation. Please, feel free to contribute 🙋🏻♂️
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)
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)
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.