Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: better-auth #71

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

Open
harshsbhat wants to merge 4 commits into upstash:main
base: main
Choose a base branch
Loading
from harshsbhat:better-auth
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions cli/src/cli/index.ts
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ export interface CliResults {
orm: "none" | "drizzle" | undefined
dialect?: "postgres" | undefined
provider?: "neon" | "postgres" | "vercel-postgres" | "planetscale" | undefined
auth: "better-auth" | "none"
noInstall?: boolean
}

export type Dialect = CliResults["dialect"]
export type Orm = CliResults["orm"]
export type Auth = CliResults["auth"]

export async function runCli(): Promise<CliResults | undefined> {
console.clear()
Expand Down Expand Up @@ -55,6 +57,8 @@ export async function runCli(): Promise<CliResults | undefined> {

let dialect = undefined
let provider = undefined
let auth: "better-auth" | "none" = "none"

if (orm === "drizzle") {
dialect = "postgres" as const // Only offering postgres

Expand All @@ -71,6 +75,22 @@ export async function runCli(): Promise<CliResults | undefined> {
outro("Setup cancelled.")
return undefined
}

// Only show auth option when using Drizzle ORM
const authResult = await select<"better-auth" | "none">({
message: "Which authentication system would you like to use?",
options: [
{ value: "better-auth", label: "Better Auth" },
{ value: "none", label: "None" },
],
})

if (isCancel(authResult)) {
outro("Setup cancelled.")
return undefined
}

auth = authResult
}

let noInstall = noInstallFlag
Expand All @@ -97,6 +117,7 @@ export async function runCli(): Promise<CliResults | undefined> {
orm,
dialect,
provider,
auth,
noInstall,
}
}
9 changes: 9 additions & 0 deletions cli/src/helpers/install-packages.ts
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,14 @@ export const installPackages = (options: InstallPackagesOptions) => {
}
}

// Handle auth installers
for (const [name, pkgOpts] of Object.entries(installers.auth)) {
if (pkgOpts.inUse) {
const spinner = ora(`Boilerplating auth: ${name}...`).start()
pkgOpts.installer(options)
spinner.succeed(chalk.green(`Successfully setup boilerplate for auth: ${chalk.green.bold(name)}`))
}
}

logger.info("")
}
5 changes: 3 additions & 2 deletions cli/src/helpers/scaffold-project.ts
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Dialect, Orm } from "@/cli/index.js"
import { Dialect, Orm, Auth } from "@/cli/index.js"
import { buildInstallerMap, InstallerMap, Provider } from "@/installers/index.js"
import { getUserPkgManager } from "@/utils/get-user-pkg-manager.js"
import path from "path"
Expand All @@ -11,9 +11,10 @@ interface ScaffoldProjectOptions {
dialect: Dialect
installers: InstallerMap
databaseProvider: Provider
auth: Auth
}

export const scaffoldProject = async ({ databaseProvider, projectName, installers, orm }: ScaffoldProjectOptions) => {
export const scaffoldProject = async ({ databaseProvider, projectName, installers, orm, auth }: ScaffoldProjectOptions) => {
const projectDir = path.resolve(process.cwd(), projectName)
const pkgManager = getUserPkgManager()

Expand Down
5 changes: 3 additions & 2 deletions cli/src/index.ts
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@ const main = async () => {
return
}

const { projectName, orm, dialect, provider } = results
const { projectName, orm, dialect, provider, auth } = results

const installers = buildInstallerMap(orm, provider)
const installers = buildInstallerMap(orm, provider, auth)

const projectDir = await scaffoldProject({
orm,
dialect,
databaseProvider: provider ?? "neon",
installers,
projectName,
auth
})

const pkgJson = fs.readJSONSync(path.join(projectDir, "package.json"))
Expand Down
55 changes: 55 additions & 0 deletions cli/src/installers/better-auth.ts
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import fs from "fs-extra"
import path from "path"
import { type Installer } from "@/installers/index.js"
import { PKG_ROOT } from "@/constants.js"
import { addPackageDependency } from "@/utils/add-package-dep.js"

export const betterAuthInstaller: Installer = ({ projectDir, databaseProvider }) => {
addPackageDependency({
projectDir,
dependencies: ["better-auth"],
devDependencies: false,
})

const extrasDir = path.join(PKG_ROOT, "template/extras")

// auth routes
const routerSrc = path.join(extrasDir, `src/app/api/auth/[...all]/route.ts`)
const routerDest = path.join(projectDir, `src/app/api/auth/[...all]/route.ts`)

// auth client
const clientSrc = path.join(extrasDir, `src/lib/auth-client.ts`)
const clientDest = path.join(projectDir, `src/lib/auth-client.ts`)

// auth lib
const libSrc = path.join(extrasDir, `src/lib/auth.ts`)
const libDest = path.join(projectDir, `src/lib/auth.ts`)

// auth schema
const schemaSrc = path.join(extrasDir, `src/server/db/schema/with-postgres-auth.ts`)
const schemaDest = path.join(projectDir, `src/server/db/schema.ts`)

// auth page
const pageSrc = path.join(extrasDir, `src/app/with-better-auth-page.tsx`)
const pageDest = path.join(projectDir, `src/app/page.tsx`)

// auth components
const componentsSrc = path.join(extrasDir, `src/app/with-better-auth-form.tsx`)
const componentsDest = path.join(projectDir, `src/app/components/signup.tsx`)

// env handling
const envSrc = path.join(extrasDir, `config/_env-drizzle-better-auth`)
const envDest = path.join(projectDir, ".env")

// copy all files
fs.copySync(routerSrc, routerDest)
fs.copySync(clientSrc, clientDest)
fs.copySync(libSrc, libDest)
fs.copySync(componentsSrc, componentsDest)
fs.copySync(pageSrc, pageDest)
fs.copySync(schemaSrc, schemaDest)

// append env vars instead of overwriting
const betterAuthEnv = fs.readFileSync(envSrc, "utf-8")
fs.appendFileSync(envDest, `\n\n# Better Auth\n${betterAuthEnv}`)
}
3 changes: 3 additions & 0 deletions cli/src/installers/dep-version-map.ts
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export const dependencyVersionMap = {
// vercel postgres
"@vercel/postgres": "^0.10.0",

// better auth
"better-auth": "^1.2.5",

// Drizzle
"drizzle-kit": "^0.30.1",
"drizzle-orm": "^0.39.0",
Expand Down
24 changes: 23 additions & 1 deletion cli/src/installers/index.ts
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { noOrmInstaller } from "./no-orm.js"
import { postgresInstaller } from "./postgres.js"
import { vercelPostgresInstaller } from "./vercel-postgres.js"
import { planetscaleInstaller } from "./planetscale.js"
import { betterAuthInstaller } from "./better-auth.js"

// Turning this into a const allows the list to be iterated over for programmatically creating prompt options
// Should increase extensibility in the future
Expand All @@ -30,6 +31,16 @@ export type InstallerMap = {
installer: Installer
}
}
auth: {
"better-auth": {
inUse: boolean
installer: Installer
}
none: {
inUse: boolean
installer: Installer
}
}
}

export interface InstallerOptions {
Expand All @@ -46,8 +57,19 @@ export type Installer = (opts: InstallerOptions) => void

export const buildInstallerMap = (
selectedOrm: Orm = "none",
selectedProvider?: Provider
selectedProvider?: Provider,
selectedAuth: "better-auth" | "none" = "none"
): InstallerMap => ({
auth: {
"better-auth": {
inUse: selectedOrm === "drizzle" && selectedAuth === "better-auth",
installer: betterAuthInstaller,
},
none: {
inUse: selectedOrm === "none" || selectedAuth === "none",
installer: () => {},
},
},
orm: {
none: {
inUse: selectedOrm === "none",
Expand Down
5 changes: 5 additions & 0 deletions cli/src/installers/neon.ts
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,18 @@ export const neonInstaller: Installer = ({ projectDir }) => {
const schemaSrc = path.join(extrasDir, "src/server/db/schema", `with-postgres.ts`)
const schemaDest = path.join(projectDir, "src/server/db/schema.ts")

// neon db instance
const dbSrc = path.join(extrasDir, `src/server/db/index-neon.ts`)
const dbDest = path.join(projectDir, `src/server/db/index.ts`)

const jstackSrc = path.join(extrasDir, "src/server/jstack", `drizzle-with-neon.ts`)
const jstackDest = path.join(projectDir, "src/server/jstack.ts")

fs.ensureDirSync(path.dirname(configDest))
fs.ensureDirSync(path.dirname(schemaDest))
fs.ensureDirSync(path.dirname(jstackDest))

fs.copySync(dbSrc, dbDest)
fs.copySync(configFile, configDest)
fs.copySync(schemaSrc, schemaDest)
fs.copySync(jstackSrc, jstackDest)
Expand Down
5 changes: 5 additions & 0 deletions cli/src/installers/postgres.ts
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export const postgresInstaller: Installer = ({ projectDir }) => {
fs.ensureDirSync(path.dirname(schemaDest))
fs.ensureDirSync(path.dirname(jstackDest))

// postgres db instance
const dbSrc = path.join(extrasDir, `src/server/db/index-postgres.ts`)
const dbDest = path.join(projectDir, `src/server/db/index.ts`)

fs.copySync(dbSrc, dbDest)
fs.copySync(configFile, configDest)
fs.copySync(schemaSrc, schemaDest)
fs.copySync(jstackSrc, jstackDest)
Expand Down
6 changes: 6 additions & 0 deletions cli/src/installers/vercel-postgres.ts
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@ export const vercelPostgresInstaller: Installer = ({ projectDir }) => {
const jstackSrc = path.join(extrasDir, "src/server/jstack", `drizzle-with-vercel-postgres.ts`)
const jstackDest = path.join(projectDir, "src/server/jstack.ts")

// db instance for vercel
const dbSrc = path.join(extrasDir, `src/server/db/index-vercel.ts`)
const dbDest = path.join(projectDir, `src/server/db/index.ts`)

fs.ensureDirSync(path.dirname(configDest))
fs.ensureDirSync(path.dirname(schemaDest))
fs.ensureDirSync(path.dirname(jstackDest))
fs.ensureDirSync(path.dirname(dbDest))

fs.copySync(configFile, configDest)
fs.copySync(schemaSrc, schemaDest)
fs.copySync(jstackSrc, jstackDest)
fs.copySync(dbSrc, dbDest)
}
7 changes: 7 additions & 0 deletions cli/template/extras/config/_env-drizzle-better-auth
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@


BETTER_AUTH_SECRET=
BETTER_AUTH_URL=http://localhost:3000
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=http://localhost:3000/api/auth/callback/google
4 changes: 4 additions & 0 deletions cli/template/extras/src/app/api/auth/[...all]/route.ts
View file Open in desktop
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be done via a jstack middleware as well. using use

Copy link
Author

@harshsbhat harshsbhat Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! I realized this after reviewing the https://www.better-auth.com/docs/integrations/hono yesterday. Give me a day. I will do the fixes. Also, the env ones.

Thanks for taking the time to review tho

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! I realized this after reviewing the https://www.better-auth.com/docs/integrations/hono yesterday. Give me a day. I will do the fixes. Also, the env ones.

Thanks for taking the time to review tho

image image

Hopefully this helps, I already implemented this for our company 2 months ago, publishing the code so it helps you. Good luck brother

harshsbhat reacted with eyes emoji
Copy link
Author

@harshsbhat harshsbhat Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for sharing this. We'll implement this tomorrow after college

Copy link

@un3trois7 un3trois7 Apr 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eager to see your implementation! the jstack cli def need a proper better-auth setup on launch

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";

export const { GET, POST } = toNextJsHandler(auth.handler);
72 changes: 72 additions & 0 deletions cli/template/extras/src/app/with-better-auth-form.tsx
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"use client"

import { useState } from "react"
import { authClient } from "@/lib/auth-client"

export const AuthComponent = () => {
const {
data: session,
isPending,
} = authClient.useSession()

const [isLoading, setIsLoading] = useState(false)

const handleGoogleAuth = async () => {
setIsLoading(true)
try {
await authClient.signIn.social({
provider: "google",
callbackURL: "/",
})
} catch (error) {
console.error(error)
} finally {
setIsLoading(false)
}
}

const handleSignOut = async () => {
setIsLoading(true)
try {
await authClient.signOut()
} catch (error) {
console.error(error)
} finally {
setIsLoading(false)
}
}

return (
<div className="w-full max-w-sm backdrop-blur-lg bg-black/15 px-8 py-6 rounded-md text-zinc-100/75 space-y-4">
{isPending ? (
<p className="text-center">Checking session...</p>
) : session?.user?.email ? (
<div className="flex flex-col items-center gap-6 p-6 bg-zinc-900 rounded-xl shadow-lg">
<div className="text-center">
<p className="text-zinc-400 text-sm">Logged in as</p>
<p className="font-semibold text-lg text-white">{session.user.email}</p>
</div>

<button
onClick={handleSignOut}
disabled={isLoading}
className="cursor-pointer w-full max-w-xs flex items-center justify-center gap-3 rounded-md text-base font-medium ring-2 ring-offset-2 ring-offset-zinc-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white ring-transparent hover:ring-white h-12 px-6 py-3 bg-gradient-to-tr from-zinc-800 to-zinc-700 text-zinc-100 transition disabled:opacity-60"
>
{isLoading ? "Signing out..." : "Sign Out"}
</button>
</div>

) : (
<div className="flex justify-center">
<button
onClick={handleGoogleAuth}
disabled={isLoading}
className="cursor-pointer w-full flex items-center justify-center gap-3 rounded-md text-base/6 ring-2 ring-offset-2 ring-offset-white focus-visible:outline-none focus-visible:ring-zinc-900 ring-transparent hover:ring-zinc-900 h-12 px-10 py-3 bg-zinc-800 text-zinc-100 font-medium bg-gradient-to-tl from-zinc-900 to-zinc-800 transition hover:bg-zinc-700 disabled:opacity-60 disabled:cursor-not-allowed"
>
{isLoading ? "Signing up..." : "Sign Up with Google"}
</button>
</div>
)}
</div>
)
}
32 changes: 32 additions & 0 deletions cli/template/extras/src/app/with-better-auth-page.tsx
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { cn } from "@/lib/utils"
import { AuthComponent } from "./components/signup"
import { RecentPost } from "./components/post"

export default async function Home() {
return (
<main className="flex min-h-screen bg-gradient-to-br from-zinc-950 via-zinc-900 to-zinc-950 flex-col items-center justify-center relative isolate">
<div className="absolute inset-0 -z-10 opacity-50 mix-blend-soft-light bg-[url('/noise.svg')] [mask-image:radial-gradient(ellipse_at_center,black,transparent)]" />
<div className="container flex flex-col items-center justify-center gap-6 px-4 py-16">
<h1
className={cn(
"inline-flex tracking-tight flex-col gap-1 transition text-center",
"font-display text-4xl sm:text-5xl md:text-6xl font-semibold leading-none lg:text-[4rem]",
"bg-gradient-to-r from-20% bg-clip-text text-transparent",
"from-white to-gray-50"
)}
>
<span>JStack</span>
</h1>

<p className="text-[#ececf399] text-lg/7 md:text-xl/8 text-pretty sm:text-wrap sm:text-center text-center mb-8">
The stack for building seriously fast, lightweight and{" "}
<span className="inline sm:block">
end-to-end typesafe Next.js apps.
</span>
</p>
<RecentPost />
<AuthComponent />
</div>
</main>
)
}
5 changes: 5 additions & 0 deletions cli/template/extras/src/lib/auth-client.ts
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createAuthClient } from "better-auth/react"

export const authClient = createAuthClient({
baseURL: process.env.BETTER_AUTH_URL,
})
Loading

AltStyle によって変換されたページ (->オリジナル) /