HomeBlogsProjects

Implementing Authentication in NextJS 15 with better auth and MongoDB

12 May, 2025

better-auth Hey there, fellow developers! After spending countless hours wrestling with different auth solutions in Next.js, I finally found my go-to authentication library: better-auth. In this blog, I’ll walk you through implementing it in your Next.js application, sharing all the gotchas I discovered along the way.

Why better-auth?

Before we dive in, you might be wondering why I chose better-auth over other options. Well, after trying various solutions, I found better-auth strikes the perfect balance between simplicity and flexibility. It’s lightweight, well-documented, and most importantly, it just works without too much configuration headache.

Getting Started

First things first, let’s install the necessary packages. Fire up your terminal and run:

npm install better-auth 
# or if you're using yarn
yarn add better-auth
# or if you're using pnpm
pnpm add better-auth

Setting environment variables

Create a .env file in the root of your project and add the following environment variables:

# use any random value here 
BETTER_AUTH_SECRET= 
# base url of your app
BETTER_AUTH_URL=http://localhost:3000

Creating auth instance

Create a auth.ts file in /lib or /utils directory of your code and add the following code.

import { betterAuth } from "better-auth";
import { mongodbAdapter } from "better-auth/adapters/mongodb";
import db from "./db"; 

export const auth = betterAuth({
    database: mongodbAdapter(db),
    emailAndPassword: {
        enabled: true,
        autoSignIn: false
    },
    // You can add social logins like this
    // ------------------------------------------
    // socialProviders: {
    //     github: {
    //         clientId: process.env.GITHUB_CLIENT_ID,
    //         clientSecret: process.env.GITHUB_CLIENT_SECRET
    //     }
    // }
    //-------------------------------------------
});

Configuring MongoDB client

Since we’re using MongoDB in this implementation, I’ve explained only MongoDB configuration. If you’re using prisma, drizzle or any other orm, check better-auth docs here.

Let’s start by installing MongoDB in our project. Fire up your terminal and run:

npm install mongodb
# or if you're using yarn
yarn add mongodb
# or if you're using pnpm
pnpm add mongodb

Initially, create the MONGODB_URI variable in your .env file

# this will work only if you're using a local database, but if you're using mongodb atlas
# copy URI srting from there and change it here
MONGODB_URI='mongodb://localhost:27017/users' 

Now create a db.ts file in either /lib or /utils directory of your code and add the following code.

import { MongoClient } from "mongodb";
// Throw an error if uri is not fetched from .env
if (!process.env.MONGODB_URI) {
     throw new Error('Invalid/Missing environment variable: "MONGODB_URI"');
}
// initializing mongodb client instance 
const client = new MongoClient(process.env.MONGODB_URI);
const db = client.db();

export default db;

Create handler

To handle API requests, we need to create a catch-all route handler in our project

Create a route.ts file at /app/api/auth/[...all]/route.ts in your project add the following code there.

import { auth } from "@/lib/auth"; // path to your auth.ts file
import { toNextJsHandler } from "better-auth/next-js";
 
export const { POST, GET } = toNextJsHandler(auth);

Server-side logic is complete! 🎉

Creating client instance

Create a auth-client.ts file at /lib directory of your project and add the following code

import { createAuthClient } from "better-auth/react"
export const authClient = createAuthClient({
    baseURL: "http://localhost:3000" // the base url of your auth server
})

That’s it, now you’re ready to use authentication in your project, here’s a very basic implementation to start out folder_structure

I have created a (auth) route group like this for better clarity, you can implement this as you want. If you’re following this tutorial, create sign-in route at /app/(auth)/sign-in/page.tsx and sign-up route at /app/(auth)/sign-in/page.tsx and a layout.tsx file inside (auth) directory

Add the following code in layout.tsx file

export default function AuthLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <main>
	    // this will align everything to center
      <div className="h-full w-screen flex items-center justify-center">{children}</div>
    </main>
  );
}

Now add this code in your sign-in route at /app/(auth)/sign-in/page.tsx

'use client'
import { authClient } from '@/lib/auth-client';
import React from 'react'
import { useState } from 'react'

const SignIn = () => {
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [loading, setLoading] = useState(false);

    const handleClick = async () => {
        setLoading(true);
        const {data, error} = await authClient.signIn.email({
            email,
            password,
            callbackURL: "/dashboard" 
        })
        setLoading(false);
    }
  return (
    <div>
      <h1>Sign In</h1>
      <input type="text" onChange={(e) => setEmail(e.target.value)} placeholder='email' />
      <input type="text" onChange={(e) => setPassword(e.target.value)} name="" id="password" />
      <button onClick={handleClick}>{loading ? "Loading..." : "Sign In"}</button>
    </div>
  )
}

export default SignIn

And for the sign up route

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

export default function SignUp() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [loading, setLoading] = useState(false);
  const [name, setName] = useState("");

  const handleClick = async () => {
    setLoading(true);
    const {data, error} = await authClient.signUp.email({
      email,
      password,
      name,
      callbackURL: "/dashboard"
    }, {
      onRequest: () => {
        setLoading(true);
      },
      onSuccess: () => {
        setLoading(false);
      },
      onError: () => {
        setLoading(false);
      }
    })
  }
  return (
   <main>
      <input type="text" placeholder="name" onChange={(e) => setName(e.target.value)} />
      <input type="email" placeholder="email" onChange={(e) => setEmail(e.target.value)} />
      <input type="password" placeholder="password" onChange={(e) => setPassword(e.target.value)} />
      <button onClick={handleClick}>{loading ? "Loading..." : "Sign Up"}</button>
   </main>
  );
}

I have created a dashboard route to redirect the user after signing-in, it has the following code, you can create any route you want to redirect your user to

import React from 'react'

const Dashboard = () => {
  return (
    <div>Dashboard</div>
  )
}

export default Dashboard

This the basic implementation of auth using better-auth in NextJS, all of this code is in this repo for more complex use cases, check better auth docs here

Thanks for reading, have a good day :)