How to access environment variables from libraries?

Hey, I work at Prisma where we develop a Node based database ORM. Our users use Prisma ORM and its Prisma Client in apps deployed to Cloudflare Workers and Cloudflare Pages.

With the “recent” changes to the environment variables handling, making them not globally available anymore, but should be accessed via env or context.env in the handlers, we are running into the problem that our default instructions to use const prisma = new PrismaClient() are not enough for PrismaClient to be able to read the environment variable with e.g. the connection string to the database. An example issue on our side: https://github.com/prisma/prisma/issues/15958

Instead, our users have to provide the value manually to the constructor. That currently looks like this via existing functionality:

import { PrismaClient } from '@prisma/client'

export default {  
  fetch(request, env, context) {
    const prisma = new PrismaClient({
      datasources: {
        db: {
          url: env.DATABASE_URL
        },
      },
    })

    ...
  },
};

We could optimize this a bit by just supplying env to the constructor, but this would still be something our users first need to figure out via hitting an error - or reading the documentation first before writing any code (sure…).


Additionally Wrangler2 and miniflare now complain (only when the env var actually does exist) about some of our existing code that tries to read globalThis['DATABASE_URL'] as a fallback when process.env can not be read - which seemed to have worked in the past, is a no-op on actual deployment (so no value can be read, which is fine), but is a error in Wrangler and miniflare:

[pages:err] ReferenceError: DATABASE_URL is not defined.
Attempted to access binding using global in modules.
You must use the 2nd `env` parameter passed to exported handlers/Durable Object constructors, or `context.env` with Pages Functions.

I absolutely understand why this was added and that its goal is probably to prevent people from being confused and without warning when using code that used to work in the past does not after it is deployed today.
It is just unfortunate that this existing code on our side broke - and we have to advise people to work around it for now until we figure out a way how to change this, without breaking the other use cases that rely on this. (Possibly by detecting Cloudflare via an env var? Ah no, we can not access those globally…)


The core of our problem is that our library depends on being able to read the env vars globally somehow, but Cloudflare only offers them via context.env inside the handler. That is incompatible and can only be fixed by our users at the very least providing context.env to our constructor. Otherwise, we can not access any values there.

So my question to you:

  • How do you expect libraries to work that need to read values from env vars inside their code?
  • Is there any way get access to context.env instide the library, without the user having to explicitly supply is as a parameter to our constructor?

Thanks
Jan

3 Likes

Hi Jan
Thanks for bringing this up. Cloudflare Workers are moving away from global environment variables, which was the way to access them in Service Worker syntax, over to getting them from the env parameter passed to the fetch() handler. There are a number of reasons for this including the fact that we want to discourage using the global namespace since it can leak between requests.

So the current approach that we encourage libraries to use is to pass in environment variables, such as the database URL, via an API - in this case the PrismClient constructor. This is not only the safest and more encapsulated approach, it is arguably better for reasoning about the code. Using globals is a well known anti-pattern.

That being said, Workers have recently added support for “async local storage”, which is effectively a kind of globally accessible data store that is contained within the context of the current request. We implemented this in our next-on-pages adapter: Add globalThis.AsyncLocalStorage for 13.X versions of Next.js by GregBrimble · Pull Request #115 · cloudflare/next-on-pages · GitHub.

Thanks @pbacondarwin - not having the global scope even for environment variables will take some time to get used to.

Just saw the blog post about Node compat, including AsyncLocalStorage: Node.js compatibility for Cloudflare Workers – starting with Async Context Tracking, EventEmitter, Buffer, assert, and util

Is your suggestion that we for example instruct our users to set the database connection string value (which we usually read from an env var) in there, and then try to access that as a fallback if we can not find an environment variable?

Yes, I would recommend users pass the connection URL to the PrismClient constructor for Cloudflare Workers deployments.