Code Monkey home page Code Monkey logo

build-a-twitter-clone-with-the-next.js-app-router-and-supabase's Introduction

Build a Twitter clone with the Next.js App Router and Supabase

This repo accompanies this free egghead course.

๐Ÿ” About

In this course, we build a Twitter clone from scratch, using the Next.js App Router and Supabase. We dive deep on the Next.js App Router, learning about:

  • Client Components
  • Server Components
  • Route Handlers
  • Server Actions
  • Middleware
  • Implementing Optimistic UI

On the Supabase side we look at:

  • Configuring Supabase Auth to use cookies
  • Using Row Level Security (RLS) policies to implement Authorization
  • Querying data across multiple tables
  • Introspecting PostgreSQL schema to generate TypeScript definitions with the Supabase CLI
  • Subscribing to realtime database changes

This course is a deep dive into modern web development and I'm very excited to see what you're going to build on the other side! ๐Ÿš€

๐ŸŽ“ 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! He is currently working as a Developer Advocate at Supabase, showing just how awesome (and not that scary ๐Ÿ‘ป) databases can be!

Jon's courses at egghead.

Follow Jon Meyers on Twitter and subscribe to his YouTube channel.

๐Ÿ—บ Table of Contents

  1. Start a new Supabase project
  2. Create a Next.js app with the create-next-app CLI
  3. Query Supabase data from Next.js Server Components
  4. Create an OAuth app with GitHub
  5. Authenticate users with GitHub OAuth using Supabase and Next.js Client Components
  6. Refresh session cookie for Next.js Server Components with Middleware
  7. Restrict access to authenticated users with RLS policies
  8. Dynamically render UI based on user session with SSR in Next.js Client Components
  9. Implement Protected Routes for authenticated users with Supabase Auth
  10. Generate TypeScript definitions from PostgreSQL schema with Supabase CLI
  11. Setup a Foreign Key relationship between PostgreSQL tables
  12. Automatically generate a profile for every user with PostgreSQL Function Triggers
  13. Run authenticated Server-side mutations with Next.js Server Actions
  14. Create a PostgreSQL join table in Supabase Studio
  15. Implement dynamic buttons with Next.js Client Components
  16. Declare global union types with Typescript
  17. Implement Optimistic UI with the Next.js useTransition hook
  18. Dynamically update UI with Database changes using Supabase Realtime
  19. Style a Twitter clone with Tailwind CSS
  20. Deploy Next.js App Router project to production with Vercel

build-a-twitter-clone-with-the-next.js-app-router-and-supabase'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

Watchers

 avatar  avatar

build-a-twitter-clone-with-the-next.js-app-router-and-supabase's Issues

Chapter 13: form submission is not working

I am trying to follow your tutorial and am getting stuck at the new-tweet form step.

I have the same form as you. I have experimental config enabled.

I fill in the form and nothing happens (not even console.log). I have also tried the submit to supabase steps and nothing happens. No errors in the console when I press enter - just nothing.

Is it possible that the action that uses the server needs to be defined in a separate file (with the use server command enabled) and then the form itself needs to be specified as a 'use client' component?

Refetch of new tweet requires refreshing the page

Noticed on video 13 at 4mins 12seconds when you create a new tweet you do not perform a refresh, but the new data is present. Looking at the NextJS Docs looks like a call to revalidatePath is required.

https://github.com/dijonmusters/build-a-twitter-clone-with-the-next.js-app-router-and-supabase/blob/941ae910701d32eebd2e3e3e77ed525dfd22ca28/13-run-authenticated-server-side-mutations-with-next.js-server-actions/app/new-tweet.tsx#L14C6-L14C6

I imported revalidatePath:
import { revalidatePath } from "next/cache"

and called: revalidatePath("/") after inserting a tweet and works as expected.

Thanks for your effort on the course, it's great!

Check if the user has liked a tweet should be server side

In Session 15 (Implement dynamic buttons with Next.js Client Components), the handleLike function is executed client side. This should be server side, because it also includes the check if the user has liked a tweet, and based on that, the tweet is liked or unliked.

In the developer tools, it is relatively easy to catch a request that likes a tweet (inserts a new row into the likes table), and run that multiple times via curl on the terminal, so there can be an infinite number of likes by the same user.

How would I fix that?

CH12: can't recreate user by logging in

hi,

in CH12, once I created the function, the trigger, and deleted my users (and tweets), I can't recreate any user by logging in.
when I click login, the cookie is created, but it stays on login page.

my function:

begin
  insert into public.profiles(id, name, username, avatar_url)
  values(
    new.id,
    new.raw_user_meta_data->>'name',
    new.raw_user_meta_data->>'user_name',
    new.raw_user_meta_data->>'avatar_url'
  );
  return new;
end;

i activated the corresponding trigger as well.

Invalid avatar_url being put into the public.profiles table

Hey there !

Following this tutorial I've noticed something at the point where I was styling it to show the avatars and so on.

The "trigger" to automatically create new public.profile for every new auth.users is defined like so:

create function public.create_profile_for_user()
returns trigger
language plpgsql
security definer set search_path = public
as $$
begin
  insert into public.profiles (id, name, username, avatar_url)
  values (
    new.id,
    new.raw_user_meta_data->'name',
    new.raw_user_meta_data->'user_name',
    new.raw_user_meta_data->'avatar_url'
  );
  return new;
end;
$$;

When later on, in the app, we use the "avatar_url" in two different ways, the first one, is inside NewTweet component, to which we pass the user coming from our session then we do user.user_metadata.avatar_url and this display the user avatar just fine.

But we also show the avatars on the side of each tweet, but there, the source of the avatar_url is no longer the session but the profile table.

And in that case, next/image complain that the image url is not a valid one, because the actual content in the profile.avatar_url is "https://githubcontent/avatar/..." (notice the extra quotes).

This is because, we are using the "json representation" of the "avatar_url" value in our trigger instead of the actual string value.

Changing the trigger function in favor of this fixed the issue:

create function public.create_profile_for_user()
returns trigger
language plpgsql
security definer set search_path = public
as $$
begin
  insert into public.profiles (id, name, username, avatar_url)
  values (
    new.id,
    -- We want to access the value with ->> operator
    new.raw_user_meta_data->>'name',
    new.raw_user_meta_data->>'user_name',
    new.raw_user_meta_data->>'avatar_url'
  );
  return new;
end;
$$;

I've opened a PR to fix the concerned README.md. Let me know if I can be of any other help.

Github login stopped working

While working on "12 Automatically generate profile for ...", I noticed that github login has stopped working.
I can see the session cookie in chrome devtools, but Home shows login button as "session" variable is null.

Edit: It was a configuration error on my side.

Restricting users with RLS Policy, not working

Hello, hope your doing well. I've been following along the tutorial.
On this video, 07-restrict-access-to-authenticated-users-with-rls-policies
Line by line i've replicated what you have but the tweets are only showed when the policy is for public, the moment i turn on authenticated, nothing shows.

i've even cloned this repo, pasted my supabase credentials, same problem.Even when i'm logged in.
Might it be because of the major update the has been made recently given this tutorial was made over a month ago??

Help would really come a long way.

how to reset input field?

this is using a server action to submit a tweet. how would you clear the input field after posting a tweet ?

error when add supabase-function in CH12

when create function, sql code block suggested on lecture is following

begin
  insert into public.profiles(id, name, username, avatar_url)
  values(
    new.id,
    new.raw_user_meta_data->>'name',
    new.raw_user_meta_data->>'user_name',
    new.raw_user_meta_data->>'avatar_url'
  );
  return new;
end;

But in my user data(raw_user_meta_data), there is no name.

{
  "iss": "https://api.github.com",
  "sub": "59201980",
  "email": "[email protected]",
  "user_name": "nakzyu",
  "avatar_url": "https://avatars.githubusercontent.com/u/59201980?v=4",
  "provider_id": "59201980",
  "email_verified": true,
  "preferred_username": "nakzyu"
}

So I could not insert data to profiles table through the function, cuz it failed. I 'm able to trigger the function after change the code like following

begin
  insert into public.profiles(id, name, username, avatar_url)
  values(
    new.id,
    new.raw_user_meta_data->>'user_name',
    new.raw_user_meta_data->>'user_name',
    new.raw_user_meta_data->>'avatar_url'
  );
  return new;
end;

Anyway, I'm very appreciate for your works. thank you very much!!!

CH17: useOptimistic hook not working as expected

Hi, I've been following this course up to Chapter 17, which covers "Implementing an Optimistic UI with the React useOptimistic Hook in Next.js." I've run into an issue where, after triggering addOptimisticTweet (from the useOptimistic hook), and before the backend call is completed, the Tweets component renders twice unexpectedly. The first render correctly updates the like count, e.g., from 0 to 1. However, the second render, which shouldn't happen, resets the like count from 1 back to 0, resulting the useOptimistic hook ineffective.

Issue:
image
Untitled_ Feb 18, 2024 2_38 PM (1)

Tweets.tsx

export default function Tweets({ tweets, user }: { tweets: TweetWithAuthor[]; user: User }) {
  const router = useRouter()
  const supabase = createClientComponentClient<Database>();
  const [optimisticTweets, addOptimisticTweet] = useOptimistic<TweetWithAuthor[], TweetWithAuthor>(
    tweets,
    (currOptimisticTweets, newTweet) => {
      const newOptimisticTweets = [...currOptimisticTweets];
      const index = newOptimisticTweets.findIndex((t) => t.id === newTweet.id);
      newOptimisticTweets[index] = newTweet;
      return newOptimisticTweets;
    }
  );

  useEffect(() => {
    const channel = supabase.channel('realtime tweets').on('postgres_changes', {
      event: '*',
      schema: 'public',
      table: 'tweets'
    }, (payload) => {
      router.refresh()
    }).subscribe(); 

    return () => {
      channel.unsubscribe();
    }
  }, [supabase, router]);

  console.log(JSON.parse(JSON.stringify(optimisticTweets[0])), 'render');

  return optimisticTweets.map((tweet) => (
    <div key={tweet.id} className='m-8'>
      <p>{tweet.author.username}</p>
      <p>{tweet.title}</p>
      <LikeButton tweet={tweet} addOptimisticTweet={addOptimisticTweet} user={user} />
    </div>
  ));
}

LikeButton.tsx

export default function LikeButton({tweet, addOptimisticTweet, user}: { tweet: TweetWithAuthor, addOptimisticTweet: (newTweet: TweetWithAuthor) => void, user: User }) {
  const router = useRouter();

  const handleClick = async () => {
    const supabase = createClientComponentClient<Database>();
    // const { data: { user } } = await supabase.auth.getUser();
    if (!user) return;
    if (tweet.user_has_liked_tweet) {
      startTransition(() => {
        addOptimisticTweet({
          ...tweet,
          likes: tweet.likes - 1,
          user_has_liked_tweet: false,
        })
      })
      
      await supabase.from('likes').delete().match({'tweet_id': tweet.id, 'user_id': user.id });
      router.refresh(); 
    } else {
      startTransition(() => {
        addOptimisticTweet({
          ...tweet,
          likes: tweet.likes + 1,
          user_has_liked_tweet: true,
        })
      })
      await supabase.from('likes').insert({ tweet_id: tweet.id, user_id: user.id });
      router.refresh();
    }
  }

  return (
    <div>
      <button onClick={handleClick}>{tweet.likes} likes</button>
    </div>
  )
}

Here's my full code: https://github.com/cherishh/clone-twitter
One note, though: I'm using a traditional username/password authentication method instead of OAuth. However, I don't believe this is relevant to the issue at hand.

Any assistance would be appreciated.

Error creating user

Hi,

Just wanted to thank you for a good tutorial.
But I want to feedback that github name is optional and this will make the creation of profile through trigger crash.

Br

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.