Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jstack and Better Auth ENV problem #32

Open
emretfn opened this issue Feb 3, 2025 · 12 comments
Open

Jstack and Better Auth ENV problem #32

emretfn opened this issue Feb 3, 2025 · 12 comments

Comments

@emretfn
Copy link
Contributor

emretfn commented Feb 3, 2025

Using jstack & better-auth in a Cloudflare Workers Environment

Hi,

I'm encountering some issues while trying to use better-auth with jstack. Below, I've detailed the steps I took and the problems I faced, particularly regarding accessing environment variables in the Cloudflare Workers environment where process.env is not available. I'm looking for guidance on how to integrate jstack and better-auth under these constraints.

1. Creating the db Object with Drizzle

First, in the src/lib/db.ts file, I created a db object using Drizzle:

import { drizzle } from "drizzle-orm/neon-http";

const getDbUrl = () => {
  if (typeof process.env.DATABASE_URL === "undefined") {
    throw new Error("DATABASE_URL is not defined");
  }

  return process.env.DATABASE_URL;
};

export const db = drizzle(getDbUrl());

2. Using the Drizzle Adapter for better-auth

In the src/lib/auth.ts file, I passed the drizzle adapter along with the necessary schemas to the better-auth function:

import { betterAuth } from "better-auth";
import { db } from "./db";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { posts } from "@/server/db/schema";
import { account, session, user, verification } from "../../auth-schema";

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg",
    schema: {
      ...posts,
      user: user,
      account: account,
      session: session,
      verification: verification,
    },
  }),
  emailAndPassword: {
    enabled: true,
  },
});

3. Defining the API Route Handler

I defined the handler from the returned auth object as the route handler for POST and GET requests in src/server/index.ts:

const api = j
  .router()
  .basePath("/api")
  .on(["POST", "GET"], "/auth/**", (c) => auth.handler(c.req.raw))
  .use(j.defaults.cors)
  .onError(j.defaults.errorHandler);

4. Setting Up the Auth Middleware

In the src/server/jstack.ts file, I defined an auth middleware that also makes use of the auth object:

// ... other code
const databaseMiddleware = j.middleware(async ({ c, next }) => {
  const { DATABASE_URL } = env(c);

  const db = drizzle(DATABASE_URL);

  return await next({ db });
});

const authMiddleware = j.middleware(async ({ c, next }) => {
  const session = await auth.api.getSession({ headers: c.req.raw.headers });

  if (!session) {
    throw new HTTPException(401, {
      message: "Unauthorized",
    });
  }

  return await next({ user: session.user });
});

// ... other code
export const privateProcedure = j.procedure.use(databaseMiddleware).use(authMiddleware);

5. Wrangler Configuration

While deploying to Cloudflare Workers, I initially encountered errors that were resolved by adding the compatibility_flags and updating the compatibility_date in my wrangler.toml file:

name = "app-name"
compatibility_flags = [ "nodejs_compat" ]
compatibility_date = "2024-09-23"
main = "src/server/index.ts"

[dev]
port = 8080

6. The Issue and Question

After the above steps, I began receiving an error in the src/lib/db.ts file because the Cloudflare Workers environment doesn't support accessing process.env.DATABASE_URL. Since Cloudflare Workers doesn't have the typical Node.js environment variables, they are instead provided via Workers bindings.

Question:
How should I access environment variables in a Cloudflare Workers environment when using jstack and better-auth together, instead of relying on process.env? What is the best practice in this situation? For instance, how can I structure the Workers bindings and integrate the drizzle adapter with better-auth in this context?

I'd appreciate any suggestions or recommendations on how to properly set up jstack and better-auth in a Cloudflare Workers environment.

Thanks!

@Shivam-002
Copy link

You have to create a .dev.vars file and place your .env variables there. Workers will take care rest of it.

//wrangler.toml 
name = "app-name"
compatibility_date = "2025-02-01"
compatibility_flags = ["nodejs_compat"]
main = "src/server/index.ts"

[dev]
port = 8080

Also you have to specify your backend host in client.ts

import type { AppRouter } from "@/server";
import { createClient } from "jstack";

/**
 * Your type-safe API client
 * @see https://jstack.app/docs/backend/api-client
 */
export const client = createClient<AppRouter>({
  // baseUrl: "http://localhost:3000/api",
  baseUrl: "http://localhost:8080/api",
});

Then simply run

npm run dev // `starts` frontend at port 3000
wrangler dev // starts backend at port 8080

Let me know if issue still persists.

@emretfn
Copy link
Contributor Author

emretfn commented Feb 4, 2025

Hello @Shivam-002 as you said, I created the .dev.vars file as follows and did what you said, but the problem is still not solved.

DATABASE_URL="db_url"
BETTER_AUTH_SECRET="secret"
BETTER_AUTH_URL="http://localhost:8080"

The problem here is that we are trying to access the env value with process.env in db.ts.

X [ERROR] service core:user:jstack-drizzle-be: Uncaught Error: DATABASE_URL is not defined

    at null.<anonymous> (index.js:44177:11) in getDbUrl
    at null.<anonymous> (index.js:44181:18)

@joschan21
Copy link
Collaborator

hey! You can safely create the client and auth inside of this handler function:

.on(["POST", "GET"], "/auth/**", (c) => auth.handler(c.req.raw))

use the env adapter as follows to access the env vars you need: https://jstack.app/docs/getting-started/environment-variables#cloudflare-workers

after that, still inside the handler function, create the auth in the same way as you currently are and you should be good to go!

@joschan21
Copy link
Collaborator

by the way, the above mentioned env adapter will work for any runtime. it checks which runtime you are on and will access either process.env from the nodejs globals or read from context in the workerd runtime. its really useful

@emretfn
Copy link
Contributor Author

emretfn commented Feb 4, 2025

Hey @joschan21, I used auth.handler in that way, I don't think the problem is there. The problem occurs in src/lib/db.ts (mentioned in first point) because there I try to get the database url from process.env to pass to drizzle. I pass the db I created here to the betterAuth function in src/lib/auth.ts. I use the auth instance returned from this function both in frontend and hono (as you gave in the api example) which is the problem i think. If I use the env adapter you mentioned, I cannot pass the c parameter to get the env in the file where I create the db. So how can i access the envs for both frontend and hono on Cloudflare workers?

I also tried to create the instance in middleware with the betterAuth function (which is bad practice i think), but according to the documentation of better auth, we need to define it in root, under lib file or under utils file. Because Better Auth creates the auth schema by looking at this config.

@ajiohjesse
Copy link
Contributor

I use the auth instance returned from this function both in frontend and hono (as you gave in the api example) which is the problem i think.

This might be a dumb solution but how about creating two instances of your auth? The same way you have two instances of your db, where one is created inside the lib/db.ts and the other inside the databaseMiddleware. You could possibly instantiate auth inside the same middleware you're creating the db in, pass it that created db and then expose the auth function in the context. Then you can destructure that auth function from the context when registering the auth/** routes.

@yahyazgour
Copy link

@emretfn you have to completely let go of defining your auth and db instance in Next.js, and instead define them in Hono backend. The way I did it is through Jstack middleware. After that you have to define an authRouter with procedures for each Better Auth Endpoint, and it should work.

The reason why I created a procedures for each endpoint instead of one catch all procedure "*" is because Jstack expects every procedure to be either query() or mutation(), while better auth handler works on both methods.

You don't need to create a /api/auth/* catch all route in Next.js since Jstack already provides a /api/* catch all route, which will forward everything to the Hono setup.

You can keep Better Auth Client(auth-client.ts) defined on your Next.js app and use it on your client components (or use the procedures you defined in your authRouter). In your server components, use the procedures.

I will link the repo I used to figure out this setup, not sure if it's optimal though, so any ideas or suggestions to improve this are welcome.
https://github.com/yahyazgour/jstack-betterauth/tree/main/src/server

@joschan21 can you please confirm to me that defining DB and AUTH in middlewares is fine? example in the same repo, I added a singlton pattern cause i'm not sure. Thanks a lot!

@emretfn
Copy link
Contributor Author

emretfn commented Feb 4, 2025

I use the auth instance returned from this function both in frontend and hono (as you gave in the api example) which is the problem i think.

This might be a dumb solution but how about creating two instances of your auth? The same way you have two instances of your db, where one is created inside the lib/db.ts and the other inside the databaseMiddleware. You could possibly instantiate auth inside the same middleware you're creating the db in, pass it that created db and then expose the auth function in the context. Then you can destructure that auth function from the context when registering the auth/** routes.

It is already ridiculous to use the same db instance both in nextjs server side and in hono. There should be a single instance on the server side and it should be under hono. If I want to access it, I should access it with the api provided by jstack. But I could not find how to pass the better auth instance I created on the hono side to the /api/auth/** handler. Also, since I defined the better auth instance here, I cannot generate the drizzle schema with the better auth cli.

@emretfn you have to completely let go of defining your auth and db instance in Next.js, and instead define them in Hono backend. The way I did it is through Jstack middleware. After that you have to define an authRouter with procedures for each Better Auth Endpoint, and it should work.

I absolutely agree, if I want to use better auth server-side, I have to do it on jstack, otherwise it makes no sense anyway. I will review the repo you shared. But one thing stuck in my mind, if I don't create a better auth instance on the nextjs side, I can't generate the auth schema with better auth cli. How did you solve this?

@yahyazgour
Copy link

Just export the auth instance from jstack.ts, and the better auth generate command should work. alternatively you can use --config flag to provide the path.

Image

@emretfn
Copy link
Contributor Author

emretfn commented Feb 4, 2025

Just export the auth instance from jstack.ts, and the better auth generate command should work. alternatively you can use --config flag to provide the path.

Yes this could be a solution. but I still think something is wrong. I feel like Jstack x Better Auth should work together much more easily.

Hey @Bekacru, sorry for tagging but maybe you can help us with how to integrate better auth into jstack with best practices.

@yamz8
Copy link

yamz8 commented Feb 14, 2025

any updates here?

@Bekacru
Copy link

Bekacru commented Feb 14, 2025

You can make a getter for your auth instance

function getAuth(ctx){
    return betterAuth({ }) // config here
}

and for migrations, you can define an endpoint that you can call and use the migrator programmatically

import { getMigrations } from "better-auth/api";

// call this is inside an endpoint
const { runMigrations } = await getMigrations(getAuth(ctx));
await runMigrations()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants