Better Auth is an open-source authentication library for web applications. It's written in TypeScript, can be extended with plugins, and supports multiple database adapters, including Prisma.
In this guide, you'll wire Better Auth into a brand-new Next.js app and persist users in a Prisma Postgres database. You can find a complete example of this guide on GitHub.
It will prompt you to customize your setup. Choose the defaults:
Navigate to the project directory:
cd betterauth-nextjs-prisma
These selections create a Next.js project with TypeScript for type safety, ESLint for code quality, and Tailwind CSS for styling. Using the src/ directory and the App Router are common conventions for new Next.js applications.
Install the necessary Prisma packages. The dependencies differ slightly depending on whether you use Prisma Postgres with Accelerate or another database.
title="bun"
bun add prisma@prev tsx @types/pg --dev
pnpm
pnpm add prisma@prev tsx @types/pg --save-dev
yarn
yarn add prisma@prev tsx @types/pg --dev
npm
npm install prisma@prev tsx @types/pg --save-dev
bun add @prisma/client@7 @prisma/adapter-pg dotenv pg
[!NOTE]
If you are using a different database provider (MySQL, SQL Server, SQLite), install the corresponding driver adapter package instead of @prisma/adapter-pg. For more information, see Database drivers.
Once installed, initialize Prisma in your project:
Once installed, initialize Prisma in your project:
[!NOTE]
prisma init creates the Prisma scaffolding and a local DATABASE_URL. In the next step, you will create a Prisma Postgres database and replace that value with a direct postgres://... connection string.
This will create:
A prisma directory with a schema.prisma file
A .env file containing a local DATABASE_URL at the project root
An output directory for the generated Prisma Client as better-auth/generated/prisma
Create a Prisma Postgres database and replace the generated DATABASE_URL in your .env file with the postgres://... connection string from the CLI output:
This will create:
A prisma directory with a schema.prisma file
A .env file containing a local DATABASE_URL at the project root
An output directory for the generated Prisma Client as better-auth/generated/prisma
Create a Prisma Postgres database and replace the generated DATABASE_URL in your .env file with the postgres://... connection string from the CLI output:
Now, create a configuration file for Better Auth. In the src/lib directory, create an auth.ts file:
Bash
touch src/lib/auth.ts
In this file, you'll configure Better Auth to use the Prisma adapter, which allows it to persist user and session data in your database. You will also enable email and password authentication.
Better Auth also supports other sign-in methods like social logins (Google, GitHub, etc.), which you can explore in their email and password documentation.
[!NOTE]
If your application runs on a port other than 3000, you must add it to the trustedOrigins in your auth.ts configuration to avoid CORS errors during authentication requests.
Now, create a configuration file for Better Auth. In the src/lib directory, create an auth.ts file:
touch src/lib/auth.ts
In this file, you'll configure Better Auth to use the Prisma adapter, which allows it to persist user and session data in your database. You will also enable email and password authentication.
title="src/lib/auth.ts"
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import prisma from "@/lib/prisma";
export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: "postgresql",
}),
});
Better Auth also supports other sign-in methods like social logins (Google, GitHub, etc.), which you can explore in their email and password documentation.
Better Auth provides a CLI command to automatically add the necessary authentication models (User, Session, Account, and Verification) to your schema.prisma file.
Run the following command:
title="bun"
bunx auth generate
pnpm
pnpm prisma migrate dev --name add-auth-models
yarn
yarn prisma migrate dev --name add-auth-models
npm
npx prisma migrate dev --name add-auth-models
This will add the following models:
model User {
id String @id
name String
email String
emailVerified Boolean
image String?
createdAt DateTime
updatedAt DateTime
sessions Session[]
accounts Account[]
@@unique([email])
@@map("user")
}
model Session {
id String @id
expiresAt DateTime
token String
createdAt DateTime
updatedAt DateTime
ipAddress String?
userAgent String?
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([token])
@@map("session")
}
model Account {
id String @id
accountId String
providerId String
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
accessToken String?
refreshToken String?
idToken String?
accessTokenExpiresAt DateTime?
refreshTokenExpiresAt DateTime?
scope String?
password String?
createdAt DateTime
updatedAt DateTime
@@map("account")
}
model Verification {
id String @id
identifier String
value String
expiresAt DateTime
createdAt DateTime?
updatedAt DateTime?
@@map("verification")
}
Better Auth needs an API endpoint to handle authentication requests like sign-in, sign-up, and sign-out. You'll create a catch-all API route in Next.js to handle all requests sent to /api/auth/[...all].
In the src/app/api directory, create an auth/[...all] folder structure and a route.ts file inside it:
Add the following code to the newly created route.ts file. This code uses a helper from Better Auth to create Next.js-compatible GET and POST request handlers.
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth);
Next, you'll need a client-side utility to interact with these endpoints from your React components. In the src/lib directory, create an auth-client.ts file:
touch src/lib/auth-client.ts
Add the following code, which creates the React hooks and functions you'll use in your UI:
Next, import the necessary hooks from React and Next.js to manage state and navigation. Initialize the router and a state variable to hold any potential error messages.
Now, import the signUp function from your Better Auth client and add the handleSubmit function. This function is triggered on form submission and calls the signUp.email method provided by Better Auth, passing the user's name, email, and password.
title="src/app/sign-up/page.tsx"
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
//add-next-lin
import { signUp } from "@/lib/auth-client";
export default function SignUpPage() {
const router = useRouter();
const [error, setError] = useState<string | null>(null);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setError(null);
const formData = new FormData(e.currentTarget);
const res = await signUp.email({
name: formData.get("name") as string,
email: formData.get("email") as string,
password: formData.get("password") as string,
});
if (res.error) {
setError(res.error.message || "Something went wrong.");
} else {
router.push("/dashboard");
}
}
return (
<main className="max-w-md mx-auto p-6 space-y-4 text-white">
<h1 className="text-2xl font-bold">Sign Up</h1>
</main>
);
}
To inform the user of any issues, add an element that conditionally renders when the error state is not null.
title="src/app/sign-up/page.tsx"
"use client";import { useState } from "react";import { useRouter } from "next/navigation";import { signUp } from "@/lib/auth-client";export default function SignUpPage() { const router = useRouter(); const [error, setError] = useState<string | null>(null); async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); setError(null); const formData = new FormData(e.currentTarget); const res = await signUp.email({ name: formData.get("name") as string, email: formData.get("email") as string, password: formData.get("password") as string, }); if (res.error) { setError(res.error.message || "Something went wrong."); } else { router.push("/dashboard"); } } return ( <main className="max-w-md mx-auto p-6 space-y-4 text-white"> <h1 className="text-2xl font-bold">Sign Up</h1> {error && <p className="text-red-500">{error}</p>} </main> );}
Finally, add the HTML form with input fields for the user's name, email, and password, and a submit button.
Import the useSession hook from your Better Auth client. This hook is the key to managing authentication state on the client side. It provides the session data and a pending status.
To protect this route, use a useEffect hook. This effect checks if the session has loaded (!isPending) and if there is no authenticated user (!session?.user). If both are true, it redirects the user to the sign-in page.
Finally, if the user is authenticated, display their name and email from the session object. Also, import the signOut function and add a button that calls it, allowing the user to log out.