Post

Why Cloudflare Workers Don't Work With MongoDB

Cloudflare Workers is a great platform for deploying serverless JavaScript code, but has historically never supported raw sockets. In May of 2023 they announced support for a connect() API which allows TCP Sockets to be created within Workers, but is not a direct replacement for Node.js’ net.Socket API.

Many NPM packages - including MongoDB’s Node.js driver - rely on net.Socket or tls.TLSSocket to connect to and communicate with remote systems. Using these libraries directly from Cloudflare Workers has not been possible as a result of these missing APIs.

Cloudflare recently announced that more NPM packages would be supported on Cloudflare Workers, but for libraries that need net.Socket or tls.TLSocket access, will they work in a Cloudflare Workers environment?

Packages that could not be imported with nodejs_compat, even as a dependency of another package, will now load. This includes popular packages such as […] mongodb, […] and many more.

Based on the blog post the Node.js driver should load, but can it be used?

Note that the fact that Workers can load, but not use the mongodb package was reported to Cloudflare at https://github.com/cloudflare/workers-sdk/issues/6684

The Cloudflare blog post has since been updated to remove mongod from the list of useable packages to avoid further confusion.

Sample Application

To test the latest iteration of Cloudflare Workers compatibility flags we’ll be working with the following configuration:

src/worker.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { BSON, MongoClient } from 'mongodb';

export interface Env {
  MONGODB_URI: string;
}

let client = null;
let requestCount = 0;

export default {
  async fetch(request: Request, env: Env, _ctx: ExecutionContext): Promise<Response> {
    console.log(JSON.stringify({ requestCount }));
    requestCount += 1;
    client ??= new MongoClient(env.MONGODB_URI, {
      maxPoolSize: 1, minPoolSize: 0,
      serverSelectionTimeoutMS: 5000,
    });

    const db = client.db('test');
    const coll = db.collection('test');

    if ((await coll.countDocuments()) > 10) {
      await coll.drop().catch(() => null);
    }

    await coll.insertOne({ a: 1 });

    return new Response(BSON.EJSON.stringify(await coll.findOne({ a: 1 }), null, '  ', { relaxed: false }));
  },
};

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "name": "mongodb-cloudflare-example",
  "version": "0.0.0",
  "type": "module",
  "private": true,
  "scripts": {
    "start": "wrangler dev"
  },
  "devDependencies": {
    "@cloudflare/workers-types": "^4.20240603.0",
    "ts-node": "^10.9.1",
    "typescript": "^5.0.4",
    "wrangler": "^3.59.0"
  },
  "dependencies": {
    "mongodb": "^6.8.1"
  }
}

wrangler.toml

1
2
3
4
5
6
name = "mongodb-cloudflare-example"
main = "src/worker.ts"
compatibility_flags = ["nodejs_compat_v2"]

[vars]
MONGODB_URI = "mongodb+srv://..."

To test the code above you would need to do the following:

1
2
npm install
npm run start

Evaluation

The default connection string format when connecting to your Atlas cluster is mongodb+srv, which is what we included initially in the wrangler.toml file.

The first time we run our test code however we’re unable to resolve the SRV connection format as it appears that dns.resolveTxt is not implemented:

1
2
3
⎔ Starting local server...
{"requestCount":0}
[wrangler:err] Error: [unenv] dns.resolveTxt is not implemented yet!

Since Atlas allows you to also connect using the standard connection string format, let’s update the MONGODB_URI in the wrangler.toml to instead be mongodb://...:

1
2
3
⎔ Starting local server...
{"requestCount":0}
[wrangler:err] MongoServerSelectionError: socket.once is not a function

Based on the above it appears events.once is missing, which Node.js’ net module exposes from an EventEmitter import. I don’t think we’d be able to polyfill all this if that were considered the path forward 😓.

What about trying to configure wrangler to connect to a local MongoDB instance (ex: mongodb://localhost:27017)? Well in that case it will still fail, but at least it will fail differently:

1
2
3
⎔ Starting local server...
{"requestCount":0}
[wrangler:err] MongoServerSelectionError: [unenv] net.createConnection is not implemented yet!

Alternatives?

If you happen upon the article called “Create a REST API with Cloudflare Workers and MongoDB Atlas” you may be thinking there’s an alternate solution to be explored. MongoDB offered a REST-based interface to your data in Atlas via the Atlas Data API, however this product was recently deprecated and will be sunset.

A custom REST-based API would be a solution to working with your MongoDB data from within Cloudflare Workers, so until the runtime is updated to better support Node.js’ socket APIs, see the Data API deprecation page for some ideas.

Neurelo seems like a good option for getting a REST-based API off the ground with little effort.

Summary

Though module aliasing and polyfills might be an option for some functionality, it really seems like Cloudflare Workers just aren’t meant to work with Node.js’ socket APIs. As a result, libraries such as MongoDB’s Node.js driver simply won’t be able to connect to anything.

Some work was proposed by MongoDB’s team to allow a custom transport layer to be provided, however this would still require Cloudflare Workers to support Node.js’ TLS API as TLS cannot, for good reasons, be disabled for Atlas deployments.

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.