DEV Community

Isaac Dyor
Isaac Dyor

Posted on • Edited on • Originally published at isaac.dyor.com

Setting up Next.js project with Prisma, Supabase, and Shadcn.

Setting up Next.js

First run the following command to initialize the next js project with supabase, typescript, and tailwind: npx create-next-app@latest. Select all of the default options:

Setting up Prisma

Run the following command to install prisma:
npm install prisma --save-dev

Once prisma is installed run the following command to initialize the schema file and the .env file:
npx prisma init

There should now be a .env file. You should add your database_url to connect prisma to your database. Should look like this:

// .env
DATABASE_URL=url
Enter fullscreen mode Exit fullscreen mode

Inside your schema.prisma you should add your model, i am just using some random model for now:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model Post {
  id        String     @default(cuid()) @id
  title     String
  content   String?
  published Boolean @default(false)
  author    User?   @relation(fields: [authorId], references: [id])
  authorId  String?
}

model User {
  id            String       @default(cuid()) @id
  name          String?
  email         String?   @unique
  createdAt     DateTime  @default(now()) @map(name: "created_at")
  updatedAt     DateTime  @updatedAt @map(name: "updated_at")
  posts         Post[]
  @@map(name: "users")
}
Enter fullscreen mode Exit fullscreen mode

Now you can run the following command to sync your database with your schema:
npx prisma db push

In order to access prisma on the client side you need to install prisma client. You can do so by running the following command:
npm install @prisma/client

Your client must be in sync with your schema as well and you can do this by running the following command:
npx prisma generate

When you run npx prisma db push the generate command is automatically called.

In order to access the prisma client you need to create an instance of it, so create a new folder in the src directory called lib, and add a new file called prisma.ts to it.

// prisma.ts

import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export default prisma;
Enter fullscreen mode Exit fullscreen mode

Now you can import the same instance of Prisma in any file.

Setting up Shadcn

First run the following command to start setting up shadcn:
npx shadcn-ui@latest init

I selected the following options:
typescript: yes
style: default
base color: slate
global css: src/app/globals.css
css variables: yes
tailwind config: tailwind.config.ts
components: @/components (default)
utils: @/lib/utils (default)
react server components: yes
write to components.json: yes

Next run the following command to set up next themes:
npm install next-themes

Then add a file called theme-provider.tsx to your components library and add the following code:

// theme-provider.tsx

"use client"

import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
Enter fullscreen mode Exit fullscreen mode

Once you have your provider setup, you want to add it to the layout.tsx so it is implemented on the entire app. Wrap the {children} with the Theme provider like this:

// layout.tsx
  return (
    <html lang="en" suppressHydrationWarning>
      <body className={inter.className}>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
Enter fullscreen mode Exit fullscreen mode

Now go to the shadcn themes page. And select the theme you want to use and press copy code. Then add that copied code to your globals.css so it looks like this:

// globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 224 71.4% 4.1%;
    --card: 0 0% 100%;
    --card-foreground: 224 71.4% 4.1%;
    --popover: 0 0% 100%;
    --popover-foreground: 224 71.4% 4.1%;
    --primary: 262.1 83.3% 57.8%;
    --primary-foreground: 210 20% 98%;
    --secondary: 220 14.3% 95.9%;
    --secondary-foreground: 220.9 39.3% 11%;
    --muted: 220 14.3% 95.9%;
    --muted-foreground: 220 8.9% 46.1%;
    --accent: 220 14.3% 95.9%;
    --accent-foreground: 220.9 39.3% 11%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 210 20% 98%;
    --border: 220 13% 91%;
    --input: 220 13% 91%;
    --ring: 262.1 83.3% 57.8%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 224 71.4% 4.1%;
    --foreground: 210 20% 98%;
    --card: 224 71.4% 4.1%;
    --card-foreground: 210 20% 98%;
    --popover: 224 71.4% 4.1%;
    --popover-foreground: 210 20% 98%;
    --primary: 263.4 70% 50.4%;
    --primary-foreground: 210 20% 98%;
    --secondary: 215 27.9% 16.9%;
    --secondary-foreground: 210 20% 98%;
    --muted: 215 27.9% 16.9%;
    --muted-foreground: 217.9 10.6% 64.9%;
    --accent: 215 27.9% 16.9%;
    --accent-foreground: 210 20% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 210 20% 98%;
    --border: 215 27.9% 16.9%;
    --input: 215 27.9% 16.9%;
    --ring: 263.4 70% 50.4%;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now you should be able to use shadcn components and themes in your project.

Setting up Supabase

The first step is to create a new supabase project. Next, install the next.js auth helpers library:
npm install @supabase/auth-helpers-nextjs @supabase/supabase-js

Now you must add your supabase url and your anon key to your .env file. Your .env file should now look like this:

// .env
DATABASE_URL=url
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key
Enter fullscreen mode Exit fullscreen mode

We are going to use the supabase cli to generate types based on our schema. Install the cli with the following command:
npm install supabase --save-dev

In order to login to supabase run npx supabase login and it will automatically log you in.

Now we can generate our types by running the following command:
npx supabase gen types typescript --project-id YOUR_PROJECT_ID > src/lib/database.types.ts which should add a new file in your lib folder with the types based on your schema.

Now create a middleware.ts file in the root of your project and add the following code:

import { createMiddlewareClient } from "@supabase/auth-helpers-nextjs";
import { NextResponse } from "next/server";

import type { NextRequest } from "next/server";
import type { Database } from "@/lib/database.types";

export async function middleware(req: NextRequest) {
  const res = NextResponse.next();
  const supabase = createMiddlewareClient<Database>({ req, res });
  await supabase.auth.getSession();
  return res;
}

Enter fullscreen mode Exit fullscreen mode

Now create a new folder in the app directory called auth, then another folder within auth called callback, and finally a file called route.ts. Add the following code in that file:

// app/auth/callback/route.ts
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

import type { NextRequest } from "next/server";
import type { Database } from "@/lib/database.types";

export async function GET(request: NextRequest) {
  const requestUrl = new URL(request.url);
  const code = requestUrl.searchParams.get("code");

  if (code) {
    const cookieStore = cookies();
    const supabase = createRouteHandlerClient<Database>({
      cookies: () => cookieStore,
    });
    await supabase.auth.exchangeCodeForSession(code);
  }

  // URL to redirect to after sign in process completes
  return NextResponse.redirect(requestUrl.origin);
}

Enter fullscreen mode Exit fullscreen mode

With that setup we can create a login page. In the app directory create a new folder called login with a page.tsx.

// app/login/page.tsx
"use client";

import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";
import { useRouter } from "next/navigation";
import { useState } from "react";

import type { Database } from "@/lib/database.types";

export default function Login() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const router = useRouter();
  const supabase = createClientComponentClient<Database>();

  const handleSignUp = async () => {
    await supabase.auth.signUp({
      email,
      password,
      options: {
        emailRedirectTo: `${location.origin}/auth/callback`,
      },
    });
    router.refresh();
  };

  const handleSignIn = async () => {
    await supabase.auth.signInWithPassword({
      email,
      password,
    });
    router.refresh();
  };

  const handleSignOut = async () => {
    await supabase.auth.signOut();
    router.refresh();
  };

  return (
    <>
      <input
        name="email"
        onChange={(e) => setEmail(e.target.value)}
        value={email}
      />
      <input
        type="password"
        name="password"
        onChange={(e) => setPassword(e.target.value)}
        value={password}
      />
      <button onClick={handleSignUp}>Sign up</button>
      <button onClick={handleSignIn}>Sign in</button>
      <button onClick={handleSignOut}>Sign out</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now create a new folder within the auth directory called sign-up, and a route.ts within that file. Add the following code:

// app/auth/sign-up/route.ts
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

import type { Database } from "@/lib/database.types";

export async function POST(request: Request) {
  const requestUrl = new URL(request.url);
  const formData = await request.formData();
  const email = String(formData.get("email"));
  const password = String(formData.get("password"));
  const cookieStore = cookies();
  const supabase = createRouteHandlerClient<Database>({
    cookies: () => cookieStore,
  });

  await supabase.auth.signUp({
    email,
    password,
    options: {
      emailRedirectTo: `${requestUrl.origin}/auth/callback`,
    },
  });

  return NextResponse.redirect(requestUrl.origin, {
    status: 301,
  });
}
Enter fullscreen mode Exit fullscreen mode

Create another folder in the same location called login.

// app/auth/login/route.ts
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

import type { Database } from "@/lib/database.types";

export async function POST(request: Request) {
  const requestUrl = new URL(request.url);
  const formData = await request.formData();
  const email = String(formData.get("email"));
  const password = String(formData.get("password"));
  const cookieStore = cookies();
  const supabase = createRouteHandlerClient<Database>({
    cookies: () => cookieStore,
  });

  await supabase.auth.signInWithPassword({
    email,
    password,
  });

  return NextResponse.redirect(requestUrl.origin, {
    status: 301,
  });
}
Enter fullscreen mode Exit fullscreen mode

And finally add a logout route in the same place.

// app/auth/logout/route.ts
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'

import type { Database } from '@/lib/database.types'

export async function POST(request: Request) {
  const requestUrl = new URL(request.url)
  const cookieStore = cookies()
  const supabase = createRouteHandlerClient<Database>({ cookies: () => cookieStore })

  await supabase.auth.signOut()

  return NextResponse.redirect(`${requestUrl.origin}/login`, {
    status: 301,
  })
}
Enter fullscreen mode Exit fullscreen mode

Now there should be basic login logout sign up functionality when you navigate to localhost http://localhost:3000/login.

Now we have some basic boilerplate for a next js app with prisma shadcn and supabase auth setup.

Top comments (6)

Collapse
 
webdesignersusa profile image
WebDesignersUSA

Do we really need Prisma when we are working on Supabase+Next.js project? Are there any additional benefits over using Supabase javascript libraries?

Collapse
 
isaacdyor profile image
Isaac Dyor

No you don't need Prisma. I like how they handle migrations so instead of going into supabase and adding all of the columns and relationships yourself you can just make a schema.prisma which will do it for you.

This is just a personal preference and you can definitely do it without Prisma.

Collapse
 
rodolfo_raquion_782d7f35d profile image
Rodolfo Raquion

Will migrations and rollbacks still work normally?

Collapse
 
thedommyllama profile image
Dommy

I came here to ask this question too . Not out of hateerness this question is eating me alive

Collapse
 
awalias profile image
awalias

Great post, Isaac!

Collapse
 
appletreetim profile image
Smart Dev

Could you provide source code