Coder Social home page Coder Social logo

build-a-saas-with-next-js-supabase-and-stripe's Introduction

Course artwork

This repo accompanies this free egghead course.

๐Ÿ” About

For your frontend, this application will be using Next.js and Tailwind CSS. You will learn how to maneuver around an app and build a strong static pricing page.

For your backend AND authentication, we will make use of Supabase. Supabase is open source and is all of the backend services that you will need to build your site. It includes a dedicated and scalable Postgres database and user management with Row Level Security!

Lastly, for payments, you will be using Stripe. It will be an individual payment checkout system that will create and update users' subscriptions.

Jon will take you through all of this and more in just 1 hour and 10 minutes!

๐ŸŽ“ Instructor

Jon Meyers is a Software Engineer, Educator and Hip Hop Producer from Melbourne, Australia. He's passionate about web development and enabling others to build amazing things!

Jon's courses at egghead.

Enjoyed the course? Follow Jon on Twitter and subscribe to his YouTube channel.

๐Ÿ—บ Table of Contents

  1. Create a Supabase Project
  2. Create a Table in Supabase
  3. Create a Next.js App with Tailwind CSS
  4. Query Data From Supabase Using Next.js
  5. Use Next.js to Query a Single Record From Supabase
  6. Implement Third Party Authentication with GitHub in Next.js Using Supabase
  7. Add Relationships Between Tables in Supabase Using Foreign Keys
  8. Use Postgres Functions to Implement Database Logic with Supabase
  9. Use Supabase to Subscribe to Database Events with Postgres Triggers
  10. Create a Stripe Customer with Next.js API Routes
  11. Generate a Custom API Key to Secure an API Route in Next.js
  12. Automatically Create a Stripe Customer for Each User with Supabase Function Hooks
  13. Make User State Globally Accessible in Next.js with React Context and Providers
  14. Implement Authorization Using Row Level Security and Policies
  15. Implement Gated Content Using Row Level Security with Supabase
  16. Use Stripe.js to Query Product Data and Pre-Render with Next.js
  17. Create Shared Nav Bar in Next.js with _app.js
  18. Query Dynamic Supabase Data in Static Pages Using Next.js
  19. Pass Supabase Session Cookie to API Route to Identify User
  20. Charge Customer for Stripe Subscription in Next.js
  21. Subscribe to Stripe Webhooks Using Next.js API Routes
  22. Use the Supabase Service Key to Bypass Row Level Security
  23. Create a Client Page that Requires Authentication in Next.js Using getServerSideProps
  24. Allow Customer to Manage Their Subscription with Stripe Customer Portal
  25. Subscribe the UI to Database Changes with Supabase Real-Time
  26. Configure Stripe for Production and Deploy Next.js Application with Vercel

build-a-saas-with-next-js-supabase-and-stripe's People

Contributors

dijonmusters avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

build-a-saas-with-next-js-supabase-and-stripe's Issues

Invalid hook call in context/users.js

I was trying to follow this guide to make my supabase user globally available in my next.js app but when I attempt to login I'm getting the error "Error: Invalid hook call. Hooks can only be called inside of the body of a function component."

It highlights this line "export const useUser = () => useContext(Context);" as being the cause.

This seems like an issue that everyone who follows this series/tutorial would have as well, though that doesn't seem to be the case? Has something changed recently?

Egghead Tutorial: Updating `stripe_customer` not working with hook.

Hi, I'm doing the Egghead tutorial. This lesson add hook function to add stripe_customer to Profile.

When I remove row level security in Profile table, stripe_customer updates. But with it on, it doesn't. I added the 'read' row level security from prior lesson. Has something changed?

With row level security on

const status = await supabase
    .from('profile')
    .update({
      stripe_customer: stripeCustomer.id
    })
    .eq('id', req.body.record.id);

status is

status: 404
statusText: "Not Found"

When I turn off security it's status 200.

Is the egghead tutorial still current?

I'm trying to learn from this egghead tutoiral: https://egghead.io/lessons/supabase-query-data-from-supabase-using-next-js

I am not able to read from the supabase database as per lesson 4. I have changed the nextjs version in package.json from latest to v 13.0.2 . Whilst this clears the errors in the terminal and the console, I can only read an empty array when I console log the output of the lesson table.

Maybe this tutorial is no longer current. Is that the case? Do you know if there are any changes that can be made to continue trying to learn some principles from the tutorial - or is it too out of date to be a starting point to learn?

I have been trying to learn basics for 10+ years and am yet to find a current resource that I can understand (I need CS50, replit 100 days of code level dummies explanations). If this is out of date, I wont keep trying to figure out what's going wrong, but if there are small changes that can be made to make this useful - I very much like the tutor's style so far and would like to try and learn.

Session error

Hi. I'm unable to move past lesson #19. I followed along and adjusted the code in [priceId].js to pass the session token to supabase, but I'm still receiving the 500 type error: "cannot read property 'stripe_customer' of null"

I'm not sure how to proceed. Should the server side call be handled differently?

For context, my code was working fine up to this point. So I'm confident the issue can be narrowed down to how the session cookie is being dealt with between Next and Supabase.

User Authentication State doesn't match on Server and Client when Refresh Page

The user state doesn't match on the server and the client

When I logged in and refresh a page, I see the following error in my browser console

next-dev.js?3515:32 Warning: Text content did not match. Server: "Auth" Client: "Exit"

In my browser, my user state returns a boolean true that shows I'm authenticated; however, in my server, my user state returns a boolean false that shows I'm not authenticated

If I navigate via the client side without refreshing the page, the auth status works fine

Anyone knows why is my server and client authentication status different? I followed the example in the youtube video. Anything I missed out? Thanks

Full Repo

AuthContext.tsx

interface AuthUser extends User {
  is_subscribed: boolean;
  interval: string;
}
export interface IAuthContext {
  // setUser: Dispatch<SetStateAction<any>>;
  user: AuthUser | null;
  loginWithMagicLink: (email: string) => Promise<{ error: any | null }>;
  signOut: () => Promise<{ error: ApiError | null } | undefined>;
  isLoading: boolean;
}

export const AuthContext = createContext<IAuthContext>(null!);

interface IProps {
  supabaseClient: SupabaseClient;
}

const AuthProvider: FC<IProps> = ({ children }) => {
  const [user, setUser] = useState<AuthUser | null>(
    supabase.auth.user() as AuthUser
  );
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const { push } = useRouter();

  useEffect(() => {
    const getUserProfile = async () => {
      const sessionUser = supabase.auth.user();

      if (sessionUser) {
        const { data: profile } = await supabase
          .from("profile")
          .select("*")
          .eq("id", sessionUser.id)
          .single();

        setUser({
          ...sessionUser,
          ...profile,
        });

        setIsLoading(false);
      }
    };

    getUserProfile();

    supabase.auth.onAuthStateChange(() => {
      getUserProfile();
    });

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    try {
      axios.post(`/api/set-supabase-cookie`, {
        event: user ? "SIGNED_IN" : "SIGNED_OUT",
        session: supabase.auth.session(),
      });
    } catch (error) {
      console.log(error);
    }
  }, [user]);

  const loginWithMagicLink = async (email: string) => {
    const data = await supabase.auth.signIn({ email });
    return data;
  };

  const signOut = async () => {
    try {
      const data = await supabase.auth.signOut();
      setUser(null);
      push("/auth");
      return data;
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <AuthContext.Provider
      value={{
        loginWithMagicLink,
        user,
        signOut,
        isLoading,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error(`useUser must be used within a UserContextProvider.`);
  }
  return context;
};

export default AuthProvider;


Navbar.tsx

export const Navbar = ({ title = "L A B" }: Props) => {
  const { user, signOut } = useAuth();
  console.log("nav isAuthenticated", !!user);

  return (
    <div className="navbar bg-base-100 shadow-lg">
      <div className="flex-1">
        <Link href="/">
          <a className="btn btn-ghost normal-case text-xl">
            <span className="text-lg font-bold tracking-widest">{title}</span>
          </a>
        </Link>
      </div>

      <div className="flex-none">
        <ul className="menu menu-horizontal p-0">
          {links.map((l) => (
            <li key={l.title} className="hidden md:block">
              <Link href={l.url}>
                <a className="cursor-pointer">{l.title}</a>
              </Link>
            </li>
          ))}

          {!!user ? (
            <li className="hidden md:block">
              <a onClick={signOut}>Exit</a>
            </li>
          ) : (
            <li className="hidden md:block">
              <Link href="/auth">
                <a className="cursor-pointer">Auth</a>
              </Link>
            </li>
          )}
          <li tabIndex={0} className="md:hidden">
            <a>
              <svg
                xmlns="http://www.w3.org/2000/svg"
                fill="none"
                viewBox="0 0 24 24"
                className="inline-block w-5 h-5 stroke-current"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth="2"
                  d="M4 6h16M4 12h16M4 18h16"
                ></path>
              </svg>
            </a>

            <ul className="bg-base-100">
              {links.map((l) => (
                <li key={l.title}>
                  <Link href={l.url}>
                    <a
                      className="cursor-pointer tooltip tooltip-left"
                      data-tip={l.tip}
                    >
                      {l.icon}
                    </a>
                  </Link>
                </li>
              ))}
              {!!user ? (
                <li>
                  <a
                    onClick={signOut}
                    className="cursor-pointer tooltip tooltip-left"
                    data-tip="Exit"
                  >
                    <AiFillAliwangwang size={40} color="red" />
                  </a>
                </li>
              ) : (
                <li>
                  <Link href="/auth">
                    <a
                      className="cursor-pointer tooltip tooltip-left"
                      data-tip="Auth"
                    >
                      <AiFillAccountBook size={40} color="green" />
                    </a>
                  </Link>
                </li>
              )}
            </ul>
          </li>
        </ul>
        <ThemeChanger />
      </div>
    </div>
  );
};

fix pages/index lessons map

when cloning the project, in pages/index at line 10, lessons.map(.. should be optional since we don't have any lessons yes

Function hook not working: Unable to proceed because of [object Object] in params field

Hello,

Great tutorial so far. Just wanted to give a heads up on an error that is a blocker for this point in the tutorial.
On the Supabase backend when I try to add API_ROUTE_SECRET to the Http params field, the value field pre-populates with [object Object] and I'm not able to either override it or save the param to make the function hook work.

Here's a screen grab:
object_error

update for nextjs 13

Hi John,

Thank you for making this egghead course. I am a very slow learner.

I am stuck on lesson 4 in your tutorial. I think the issue might have something to do with a change in nextjs v 13. This stackoverflow post has a suggestion, which is to change the Home function into a const with an arrow in it. I tried it as follows but still can't get past this step.

import { supabase } from '../utils/supabase'

const Home = ({lessons}) => {
  
  return (
    <div className="flex min-h-screen flex-col items-center justify-center py-2">
      {lessons.map( lesson => (
        <p key={lesson.id}>{lesson.title}</p>
      ))}
    </div>
  )
}

export default Home;

export const getStaticProps = async () => {
  const { data: lessons } = await supabase.from('lesson').select('*')

  return { props: { lessons } }
}

My error message is in the browser is:

Error: Failed to fetch update manifest Internal Server Error
at http://localhost:3000/_next/static/chunks/webpack.js?ts=1668894571923:1188:37

My error message in the terminal is:

TypeError: Cannot read properties of null (reading 'length')
at eval (webpack-internal:///./node_modules/next/dist/client/dev/error-overlay/hot-dev-client.js:262:55)

Do you have any ideas on how to get past this error?

Thank you

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.