GuidesDatabase

MongoDB

Setup

  • Create a new project and deploy a cluster on MongoDB Atlas
  • Run a local database for your dev setup so you can work offline and it's faster. You can read more here.

  • In your project on MongoDB Altas, click [Network Access] then [+ Add IP Address]. Enter 0.0.0.0/0 in [Access List Entry]. This allows connections from your computer and your production deployment(s) (Vercel for instance).
  • If you haven't done it yet, rename .env.example to .env.local. Then add your connection string to MONGODB_URI in .env.local.

Mongoose (Optional)

Mongoose makes it easier to deal with MongoDB and has some cool features.

Models are defined in the folder /models. Add any new models there.

The plugin toJSON is added to all models to remove the _id and __v (easier on front-end). Also if you add private: true to any field it will be removed from the response. I.e. make email private so it's not sent to the front-end.

MongoServerSelectionError

Change the mongo.js file to:

mongo.js

1import { MongoClient } from "mongodb";
2
3// This lib is use just to connect to the database in next-auth.
4// We don't use it anywhere else in the API routes—we use mongoose.js instead (to be able to use models)
5// See /libs/next-auth.js file.
6
7const uri = process.env.MONGODB_URI;
8const options = {};
9
10let client;
11
12if (!uri) {
13  console.group("⚠️ MONGODB_URI missing from .env");
14  console.error(
15    "It's not mandatory but a database is required for Magic Links."
16  );
17  console.error(
18    "If you don't need it, remove the code from /libs/next-auth.js (see connectMongo())"
19  );
20  console.groupEnd();
21} else if (process.env.NODE_ENV === "development") {
22  if (!global._mongoClient) {
23    global._mongoClient = new MongoClient(uri, options);
24  }
25  client = global._mongoClient;
26} else {
27  client = new MongoClient(uri, options);
28}
29export default client;

Supabase

Setup

  • In Supabase SQL Editor, run this query to add a profiles table (an extension of the authenticated user to store data like Stripe customer_id, subscription access, etc...):
  • Supabase SQL Editor

    1create table public.profiles (
    2  id uuid not null references auth.users on delete cascade,
    3  customer_id text,
    4  price_id text,
    5  has_access boolean,
    6  email text,
    7
    8  primary key (id)
    9);
    10
    11alter table public.profiles enable row level security;
  • Go to the new profiles table and add 2 RLS policies:
    • Enable read access for authenticated users only
    • Enable insert access for authenticated users only
    Supabase RLS - Enable read access for authenticated users onlySupabase RLS - Enable insert access for authenticated users only
  • (Optional) If you want to collect leads with ButtonLead, create a new table called leads and add a RLS policy with insert access for anyone:
  • Supabase SQL Editor

    1create table public.leads (
    2  id uuid default gen_random_uuid(),
    3  email text,
    4  created_at timestamp with time zone default timezone('utc'::text, now()) not null,
    5
    6  primary key (id)
    7);
    8
    9alter table public.leads enable row level security;

Migrate to SSR (WIP)

  • Delete the /app/api/callback/route.js API endpoint and replace it with the /app/api/auth/confirm/route.js API endpoint.
  • /app/api/auth/confirm/route.js

    1import { cookies } from "next/headers";
    2import { NextResponse } from "next/server";
    3
    4import { createClient } from "@/libs/supabase-server";
    5import config from "@/config";
    6
    7export async function GET(request) {
    8	const { searchParams, origin } = new URL(request.url);
    9
    10	const code = searchParams.get("code");
    11	const token_hash = searchParams.get("token_hash");
    12	const type = searchParams.get("type");
    13
    14	try {
    15		const cookieStore = cookies();
    16		const supabase = createClient(cookieStore);
    17
    18		// For OAuth
    19		if (code) {
    20			const { error } = await supabase.auth.exchangeCodeForSession(code);
    21
    22			if (error) throw error;
    23
    24			return NextResponse.redirect(`${origin}${config.auth.callbackUrl}`);
    25		}
    26
    27		// For Magic Link
    28		if (token_hash && type) {
    29			console.log("type & token_hash");
    30
    31			const { error } = await supabase.auth.verifyOtp({
    32				type,
    33				token_hash,
    34			});
    35
    36			if (error) throw error;
    37
    38			return NextResponse.redirect(`${origin}${config.auth.callbackUrl}`);
    39		}
    40
    41		throw new Error(
    42			"Something went wrong while signing in! Please try again or contact support if the issue persists."
    43		);
    44	} catch (error) {
    45		console.log(error);
    46
    47		// Redirect back to login page with error message
    48		const redirectTo = request.nextUrl.clone();
    49		redirectTo.pathname = config.auth.loginUrl;
    50
    51		redirectTo.searchParams.delete("code");
    52		redirectTo.searchParams.delete("token_hash");
    53		redirectTo.searchParams.delete("type");
    54
    55		redirectTo.searchParams.set("error", error.message);
    56
    57		return NextResponse.redirect(redirectTo);
    58	}
    59}