Let’s Build Leerob’s Portfolio Guestbook Feature with Supabase and Next Auth.

Bawantha Rathnayaka
6 min readMar 2, 2024

Lee Robinson’s portfolio is truly impressive. While browsing through the website, I came across a fantastic feature called the Guestbook. Essentially, this feature allows website visitors to add comments. Today, I’m excited to embark on implementing this feature using Next.js, Supabase, and NextAuth.

1. Create Next JS App

First create next js app using this command. refer this link for more information

npx create-next-app@latest

2. Install Required packages

  • For the database we are using supabase. Supabase is an open source Firebase alternative. You need to install supabase client using this command
npm install @supabase/supabase-js
npm install next-auth 

3. Implementing Authentication

To add NextAuth to your project create a file named route.ts in the /app/api/auth/[...nextauth]/ address, if this folders do not exist just create it, This file contains the dynamic route handler for NextAuth.js which will also contain all of our global NextAuth.js configurations too.

In /app/api/auth/[...nextauth]/route.ts route put the below codes:

import { NextAuthOptions } from 'next-auth';
import GithubProvider from 'next-auth/providers/github';
import NextAuth from 'next-auth';

const GITHUB_ID = "<your github id>";
const GITHUB_SECRET = "<your github Secret ID>";
const NEXTAUTH_SECRET = "<next auth secret>"

export const authOptions: NextAuthOptions = {
secret: NEXTAUTH_SECRET,

providers: [
GithubProvider({
clientId: GITHUB_ID,
clientSecret: GITHUB_SECRET,
}),
],
};

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

As you can see I added just one Github provider. It’s a good practice to keep all your third-party secret and public keys and credentials in .env file too.

How to get Github Credentials

For Github, Go to your Github profile settings > Developer settings > OAuth Apps and create new OAuth App.

add these configuration in your application settings

Then get your client and secret ID and save them in your .env file.

Use authentication session in your client side pages or components

To retrieving session data in a client side components or pages you need to add NextAuth’s SessionProvider to your layout, then you can access session data by using useSession() hook in all your client pages. I did another mistake here and used SessionProvider directly in my root layout, and faced Error: React Context is unavailable in Server Components as layout.tsx is rendering in server but SessionProvider is a client side, to fix this we can create a client component provider.tsx with use client directive:

"use client";

import { SessionProvider } from "next-auth/react";

export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>;
}

Then import it to our layout.tsx and wrap our children with our Providers component:

import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { Providers } from "./providers";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<Providers>
<body className={inter.className}>{children}</body>
</Providers>
</html>
);
}

Now we can access session in all our client components or pages:

"use client";

import { useSession, signIn, signOut } from "next-auth/react";

export default function Home() {
const { data: session }: any = useSession<any>();

return (
<div className=" container mx-auto max-w-5xl mt-10">
<h1 className=" text-lg">Guestbook Feature</h1>

{session && (
<div>
<p>Signed in as {session.user && session.user.name}</p>
<a className=" cursor-pointer" onClick={() => signOut()}>
Sign out by link
</a>
</div>
)}

{!session && (
<button
onClick={() => signIn("github")}
className=" bg-gray-900 flex flex-row justify-center gap-5 rounded-xl text-white px-5 py-3"
>
Sign in with Github
</button>
)}
</div>
);
}

4. Implement Add Message feature

Setting up supabase project

To get started with Supabase, visit app.supabase.io. You can use your GitHub account to create a new account with Supabase.

From there, click on the New Project button to create a new project. You will need to select an organization. Once selected, the following page will appear

Give your project a name and password, then select the region that is closest to you.

It will take a couple of minutes to build the database and API. Once completed, go to Settings -> API and copy the URL and Public key for later

Create Database

Click on the table editor from the left panel to create a new table.

Then click on the “Create new table” button, fill in table details, and click Save to create your table. When saving a table is completed, you can preview it from the table editor page.

This is my table structure.

“This is my page.tsx file, which serves as an integral part of my application. Within this file, I've primarily focused on implementing two key functions: getMessages and addMessage.”

"use client";
import React, { useState, useEffect } from "react";
import { useSession, signIn, signOut } from "next-auth/react";
import { createClient } from "@supabase/supabase-js";

export default function Home() {
const { data: session } = useSession<any>();
const [loading, setLoading] = useState<boolean>(false);
const [message, setMessage] = useState<string>("");
const [userMsg, setUserMsg] = useState<string>([]);

const SUPABASE_URL = "<your supabse url>";
const SUPABASE_ANON_KEY = "<your supabse anon key>";

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);

const getGuestBookData = async () => {
try {
setLoading(true);
const { data, error } = await supabase
.from("guestbook")
.select()
.order("created_at", { ascending: false });

setMessage(data);
setLoading(false);
} catch (error) {
console.log(error);
}
};

useEffect(() => {
getGuestBookData();
}, []);

const handleInput = (e: any) => {
setUserMsg(e.target.value);
};

const createMessage = async (e: any) => {
e.preventDefault();
try {
const { data, error }: any = await supabase.from("guestbook").insert([
{
message: userMsg,
username: session?.user?.name,
},
]);
setUserMsg("");
getGuestBookData();
} catch (error) {
console.log(error);
}
};

return (
<div className=" container mx-auto max-w-5xl mt-10">
<h1 className=" text-lg">Guestbook Feature</h1>

{session && (
<>
<div className="flex flex-col gap-5">
<h4 className="text-lg">sign in as {session?.user?.name}</h4>
<div>
<form onSubmit={createMessage} className="flex flex-row gap-3">
<input
type="text"
value={userMsg}
onChange={handleInput}
className="border-2 p-2 rounded-md w-full"
placeholder="Enter Your Message"
/>
<button
type="submit"
className=" bg-black px-5 rounded-md text-white w-[250px]"
>
Submit
</button>
</form>
</div>

<button
onClick={() => signOut()}
className="bg-black text-white flex flex-row gap-3 items-center p-3 rounded-md w-[250px] justify-center"
>
Sign out
</button>
</div>
<div className="mt-10 flex flex-col gap-3">
{loading && <h1>Loading ..</h1>}
{message &&
message.map((item: any, index: number) => (
<div
key={index}
className="flex flex-row gap-5 bg-secondary p-5 rounded-xl justify-between mt-5"
>
<div className="left flex flex-row gap-5">
<p style={{ color: "#525252" }}>{item.username} : </p>
<p style={{ color: "#525252" }}>{item.message}</p>
</div>

<p style={{ color: "#525252" }}>{item.created_at}</p>
</div>
))}
</div>
</>
)}

{!session && (
<button
onClick={() => signIn("github")}
className=" bg-gray-900 flex flex-row justify-center gap-5 rounded-xl text-white px-5 py-3"
>
Sign in with Github
</button>
)}
</div>
);
}

this is the final output

Full Source Code on Github : https://github.com/Bawanthathilan/guestbook-nextjs

--

--

Bawantha Rathnayaka

I write about my experiences as a Software Engineer and the tech I use daily. portfolio - https://bawanthathilan.vercel.app/