shahriyar.dev
Back to blog
Next.jsauthenticationMongoDBBetter Authserverlessweb development

Setting Up Better Auth with MongoDB in Your Next.js Application

·6 min read

Setting up authentication in a modern web application can quickly become complex, especially when balancing security, user experience, and developer productivity. Next.js provides a great foundation for building full-stack apps, but you still need a reliable authentication layer. That's where Better Auth comes in — a flexible, serverless‑friendly authentication library that works seamlessly with MongoDB. In this post, we'll walk through configuring Better Auth with MongoDB in your Next.js application, covering everything from environment setup to protecting your routes.

Why Choose Better Auth?

Before we dive into the code, let's look at what makes Better Auth a solid choice for Next.js projects.

  • Serverless ready – No persistent server processes needed; works with Vercel, Netlify, and self‑hosted deployments.
  • Provider agnostic – Supports email/password, OAuth (Google, GitHub, etc.), and magic links out of the box.
  • Database flexibility – Works with SQL databases via Prisma, but also has a first‑class MongoDB adapter.
  • Lightweight – Minimal dependencies and a small footprint.
  • Type‑safe – Built with TypeScript, providing full type inference for your user objects and session data.

If you're already using MongoDB (or want to) and need an authentication solution that doesn't lock you into a specific service, Better Auth is worth considering.

Prerequisites

Make sure you have the following before starting:

  • A Next.js 14 (or later) application (App Router or Pages Router – both work).
  • Node.js 18+ installed.
  • A MongoDB instance (local, Atlas, or Docker).
  • A basic understanding of environment variables and Next.js API routes.

Step 1 – Install Dependencies

Open your terminal in the root of your Next.js project and install the required packages:

bash
npm install better-auth mongodb

Optionally, if you want to use dotenv to load your MongoDB URI locally (though Next.js already handles .env.local), you can add it, but it's not required.

Step 2 – Configure MongoDB Connection

Better Auth needs a MongoDB client instance to interact with your database. Create a utility file to manage the connection. We'll use a singleton pattern to reuse the client across serverless function invocations.

ts
// lib/mongodb.ts
import { MongoClient } from "mongodb";

const uri = process.env.MONGODB_URI!;
const options = {};

let client: MongoClient;
let clientPromise: Promise<MongoClient>;

if (process.env.NODE_ENV === "development") {
  // In development, preserve client across hot reloads
  const globalWithMongo = global as typeof globalThis & {
    _mongoClientPromise?: Promise<MongoClient>;
  };

  if (!globalWithMongo._mongoClientPromise) {
    client = new MongoClient(uri, options);
    globalWithMongo._mongoClientPromise = client.connect();
  }
  clientPromise = globalWithMongo._mongoClientPromise;
} else {
  client = new MongoClient(uri, options);
  clientPromise = client.connect();
}

export default clientPromise;

Add your MongoDB URI to .env.local:

MONGODB_URI=mongodb://localhost:27017/your-db-name

Step 3 – Initialize Better Auth

Create an authentication configuration file. This is where you'll set up Better Auth with the MongoDB adapter and define your providers.

ts
// lib/auth.ts
import { BetterAuth } from "better-auth";
import { MongoDBAdapter } from "better-auth/adapters/mongodb";
import clientPromise from "./mongodb";

export const auth = new BetterAuth({
  database: new MongoDBAdapter(clientPromise, {
    // Optional: specify collections or indexes
  }),
  providers: [
    // Email / Password provider
    {
      type: "credentials",
      id: "email-password",
      name: "Email and Password",
      options: {
        credentials: {
          email: { label: "Email", type: "email" },
          password: { label: "Password", type: "password" },
        },
        authorize: async (credentials) => {
          // Your custom authentication logic
          // Better Auth will handle password hashing and verification
          // You only need to return the user (or null)
        },
      },
    },
    // Add OAuth providers as needed:
    // {
    //   type: "oauth",
    //   provider: "google",
    //   clientId: process.env.GOOGLE_CLIENT_ID!,
    //   clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    // },
  ],
  session: {
    strategy: "jwt", // or "database" – jwt is simpler for serverless
  },
});

Note: Better Auth currently provides a credentials provider for email/password, but you'll need to implement authorize yourself (using bcrypt or similar). The library is designed to be extended.

Step 4 – Create API Routes for Authentication

Better Auth works as a middleware inside Next.js API routes. Create a catch‑all route to handle sign‑in, sign‑up, sessions, etc.

ts
// app/api/auth/[...auth]/route.ts (App Router)
// or pages/api/auth/[...auth].ts (Pages Router)
import { auth } from "@/lib/auth";
import { NextRequest, NextResponse } from "next/server";

// For App Router
export async function handler(req: NextRequest) {
  return auth.handleRequest(req);
}

export const GET = handler;
export const POST = handler;
export const PUT = handler;
export const DELETE = handler;

If you're using the Pages Router, the pattern is similar but with NextApiRequest/NextApiResponse. The important thing is to pass all HTTP methods to auth.handleRequest.

Step 5 – Add Authentication Middleware (Optional)

To protect routes, you can create a middleware that checks the session before rendering pages.

ts
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { auth } from "@/lib/auth";

export async function middleware(request: NextRequest) {
  const session = await auth.api.getSession({ headers: request.headers });
  const { pathname } = request.nextUrl;

  // Define public routes that don't require authentication
  const publicPaths = ["/login", "/register", "/api/auth"];
  const isPublic = publicPaths.some((p) => pathname.startsWith(p));

  if (!isPublic && !session) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/((?!_next/static|favicon.ico).*)"],
};

Step 6 – Access Auth in Frontend Components

Better Auth provides a useSession hook (or you can call the API directly). Install the React package:

bash
npm install better-auth/react

Then in your components:

tsx
"use client";
import { useSession } from "better-auth/react";

export default function UserProfile() {
  const { data: session, status } = useSession();

  if (status === "loading") return <p>Loading...</p>;
  if (!session) return <p>You are not signed in.</p>;

  return <p>Welcome, {session.user.email}!</p>;
}

Deployment Considerations

  • Environment variables – Ensure MONGODB_URI is set in your deployment (Vercel, Netlify, etc.).
  • Connection pooling – MongoDB adapter in Better Auth handles connection pooling automatically via the shared client.
  • Cold starts – Serverless functions may experience a slight delay on first request while MongoDB connects. Use a warm‑up function or keep the connection alive if needed.
  • Security – Always use HTTPS in production and never expose your MongoDB URI client‑side.

Troubleshooting Common Issues

  • "MongoDB adapter not found" – Make sure you imported from "better-auth/adapters/mongodb" (not "mongodb" standalone).
  • Session not persisting – Check that your MONGODB_URI is correct and the database is reachable. Also verify that the session strategy matches your use case (JWT doesn't store sessions in DB).
  • CORS errors – If you're calling auth routes from a different origin, configure CORS on the API route or use server‑side requests.

Final Thoughts

Better Auth simplifies the authentication workflow in Next.js, and pairing it with MongoDB gives you a scalable, cost‑effective solution. The library is still evolving, but its modular design lets you keep your code clean and maintainable. Start with the credentials provider, add OAuth later, and extend as your app grows.

Have you tried Better Auth with another database? Share your experience in the comments below.

Comments