Setting Up Better Auth with MongoDB in Your Next.js Application
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:
npm install better-auth mongodbOptionally, 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.
// 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-nameStep 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.
// 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.
// 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.
// 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:
npm install better-auth/reactThen in your components:
"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_URIis 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_URIis 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
Related posts
10 Beginner-Friendly JavaScript Problems Solved: Step-by-Step Code Examples
Master JavaScript basics by working through ten beginner-friendly problems with clear, step-by-step code examples—perfect for building confidence with loops, conditionals, arrays, and strings.
Jun 17, 2026 · 6 min read
Server Components vs Client Components: when to actually use each
Server Components let you render static content and fetch data server-side with zero JavaScript sent to the client, while Client Components handle interactivity and browser APIs—but the key is to push the client boundary as low as possible, starting with a Server Component by default and only adding `'use client'` when truly necessary. This shift dramatically reduces JavaScript payloads and improves performance without sacrificing rich interactivity.
Jun 17, 2026 · 7 min read
Optimizing Your Next.js Stack: A Multi-Stage Dockerfile for Bun, Prisma, and Production-Ready Deployments
Learn how to build a lean, secure, and fast multi-stage Dockerfile for a Next.js application using Bun and Prisma, separating build and runtime stages to slash image size and boost deployment reliability.
Jun 17, 2026 · 6 min read