How to use better-auth CLI in a Cloudflare workers + Cloudflare D1 + react-router + drizzle project?
I created a React Router 7 project using the Cloudflare workers template:
pnpm create cloudflare@latest my-react-router-app --framework=react-router
. I added D1 bindings and drizzle to this project.
The Problem:
I am struggling to find a pattern where I can somehow export auth
in a way the CLI can pick up.
The crux is that betterAuth()
requires the db instance as a function argument. In Cloudlfare workers, however, the db instance is only available in the fetch()
function. So logically the only spot I can instance the drizzle ORM db instance, and thus the betterAuth instance, is within the fetch
function scope.
The question is: is there a work around to this? I can't be the first to run into this little puzzle. Let me know any ideas!
Note: I am aware of a way to import the env
globally by import { env } from "cloudflare:workers";
, however this package is only available in a Cloudflare or Miniflare environment. Ref: https://developers.cloudflare.com/changelog/2025-03-17-importable-env/
Cloudflare Docs
Import
env
to access bindings in your Worker's global scopeSolution:Jump to solution
I recommend making a fake auth file and a real auth file.
The fake one is just your better-auth config except for any fields which require ENV vars.
This file will be the one you would then use for the better-auth cli to read.
The real one would be the one which includes all needed ENV variables for Better Auth to function....
7 Replies
Solution
I recommend making a fake auth file and a real auth file.
The fake one is just your better-auth config except for any fields which require ENV vars.
This file will be the one you would then use for the better-auth cli to read.
The real one would be the one which includes all needed ENV variables for Better Auth to function.
Tip: Make a config which you can export and use between the both of the real & fake auth files, to keep things consistent - and of course, this excludes the ENV parts in this config.
Gochya. Thanks for the response! I think that is a fine workaround. I simply did this to generate my tables:
I have this issue as well -- is there any chance to provide 1st class support for this, or at least dedicated docs?
I'm also hitting this issue. I thought I'd get this thing setup real fast today...and then completely hit a roadblock because I can't scaffold the database.
@Ping I'm not seeing how your recommendation would work, particularly since the betterAuth configuration requires a database, which can only come from the env in cloudflare. How should that be configured?
Ok, to get the DB setup. I had to do what @true said above as a first step. That enabled me to get drizzle to create a schema file. I then had to use drizzle-kit with that schema file to create the SQL. Finally, I had to manually connect to the local cloudflare db and execute the sql container in that file. That gets the DB setup. It's not great though, because I have no desire to use drizzle. But, right now this seems the only way to get some SQL to actually use in setting up the DB.
I'm on to the next in a string of problems now. When I try to do a POST /api/auth/sign-up/email I get a 422 UNPROCESSABLE_ENTITY error. So, no idea what's going on there. I'm using code just like the docs and have no fancy backend stuff. I'm just handing it straight to the better auth handler.
I resolve this next set of issues. It turns out the Drizzle was snake_casing the properties, but Keysely was sending camelCase. Drizzle apparent has no way to not snake_case and Keysely has no way to snake_case. So, to fix this issue, I had to manually update the DB schema generated by Drizzle to use pascalCase column names. Then, once that was used to set up the DB, everything worked. NOTE: See the next message for a better (not silly) way to handle this, which occurred to me once I stepped away from the problem.
Ok, I am silly. The simpler solution is to just use
better-sqlite3
directly. For some reason I didn't realize this was an option. Didn't have my thinking cap on I guess. So, the solution I ended up with is having two auth configuration files. One is for the runtime setup, which uses the D1Dialect
. No changes there. Then I have a second setup that changes the database property. I import Database from "better-sqlite3"
directly and then set database: new Database("database.sqlite")
in the config. This serves as a dummy database. I then run something like npx @better-auth/cli@latest generate --config ./build/auth.ts --output ./migrations/schema.sql --y
to injest my custom build-time config and output the generated schema. Finally, using SQLLiteStudio, I open the local Cloudflare database, located at .wrangler/state/v3/d1
. From the DB context menu, I can select "Execute SQL from file". I then select my schema and run it. Voila, the DB is now setup for local development with Cloudflare. Hopefully this helps folks in the future.Btw here you don't need to run
generate
migrate
is enough. Generate is only required if you want to run migration on your ownWell, I'll need to run it on my own too.
Thanks!
you have example code for your solution? i do not quite get it what you have done...