From Todo App to B2B SaaS with Next.js and Clerk

Rate this content

If you’re like me, you probably have a million side-project ideas, some that could even make you money as a micro SaaS, or could turn out to be the next billion dollar startup. But how do you know which ones? How do you go from an idea into a functioning product that can be put into the hands of paying customers without quitting your job and sinking all of your time and investment into it? How can your solo side-projects compete with applications built by enormous teams and large enterprise companies?

Building rich SaaS products comes with technical challenges like infrastructure, scaling, availability, security, and complicated subsystems like auth and payments. This is why it’s often the already established tech giants who can reasonably build and operate products like that. However, a new generation of devtools are enabling us developers to easily build complete solutions that take advantage of the best cloud infrastructure available, and offer an experience that allows you to rapidly iterate on your ideas for a low cost of $0. They take all the technical challenges of building and operating software products away from you so that you only have to spend your time building the features that your users want, giving you a reasonable chance to compete against the market by staying incredibly agile and responsive to the needs of users.

In this 3 hour workshop you will start with a simple task management application built with React and Next.js and turn it into a scalable and fully functioning SaaS product by integrating a scalable database (PlanetScale), multi-tenant authentication (Clerk), and subscription based payments (Stripe). You will also learn how the principles of agile software development and domain driven design can help you build products quickly and cost-efficiently, and compete with existing solutions.

Dev Agrawal
Dev Agrawal
153 min
09 Nov, 2023


Sign in or register to post your comment.

Video Summary and Transcription

The workshop focuses on building a basic Todo application and transforming it into a fully functioning SaaS product using modern developer tools. Dev tools like Next.js, Vercel, Clerc, PlanetScale, and Stripe are used to simplify the development process. The workshop covers topics such as fixing issues, adding interactivity and database integration, implementing authentication and payment, customizing Clerk components, adding premium payment plans with Stripe integration, and implementing multi-tenancy features. Participants are encouraged to provide feedback and share their projects built with Clerk.

Available in Español

1. Introduction to the Workshop

Short description:

Thank you all for being here. The workshop is called From a TodoApp to a B2B SaaS Application. We're going to start with a very basic Next.js app that is a Todo application. And by the end of this workshop, we want to get to a fully functioning SaaS product that we can deploy and have users for. Throughout our time as software developers, we often have ideas or projects that face friction when trying to build a working piece of software. The adjacent concerns, such as deployment, databases, authentication, and payments, often get in the way. However, there is a new generation of developer tooling that is changing this. These tools take advantage of the cloud, are reliable and scalable, and optimize for developer experience.

Thank you all for being here. Let me quickly get my screen shared so that we can get this party started. The workshop is called From a TodoApp to a B2B SaaS Application. Oh, what's happening there? Cool. Let me make sure I have the chat here. Obviously there's a QR code here, but we can't really use QR codes. Not the best choice for over the wire or, like, on Zoom. Let me copy and paste this link for the workshop starter kit, or the workshop starter repo here in the chat.

So basically the idea of the workshop is that we're going to start with a very basic Next.js app that is a Todo application. It doesn't even work. It's a nonfunctioning Todo application. And by the end of this workshop, we want to get to a fully functioning SaaS product that we can deploy and we can have users for, even thousands or millions of users for. That's the idea that we want to get for today's workshop. Again, it's a really hands on workshop. I highly encourage that you guys follow along. And the best way to follow along is through the starter repo. And before the starter repo, make sure you have these things installed. Obviously, if you've done any sort of JavaScript React development before, you probably already have Git and node.js installed. But what would be really useful is to have Stripe CLI installed. I'm also going, I can quickly show what the repo looks like. Looks something like this. Let me actually check if you can see this. Yeah. So, this is the GitHub repo that you should be able to see from Todo app to B2B SaaS. And this shows you how to clone it, CD into it, and then. So, I'm specifically just going to be using BUN sometimes. It's just a replacement for npm. So, whenever you see something like BUN install, or if you see me typing BUN dev or BUN run a script, just we can just replace that with npm. I might actually just do it with npm. And really just make sure that you can go over to the services that we are going to be using throughout this workshop, and you can make an account there. And the most important part is that make sure you have the Stripe CLI installed, because we're going to be using the Stripe CLI a bit throughout, once or twice throughout this workshop. Now before we really get into the hands-on part, and at this point I'm going to switch slides. So, just make sure that you have the repo opened up, you have everything that you need, or you can start installing and setting up the repo on the side. While I quickly give a little introduction about myself. I am dev. That's my name. I am a dev as well. I have been writing full stack JavaScript applications for about seven years now, and currently I work as a developer advocate at Clerc, which is a sponsor of React Summit, and Clerc is a user management solution for React, as we are going to get into soon in this workshop. And before we really get into, like, the hands-on part, I want to get the point of what we are trying to achieve today across. So have you ever, as a software developer, ever have those moments when someone approaches with ideas of a new application? Or, like, let's say, hey, Kanban boards really suck. I want something simpler. Or I want a simpler way to do project management. Or if you have your own ideas of what a better travel planning application could look like, or you are already on your way to even building a startup out of your idea, or maybe even you've hired a team and secured a bit of funding, and well on your way to changing the world with the best task management application the world has seen, or the next generation decentralized social media platform, or fresh out of San Francisco, this is how AI will change your life product. Basically, the idea is, have you ever had, like, a lot of these big application ideas that you wish would exist and that you kind of want to build? How many of these ideas go to production? What about a prototype? What about just a demo? Maybe something we hack together over the weekend, maybe something we get with friends for the weekend, or maybe we are going to hackathons, and there we are trying to build some stuff. So what I want you to, what I want everyone to kind of think about is, throughout our time as a software developer, how many times has that happened? When we have ideas, or we have, or maybe we even have projects, we have startups, but then there's this kind of inevitable friction that comes in whenever we are trying to push this forward and build a working piece of software out of it. Because as we move from the ideation phase on the left to a working software phase on the right, there's a lot of things like, okay, where do I deploy this? How do I deploy this? I need to get a database, do I need Docker? People are talking about Lambdas. Everyone's going back to Postgres. How do I add Authon to this? I just hacked a dashboard together. I don't want to build user profiles, organization invites, integrate with email services, make sure everything is secure. You're also probably going to be storing some sensitive data. And if you ever want to charge your users, how do you manage payments? Are you going to deal with credit card numbers now? We heard of something called Stripe. How does that even work? So these are kind of the ideas of what goes into this red section, which is the adjacent concerns. These are not really things that are related to the idea, the thing that you really want to build, but these things inevitably get in the way. And they start taking up more and more of our effort, of our engineering effort, and that's what really makes this going from idea to working applications, a painful process. And this is why you often see, or we often see, large teams, large companies that already have a bunch of resources, a bunch of engineering expert, a bunch of existing infrastructure. And because they have all of that, they can just spin up a new thing and start a new application, start a new project. And that's why we have big companies that constantly try new things. And they constantly stay in the market because they're constantly experimenting with new ideas with their existing infrastructure. This is also why it doesn't really work for small teams. Like if me and my friend were to get together and build something, realistically, what kind of an application can we really build before we now have to hire a bunch of more people to take care of these adjacent concerns so that we can turn it into a real business? And these are things that often prevent us from going all the way with our ideas and building real applications. But what I'm here to talk about today is how that has changed, or at least how that's changing. Because there is a new generation of developer tooling that is making this possible. These tools have some very unique characteristics. First of all, they take complete advantage of what the cloud has to offer, which means they are reliable, they are scalable, and they are cheap. So you are not worried about infrastructure. The second thing is that these tools optimize for developer experience, which means the goal is to give us as software developers powerful capabilities with actually good developer experience. So you have the full power and all of these complex infrastructure and systems prebuilt for you. But not only do they give you capabilities, you can just integrate with them seamlessly and just continue working on your core competency. Your core competency is the app that you're trying to build. It's your travel planning app, it's your AI app, it's your next-gen project management app.

2. Introduction to Dev Tools and Setup

Short description:

The new generation of dev tools takes away the effort and mental space dedicated to adjacent concerns, allowing teams to focus on their core competency. With these tools, small teams can build and operate applications serving thousands or millions of users. Excuses like lack of knowledge or scalability concerns are no longer valid. In this workshop, we will build a SaaS application using Next.js, Vercel, Clerc, PlanetScale, and Stripe. Let's get started by cloning the GitHub repository and installing the dependencies.

That's your core competency that you want to be building, not dealing with Kubernetes, these YAML files or webpack configurations or these REST APIs with horrible documentation. Spend 10 minutes on it and you're done, and you're never thinking about auth or vulnerabilities or compliance or bot attacks ever again. That's the kind of mindset that these tools can put us in. And I see people are still flowing in. Thank you everyone for attending. GitHub repo that we've shown here. It's also linked in the chat. You can sign up an account for these. And also just make sure you have node.js and you have the Stripe CLI installed.

All right, so what happens with the new generation of dev tools is they actually take away the effort and the mental space that we have to dedicate to these adjacent concerns. This means really the only thing you're ever thinking about is your core competency. It's your idea here, it's the stuff in the green. So what that means is that teams don't need to be huge. You don't need to have a lot of existing infrastructure to get started, build real applications with these set of tools. You can just have a small team of three to five people or even 10 people, 20 people that can build and operate and support a piece of software, an application that is potentially serving thousands or millions of users. And that's a completely realistic expectation to have with these tools because that's what a lot of people are doing today. Again, the mindset that this puts us in is that what I like to call it is no more excuses. Is that when I have a solid idea that I think is going to really work or that I want to experiment with, I don't have these excuses of, okay, I don't really know databases or I know this is never going to scale, I can never make it scale, or I can never make this secure enough. If this is an AI application that I'm building, maybe I'm too scared of bots. Maybe I'm too scared of bots coming, going into, abusing your services for free credits. But, these are not excuses that I have anymore because these tools, this completely take that away from me. So that's what we are going to be doing today. Let's build a SaaS application with these tools. We are going to use Next.js as our framework, we're going to use Vercel as the host. We're going to use Clerc for user management and auth. We're going to use PlanetScale for database and we're going to use Stripe for all our payments flow. And, really, that's all I have for slides today. And, at this point, we can just jump into the hands-on part.

All right, so what we have here is this is the GitHub repository. Okay, I have questions. Will I need a Stripe and PlanetScale account? You will definitely need a Stripe account. The PlanetScale step, if you want to, we are starting with a SQLite database, so if you want to stick to that for the duration of the workshop, you can do that. But once we get to the PlanetScale step, in this workshop, I will go over the step of actually creating the account. So you don't have to necessarily create all these accounts right now. Right now, what's more important is that you can clone the repository and then you can run NPM install and then all the repositories should be, all the dependencies should be available for you. You will need a Stripe account because we are integrating payments and you can have a PlanetScale account if you want to connect with live databases. All right, so while the dependencies are finished installing, let's build a little bit of familiarity with this codebase. So obviously everything important is inside our SRC folder. Post the Git link. Yes, give me one second. Here. So this is the link for the GitHub repository. Make sure you clone it and install dependencies. And our dependencies are installed. We can now run NPM run dev and we should see our to-do application here on the screen in a bit. But until then, let's look at our application. We have our SRC folder and I think my computer might be struggling for resources at this point, but we'll push through. And inside here, we have an index page and we have a dashboard page. So you can think of this index page as kind of being a landing page. You're getting this error, database URL is required. So what you can do in that case is you can go to your dot env file. You should have a database URL by default in your env. All right, so in your dot env file, we can actually get rid of these two, but you can have a database URL equals this string here. So file colon db.sqlite. So if you're getting the error that you're missing database URL, you can just make sure that this is added in your dot env file. I'm going to just paste that into this chat. Thank you for pointing that out. We basically just need to point the application towards the SQLite file that's going to be used as a database. So we're starting out with just a demo SQLite database here and you can ignore everything else in the environment for now. So now you should have the application running. Let me know if everyone's caught up to this point. And our index page is basically like, so if you go to your layout file here, your layout file in src app layout. Now this has a few things that, like in a sense they're not supposed to be there. So we can just quickly get rid of this. If you see this clerk-nextjs import, you can just get rid of that. You can get rid of clerk-provider and user-button from the section. We are going to add these back in later, but right now if you're just building our to-do app, we don't need that. But what we do need is if we go to our dashboard page, in our main page here, we have a link to go to dashboard. And if we click it, we should see, what is happening here? Give me one second. Oh, I am muted.

3. Fixing Issues and Adding a Todo Model

Short description:

To fix the issues, create a new branch called 'bare' and run 'npm install'. Then, run 'npm run dev' to see the basic todo application. We also want to know who uses Next.js with server components. Let's add a model for todos in the Prisma schema file.

You are correct. Give me one second. So I think what happened is, yes, so I think what happened was that this, the main branch was supposed to be a bare bones repo, but like a bare bones setup, but it ended up having a bunch of other stuff as well. So I'm going to actually create a new branch right now that doesn't have all of these issues and we can switch to that branch. So now if in your terminal screen, if you can just do git checkout origin slash bare, this should give you the new branch, which is now an actual bare bones setup, instead of having all of those, like giving us all of these issues. This is what the actual, the main branch was supposed to be. But looks like there were some issues there during preparation. I apologize for those. But now if I run npm install, we're going to try this again. And at this point, we shouldn't have any of the, anything extra. We should just have our todo application without anything fancy in there that might break stuff.

Okay, still breaking stuff. What is the problem here? Oh, there's no models here. That's fine. npm run db push. And this should probably, same header. Okay, that's fine. So now we have a much more bare bones setup. Again, all you have to do is git checkout origin slash bare. Also going to plug this into the chat here. And just make sure you're not, you don't have a lot of other changes here. middleware, I know you're removing the middleware works, but we are going to add the middleware back in at some point once we start adding authentication. So let's try this one more time. We have our localhost, we have our index route, which is basically our landing page. And then when we go to dashboard, we have a very bare bones UI here. So I'm going to give a couple extra minutes for everyone to catch up to this point. Just make sure you're on the bare branch. And you have installed the dependencies, and you can just run npm run dev. And here you should be able to see our basic to do application that does nothing. We just have an array, a hard coded array of todos here, initialize project and create to do app. Then we have our add to do component, which is our form here. And we're just looping over the array of todos and rendering each to do component. So very familiar, React stuff here. And just to check, if we get rid of this, if we have an empty array, we should see no todos found and we can verify that here. Cool. So I have the chat pulled up. Please just let me know on the chat. You have it forked already? No, you will still have bare. Oh, you forked it. You didn't clone it. Okay. If you forked it, you can still have I believe you can still access your branches. The command before npm run dev is just an npm install. So you don't need to run the db push. Npm install should automatically do that. So just run npm install and then you can run npm run dev. Nirav, if you're still getting invalid environment variables, once again, make sure that you just get rid of all the changes in your git and you're on the bare branch. If you're not on the bare branch, then you might still run into those issues. Another thing you can do, if you're using VS code, you can just click on this button here that says discard all changes. And you can discard all of them and then you can get checkout origin bare.

Okay. So I want to hear from while we have more people trying to set this up, I want to hear from everyone in the chat about how many people use Next.js or more specifically the app router with the server components, because we are also going to do a very quick run through of how we can get really, really productive with server components. Is Next.js with server components? Great. I use React. Next. Not really. Okay, perfect. So we seem to have a decent balance of non-Next and server component users. So now that we are in Next.js with Prisma and we are in a full stack environment, we also have a database with us with Prisma. So the first thing that we can do right now is add a model to our database for our to-dos. So let's do that. You can find the Prisma schema file here in Prisma and schema.prisma. And let's add a model here for a to-do. This is how we add models in Prisma. We are going to have an ID. Now Copilot is doing some work for me here. You can obviously type all of this out or if you also have Copilot, you have this suite autocomplete. So our to-dos have an ID, that's an integer, and we can tell Prisma that it's a primary key and give it a default value. We also have a title. That's a string.

4. Adding Interactivity and Database Integration

Short description:

We have added a new model to the database and can now fetch todos from it. However, the Add Todo component does not have the logic to add a todo to the database. We can pass the Add Todo function as a prop to the component and define it on the server side to act as another API endpoint. This function acts as a post request to the server and is automatically changed to a function that makes the post endpoint when passed into the React component. This allows us to add todos to the database.

We have a completed flag that can be boolean. That's going to be this checkbox here. And then we can just add, we can set our created ad and updated ad that we basically get for free from Prisma. We're not going to use these two fields throughout the workshop, so you can feel free to skip them, but I'm going to add them here.

Now I'm going to stop my server. I'm going to run npm run DB push. What this is going to do is compile the schema file, make changes to my database, my SQLite database, and then also have the TypeScript client generated. So I'm going to run that npm run DB push. And once this is complete, I can go back to npm run dev, and I have my server running. So I'm going to close the schema file here. I'm going to refresh this, and everything should still work as expected. We haven't really made any changes to the application. We've just added a new model to the database.

But now what we can do, is instead of this array, this page here is something that's called a server component. If you haven't used server components at any point, you can kind of think of them as a get endpoint on your server, which means this component actually runs only on the server and not on the client. This doesn't run on in the browser, which means we can just directly access our database here. We can say await DB.todo.findmany, and we can make this an async function. So now this function looks less like a React component and looks more like a request handler on an express endpoint or an API endpoint of some sort. Yeah. So you can think of this as the function that runs only on the server, which is why we can just async await our data from our database here, and we can render them as JSX. So now if I save it here, what's going to happen is you should see no todos found, because now we're actually fetching these todos from the database, but there's nothing there yet, so we see no todos found. And this is just a very basic idea of server components here. We're going to dig into this a little bit more. Now I won't really have scope or time to explain a lot of server components stuff throughout this workshop, so a lot of times, if you have questions, again, feel free to put it in chat, but I would really encourage kind of holding onto them, and maybe we can chat about them after the workshop, or you can visit the next JS documentation.

But now let's add some interactivity. Right now if we go here, this does not work. So if I click add, it doesn't actually do anything. If I click refresh, there is no todo, because we don't have the logic for adding a todo into the database. So let's go into our Add Todo component and see what's happening. So this is a form, as expected. We have an input field here for this input, and then obviously we have the button that we can click, or we can just hit enter. But on the on submit handler, we are not actually doing anything. We get the value out of the form data. We call this Add Todo function, but all it's doing is logging it into the console. So if I pop up in the console here, let's move it to the bottom, and can I add this todo. If I press enter, it's just being logged in the console. There's nothing else happening.

And this component here, because we have marked this file as use client, what this basically means is these React components are the React components that we know and love. We have hooks here. We have event handlers. So these are not server components, they're just regular React components. But this page file, this is a server component, which is why we can access the database inside it. So for that error, Nelly, make sure the int, the i is supposed to be capital, not lowercase. So all the types in a Prisma schema start with a capital letter. Cool. Okay, so this is a regular React component. How do we make it add something to a database, our Add Todo component? So let's first make sure that this Add Todo component comes as props. So let's define some props here. And let's say that Add Todo, the type of the Add Todo prop is that it takes in a string and it returns a promise of void. Which means this Add Todo function, instead of being defined in here, is now something we are passing into this Add Todo component. And we can just call it here.

Okay, so now the function doesn't need to be defined in a component, now it needs to be passed in. What's the big deal? This client component is being rendered by a server component, which means, again, now that we are in page, we are in full server mode. Which means we can give it this function. The Add Todo function that we just defined on the client, that this component needs this input, needs this prop. And we can define it on the server side to kind of act as another API endpoint. So here we can just say, await db.todo.create with this title. And we need to give it the completed default value of false. Okay, so what is going on here? I just talked about how server components can be thought of a get endpoint. So when you make a get request to a server, you get back a bunch of data, or you get back an HTML page. Similarly, a server component is what you get when you make a get request, you get back some UI. This function here, which is also called an action, you can think of it as a post request to the server. Now one thing that we need to mark here is, call it use server. This directive here, similar to this use client directive, is obviously to tell the bundler that this is a special function that needs to be treated especially. But it also tells us as a developer that this is a function that's basically a post endpoint on our server. We don't explicitly have to attach it to a URL and add the parsing request and do all the request response stuff. But that's the mental model. This function here is going to be called when someone makes a post request to our server. And then when this function is passed into our actual React component, it's automatically going to be changed to a function that makes the post endpoint instead of us actually sending this code down to the client. Again we can get into a lot more details about this, but I want to keep that out of the scope of the workshop.

5. Implementing Toggle and Delete Functions

Short description:

We can create a to do and revalidate the dashboard page to update the UI. The add to do function makes an HTTP request to the server, which creates the to do in the database. The toggle completed and delete to do functions can be used to update and delete to dos. The to do component requires the toggle completed and delete to do functions as props. The toggle completed function updates the completed value of a to do in the database. The delete to do function deletes a to do from the database. Questions are welcomed at this point.

And once the workshop is done I can talk about these things all day. But again the idea here is that we say use server and now we can just create a to do. And the final thing that we need to do here is we want to revalidate the path. And we are going to say dashboard. This basically tells NextJS that hey, take the dashboard page and basically refresh it or run the server component again, get all the latest data, and then update the UI. So let's just see what this does here. I want to come back here. I'll get rid of this. I'll refresh to make sure I have all the latest code. And can I add to dos. And if I press enter, I can add them. And if I refresh it, the to do is still there. Hello React Summit. So what's happening here is that we are submitting the form, which is calling this props.addToDo function. This add to do function will make in its TTP request to our server and then our server will figure out that it needs to call this specific function here. And then it will do that, which will create this to do in our database. And then we just tell NextJS to refetch all the data that's needed for a slash dashboard. So you serve in the middle of component is just proposed update delete. Correct. Yes. So you can have these use server functions or these actions in your server components or you can also import them into client components. That's a whole different topic. And you can use these to make changes to your data for actions, for mutations. So now that we have add to dos, we also want to implement the toggle and we want to implement the delete. So let's quickly also do those for these. First, we will need to go to our to do component because that's how we are rendering each of these individual rows. So let's go to to do component and see what's happening. Well, we have a checkbox and we have a next button and we have the title. Pretty straightforward. We also have these two functions that are doing nothing. So let's also make sure that they are coming from the props. So we're going to define a toggle completed function. That's going to take a to do object and it's going to return promise of void. You also have a delete to do that's going to take it to do object and return a promise of void. So these are functions that represent here, take this to do toggle, the completed value or take this to do and just delete it from the database. And we can get rid of the declaration. And here we can be since we are restructuring the props, we can add these two things in and then everything else, our UI basically remains the same. We now have these two props that the to do component requires, the toggle completed and the delete to do function. And if we go back to our page, we can do a very similar setup to pass those functions in. So let's come down here to our to do component we are passing in the to do object and the key. Right now, all we need to do is to pass in the toggle completed and the delete completed. Follow along in the speed we are jumping between TypeScript docs. OK, so I'll slow down right after this part here and I'll allow questions. So with the toggle completed function again, it just needs to be an async function that takes in a to do object. Whoopsie, give me a second. And because again, we want to treat this as a server endpoint. We can just say use server. And in here we can say a wait do that update. I'm actually going to turn copilot off for one second for a while that you're here. That's new. Never mind. We're going to call update. And we're going to say where the to do where the to do ID where the ID of the to do in the database is the same ID that we are passed in. We're going to set the completed to the opposite value of completed. So this is how Prisma works. If you use Prisma or any item, this should be relatively familiar. And then once again, we are just going to say that fetch all the latest data for dashboard. Similarly, in our delete to do, we can say use server and then we can just say a server. And then we can just say await do that delete. Where? Oops. Where the to do ID is this ID. I also need to make sure this is an async function like that, and we're closing it out and here we can revalidate that. Dashboard. Okay, so now I'm going to pause here and let any questions, sorry, let any questions flow in. We have our ad to do component and we gave we gave it this action that allows us to create a new to do on the database. And for a regular to do component, we gave it an action to toggle the completed value and to delete a to do. And does that work? Well, yes, it does. We can just check in here and this gets toggled. I can also just remove a to do here. I can also hello world.

6. Adding Authentication and Payment

Short description:

We are still using React for everything and have not introduced any new frameworks or providers. We have a full-stack to-do application that communicates with a database. Now we can move on to authentication and payment. Connecting to the database in a component is secure as it only runs on the server. If you haven't been able to follow along, you can catch up by checking out step one. We will now add more advanced features and discuss environment variables. We want to deploy and share the application, but also add authentication. We will use Clerc to quickly integrate authentication and create a private to-do list for each user.

I can just add new to dos here. And this is just and we are still using React for everything. We are still not using anything, any new framework or another or different provider here. Obviously we're using the Next.js app router, but we are not using any fancy cloud services under the hood. This is just a web server.

And, you know, if you guys have any questions up until this point, you can you can let me know. Or if at if throughout this, throughout this adding all of these functionalities, if you have been kind of left behind, you can you can stop your server and you can do git checkout. Step one, I believe. Step one, let me quickly double check that. Yes. So if you do git checkout step one, you'll basically you'll basically be caught up to where we are now or where we have reached now.

Do we need to pass completed false? We actually don't. That's a good point, because we have defined that completed has the false value, a false default value in the schema, we can actually get rid of this. Yes. So we've added all these actions in. We have a full stack to do application that communicates to a database. And now we can kind of move on to. We basically have gotten to a point from having basically nonfunctional user interface to a UI that talks all the way to a database, a complete full stack application. And we'll continue adding a lot more stuff throughout the workshop. So let's go ahead and jump right into it. So let's talk about authentication and payment. And we'll be talking about this a little bit more stuff throughout the workshop. Especially it's going to get real crazy when we add authentication and payment system here.

Is it secure to connect to database in component? Is it secure to connect to a database in in an express API handler, because that's basically what this is. Again, this component only runs on the server. It's never sent to the client. So if it's a express API or a FASTA API, then it is to a traditional react component that runs in the browser that talks to the VDOM or has this like basically it's running in the browser. But this component is not running in the browser. It's only running on the server, which is why you are you can connect to the database directly with Prisma.

All right, hope some of you guys are following along. And once again, if you if you haven't been able to follow along, you can just get check out step one. And everything that we have done up until this point is basically completed. So you have your database model, you have your you have all your actions already set up. So you basically have you can basically skip all the next chairs portion that we just went through. Because now we can kind of move on to the next step. I'm actually going to do that myself. Let's do get. Or nevermind. I'm not going to take that risk again. But if any of you are just jumping jumping onto the step one branch instead of trying to catch up to here and if you're running into issues, let me know so that we can we can all kind of like at least reach the step one point. And from there, we can build on more. Because this if this was a little confusing react next year stuff, it's over now. We don't have to we don't have to like deal with all of this for throughout the workshop because now we are going to move on to adding some more fancy stuff.

Environment variables database URL required. Make sure you have this database URL set up in your dot ENV file. Database URL equals file DB SQLite. This should be in your dot ENV file in your root.

Okay, so now let's say we have this application. Now let's say we have this application. We it talks all the way to the database. We want to kind of deploy it. We want to share it with friends. We want to ask people to try it out to use it. But maybe we want at least some layer of authentication behind it. We don't want in the entire world to just read and write from the same to do list. We want someone to be able to log in and have their own private to do list and so on. So let's see how how quickly we can actually do that with Clerc. So the first thing I'm going to do or first thing that you'll ask you all to do is to go to and sign up. So I'm currently already signed in. So it just says dashboard. But if I sign out of here and if I go back to You should see sign in and sign up so you can go here. You can click sign up and we're going to integrate Clerc in like basically no time and you're going to see how easy it really is to go from the point that where we are right now to this like really this future of we can deploy this and we can we can start getting real users for this. So I'm going to sign in with my Google account here. I'm going to click my Gmail account and that should show me that this dashboard. Now I already have like a bunch of applications here. But if you're creating a brand new account on Clerc, you'll basically be when you go towards this new application flow. So here we can give this application a name. Let's call it Projector for our purposes today because I really like task management and project management apps. And you can see that it's a very simple projector.

7. Setting Up Clerc Authentication

Short description:

You can choose your own name for the workshop and select the authentication factors for your users. Copy the API keys from the Clerc dashboard and add them to the.env.local file. Set up the Next.js middleware by copying the code into a middleware.ts file and installing the Clerc Next.js package. Adding this middleware adds a layer of authentication to your Next.js application, redirecting users to the sign-in page. You can define public routes to exclude them from the authentication layer.

You can see that I've chosen this name for this application. Now again, feel free to choose your own name for the workshop. I just like this name. So I'm going with Projector. And here you can select what kind of what authentication factors or methods you want to allow your users. We just select email and Google by default. You can turn them off, obviously, if you want. If you only want Google or if you only want email address and don't want to deal with OAuth. And we are going to just select both for now. But again, there's 19 more. Feel free to add as many of them as you want. You can enable all of them or you can enable none of them. You at least need one of them, right? And we're going to create application.

So once you have created our application on Clerc, you're going to be on this page where you can select what framework you're using. Next year is selected by default. And we can just copy our API keys from here. You'll get a publishable key and a secret key. Oh yeah, Sorry, I should have shared that. gtps So that's the link that you'll need to follow, And you can sign up for the account there. And I'm going to copy these variables from here. And these need to go in.env.local. So here's my.env.local file. I'm going to quickly stop sharing my screen and post those in there. Or actually, I think I can just do it live here. That shouldn't be too big of a problem. So I have a bunch of different keys here. I have next keys. You can ignore everything here for now. But I'm going to replace these with the keys that I just got from the Clerc dashboard. And those should go in the env.local. They can also go in the.env file. It doesn't really matter too much. It's mostly a matter of like personal preference and the team conventions that you're used to.

Now that we have our... Now that we have a Clerc instance, a Clerc application set up and connected with our Next.js application, or at least we have the environment variables in, I'm going to quickly hop over to the documentation for one very specific thing. And it's the Next.js middleware. Now Next.js is a little notorious for having like this sort of a complex pattern matching for its middleware. But basically I'm going to copy and paste this in the chat here. Really, all we need to do is to copy this piece of code. And then in our application, we can go in our src folder, create a file called middleware.ts, and just dump the code in here. That's it. And one more thing that you might need to do is make sure you have the Clerc Next.js package installed. It's possible that it's already installed because it was a part of the package JSON. But if it's not just go in your terminal and npm install at clerc slash Next.js. Once again, I'm going to copy this and put it in chat. So make sure you have run that. And once you've run that, you can do this in your middleware.

Okay, so what does adding this middleware do? Let's see. So I'm going to npm run dev once again. I'm going to go to my localhost. And I'm going to, with a localhost 3000 here. And let's see what we get here. We might actually get an error of some sort. There we go. So we just got redirected to a sign in screen. All we did was add this middleware in with a Next.js specific pattern matching. And we configured the environment variables. Not there. We configured our Clerc environment variables. We can ignore these for now. And basically, our entire Next.js application is now sitting behind a layer of authentication that's powered by this middleware. It doesn't matter what route I try to go to, I'm always just going to be thrown to the sign in page. Now that's the default behavior. And the reason that is is because this is basically one of the fastest ways to get set up with Auth in your application. We can add... Obviously, we don't want your entire application to be always behind the auth layer. So you can define public routes here.

8. Implementing Authentication and User Management

Short description:

The middleware allows us to define public routes that can be accessed even if the user is not signed in. This is a fast and convenient way to add authentication to any application. Clerk automatically keeps track of the user's intended destination before being redirected to the sign-in page, allowing for a seamless user experience. By wrapping our application in the Clerk provider and adding the user button component, we can easily integrate user management features. Clerk takes care of the best practices and user-friendly implementation of authentication, saving developers time and effort. Check your.env file to ensure the database URL is set up correctly, and make sure your data source is set to SQLite. The next step is to implement user management features and enhance the application's functionality.

So I can tell the middleware that the slash page, which is going to be our landing page, that's actually a public route. Which means even if the user is not signed in, we can allow them to visit the public route. So I'm going to save that. And now I'm going to come back here and type localhost 3000. This time, I won't get thrown to the sign in screen. This time I should see my landing page right here, which I do. But if I try to go to dashboard, if I click go to dashboard, I'm once again going to be kicked to the sign in screen. Again, this is legitimately one of the fastest ways to add authentication into any application. And if you really just want to focus on building your application and you don't actually care too much about like building the best authentication and sign in sign up flow, then this is an absolutely valid choice for a class of applications. Is this little application that you might've heard of, that's called ChatGPT. That actually uses this model. ChatGPT has no sign in UI or sign up UI off its own. It just redirect. If you go to ChatGPT or and if you hit sign in, it does basically this exact same thing where it's going to redirect you to a hosted sign in UI or hosted auth UI that's done by Auth0. And here we have that working with Clerk. That was just running within, we got it up and running within five minutes. Where was the middle file created? This was created in the SRC folder. So SRC and middleware TS, that's it. Cool, so now let's go in here and let's sign in with an account. We have Google enabled, so just sign in with any Google account that you want. And now you see that we actually came back to slash dashboard. And now we have access to the to-do app, which we did not have when we were signed out, we were just kicked to the sign in screen. And we also, at no point, we told, we didn't tell Clerk that, oh, when the user is signed in, send them to slash dashboard. We didn't do that anywhere. Clerk can automatically keep track of where you were trying to go before you were thrown to the sign in page. And then once you're done signing in, it knows what page to redirect you back to. These are little pieces of user experience that Clerk tries to encapsulate and implement all of these without the developer, without us having to think about it. So you can just integrate Auth and you don't have to think about what's the best, most secure or most user experience friendly or user friendly way to implement Auth, because all of that is taken care by Clerk. And let me show you a great example of that. So let's go into our src app layout.tsx file, where you'll see the hello there stranger. This is what we are rendering here. So this file is basically what we have for our top header here. And what we can do, first of all, we need to wrap this root layout file into our Clerk provider. So this is what you might've seen me remove initially from our branch. I'm getting this. Prisma client did not initialize yet, please run Prisma generate. Yes. So for that, you will have to quit and you just need to run npm run db push. Going to copy that. So whenever you make any changes to the Prisma schema, you have to run this command to make sure your database and your client are up to date. Going to back, we're going back to npm run dev. And so here I just wrap my application in a Clerk provider and then let's see some real magic of Clerk. We're going to add a component called user button. Just, we're just going to drop in here in a react application. And this is going to be imported from Clerk Next.js similar to Clerk provider. Now, once again, do you have a.env file in the root of your project where you have database URL set up? This is what we need. This is what it's looking for, the database URL right here. Now, we're going to copy that. So we just added this user button here. And let's refresh this page and see what it looks like now. Okay. You have that in there and you're still getting the header. Oh, wait. You probably, you might not be, Oh, if you skipped ahead to the step one branch, make sure that your data source DB says like SQLite and the URL is this. So you make sure your data source is back to SQLite. It shouldn't say MySQL, it should say SQLite. We haven't configured any MySQL databases yet. That fixed it. Awesome. Cool. The next step that we are trying to get here is we're trying to implement as much of the user. We're trying to add in as many of the user management features as we can so that we can get rid of. Rename env example. Yes. So that's basically the same thing, which is make sure that your.env file has this database URL. But you also need to make sure that if you are on a different branch, let's make sure your data source actually says SQLite and not MySQL. And you don't have anything else here in your data source definition. Let's see what the user button added here. I'm running this on Chrome, so you can probably see the influence right here. A lot of our, some of our UI is certainly inspired by Google because that's kind of the level of quality that we are trying to achieve and deliver to developers.

9. Clerk User Profile and Pre-built Functionality

Short description:

The user profile component in Clerk provides a highly detailed pre-built interface for managing user accounts. By simply adding the user button component, developers can give users access to their entire profile, including the ability to change their name, image, email addresses, connected accounts, passwords, and view active devices. Clerk's pre-built functionality allows developers to focus on their core competency, building the To-Do app.

But if I click here, you'll see that there's a manage account and a sign out. And manage account, this basically opens up one of our highly detailed pre-built components, which is this user profile. All I had to do as a developer was drop in this one component here, the user button. And the user now has access to their entire profile. They can change their name, change their image. They can change their email addresses. They can add more connected accounts. They can change their passwords from here. And I can even see what devices I have been logged in from. So right now, this is the only device that I've logged in from. But if I have signed into this account on this application from a bunch of different devices, I would see all of them here in my active devices, and I can remotely sign out of them at any point. I can also just delete my account from here. And basically, all of this functionality was available to the user without the developer having to do anything, without us as the developer do anything other than adding this one button here. And you'll see this to be a pattern whenever you're using Clerk, where a lot of the authentication functionality or interface that you need is just already there, fully prebuilt for you, so that you can move on to building your core competency, which in this case is the To-Do app. So let's get back to the To-Do app.

10. Implementing Authentication and Authorization

Short description:

We need to fix the issue of having the same To-Do list for all users and implement proper authentication and authorization in our applications. To do this, we will add a user ID field to the To-Do model in our schema. We will also update the Add To-Do function to include the user ID of the currently logged in user. This can be done using the Auth function from clerk-next.js. By implementing these changes, we will ensure that each user has their own unique To-Do list.

Right now, we have the exact same list of To-Do's that we are showing everyone. If I sign out of here, and if I sign in with a different account, let's say I'm going to sign in with my Clerk account this time. Now the profile photo for those two accounts is the same, so it might be a little confusing. But if now, now if I go to a dashboard, and if I say, hello from Clerk, it's the same To-Do list that we are all adding to. I can even toggle and delete To-Do's that I did not create, that were created with a different account. So let's fix that in. Let's make sure we have proper authentication and authorization in our applications.

So the way to do that is, let's first try to keep track of what user ID is creating a To-Do. So let's go back to our schema. We have our model To-Do here. It's all the same stuff that we just added to it, but we also now need to add a user ID. So this is the user that created this To-Do. This is going to be String, and we can make this optional for now. So we have added this one field to our schema, and now we are going to need to quit our server, npm run db push, so that we make sure the updates of the schema propagate through, and we can run npm run dev again.

So what that allowed us to do, so so far is our people caught up. We added our user button component here. I hope that you've been able to add the user button and middleware as well, and we added this user ID to the model. All right, so the next thing we are going to do here is that now that we can add a user ID when we create a To-Do, we can just specify that here. So we have an Add To-Do function where we're adding a new entry to a database, and now we need to somehow get the user, get the ID of the currently logged in user at this moment, right? So how do we do that? Well, this is a use server function, which basically means it's kind of like the post endpoint. And whenever we have an endpoint on the server, that endpoint is responsible for doing its authentication, figuring out what the user ID is of the currently logged in user. And the way we can do this in here, we just want to grab user ID out of something called Auth, and that's literally as easy as it gets. We import the Auth function from clerk-next.js. We're just going to call it, and it's going to give us the subject that has the user ID, and then we can just pass that in when we are creating our To-Do. That is pretty much it.

11. Customizing Clerk Components

Short description:

Clerk components are highly customizable, allowing you to modify elements, styling, variables, and more. You can use CSS, custom CSS, CSS and JS solutions, or tailwind classes. Clerk provides pre-built components and a hosted UI for quick setup, but also allows for extensive customization. Dedicated documentation is available for building your own Auth flow components using Clerk's hooks.

I have a question on how customizable are those Clerk components. They are very customizable. So thanks for asking this question because I forgot to bring it up. With these Clerk components, you can modify a lot of things. You can select like individual types of elements and give them custom styling. You can change any of these variables. You can have CSS, you can have a custom CSS or CSS and JS solutions, or you can use tailwind classes. So there's a lot of different ways to customize these components. And we also have dedicated documentation on how you can build your own Auth flow components out of our hooks. So, Clerk has the entire spectrum of options there. We have the pre-built components. We have the hosted UI. So if you want to just get up and running and you don't really, right now, you care much about the look and feel of your application. If you care more about the features, you can just get started with the account portal, the hosted UI and just build your features. But as soon as you have to start thinking about the overall look and feel of your application, you can start customizing or you can start creating your own.

12. Adding Premium Payment Plan

Short description:

We are now fully authenticated with Clerk, allowing each user to have their own list of to-do's. The APIs are secure, and users can only toggle and delete their own to-do's. The pricing for Clerk is generous, with a free tier that supports up to 5,000 monthly active users. We are now ready to move on to adding a premium payment plan for our app.

We are not passing completed faults anymore. Again, like I can pass that in. It's basically, like you can pass, whoops, you can pass that in if you want. It doesn't really do anything because the schema does the same exact thing. So for simplicity's sake, I'm not going to pass it in here, but I can pass the user ID in. So let's hover over the user ID and we can see that it can either be string or it can be null. A string means that, yes, the user is logged in. Here's the ID. Null basically means that the user is not logged in. So we should have a check here that says if user ID does not exist, we just want to, let's just throw an error. Throw new error must be logged in. Now I'm throwing an error here for the sake of simplicity. If you're like building, if you ideally want much better error handling, you want better UI feedback, but again, for now I'm just going to throw an error here and I'm going to trust that when you guys take this workshop to real applications, you can implement a bit of error handling logic. So let's come back here and say actually hi from Clerk. So now we're adding this after. So now that we have updated our logic to also keep track of user ID, we can say actually hi from Clerk. I might need to refresh this. Give me a second. All right. Let's get rid of both of these. And so now our to-do is completely empty. Hello from Clerk again. So this is a to-do that I've added with my Clerk account, So now let's sign out. And now I'm going to sign in with my Gmail account here. And the first thing I'm going to see is that same to-do. So the only thing we have done so far is that when we create a to-do, we keep track of who created it, which means now it's as simple as adding a where clause here which is just going to say user ID. I'm actually going to just copy these two files, lines from here, and I'm going to do this. So you'll see that the auth function works exactly the same both in a server component and in a server action. So you just call the auth function and you get the user ID of the user that's currently logged in that's trying to make this request or either get data from a server component or trigger the server action to change something on the database. So when we are rendering this page, we're just going to check the user ID and we are going to pass that in into our find many. So if I, whoops, if I save this, I should see this disappear and I can just say, hello from dev. And I am going to see this to-do, but now I'm no longer seeing the to-do that was created by the other user. Now for a second, let's just get comment this out and I can see this to-do again, but I can also toggle it. I shouldn't be able to toggle these to-dos because all of these endpoints should also be protected by auth. So let's also add this function here on both toggleCompleted and deleteToDo. And then we can simply add the user ID in the where clause for both of those mutations. So I'm going to save this. And if I come back here again, if I try to toggle this clerk to-do, I'm going to see an error and that actually won't happen. Once again, I have terrible error handling here, which means I have non-existent error handling here. But if we did have better error handling, this would result in some sort of a UI message that says you're not allowed to do that. So now our, all of our APIs are completely secure. We are only allowed to toggle the to-do's that we have created. And we are only allowed to delete the to-do's that we have created, like that. And finally, we can uncomment this part so that we are only allowed to see the to-do's that we have created. Hello, React Summit. Hello, the it nation. So I created these with my Gmail account, but the other to-do's are not visible to me. So just like that, just by catching the user ID of the auth function and making sure it's a part of our database queries, we now have a fully authenticated application where every user has their own list of to-do's that they can operate on. We no longer have a shared list. We have authentication with Clerk. So we have all the features. We have sign in with Google, sign in with email password. You can integrate with SAML if you want. So basically, you never have to think about how to implement user management and authentication on top of it. What is the pricing like on Clerk? That's a great question. Currently, you get about 5,000 users, 5,000 monthly active users on the free tier. That number is going to go up very soon. And once again, every service that we are talking about in this workshop, they have very generous free tiers because the entire idea is that there shouldn't really be any barrier between going from an idea to working software and with hundreds or maybe even thousands of users that are willing to pay for that software, which is why everything that we have picked today has a very generous free tier, including Clerk, including Vercel, including PlanetScale and everything else. But of course, if you want more information there, you can check on our website,, that has all the specific details on the pricing. But once again, they're going to change and go down very soon. So I hope everyone's caught up to this point. If not, just like before, I believe I also have another branch called Step 2, which should bring you up up until this point or maybe even Step 3. Give me one second and let me verify that. Step 3, okay. So again, if up until this point at any point you have been kind of unable to follow along and if you've kind of been lost at some point, feel free to check out Step 3 and that should kind of bring you to the exact same point where I am here right now. And I'll give everyone two more minutes and then we'll quickly move on to adding a premium payment plan for this. So what we are going to do next, right now, you can create as many to-dos on here as you want. Any user can sign into this app and create as many to-do's as they want. So the next thing we want to do is, let's say that my to-do application idea or whatever app idea that I'm working on gets popular and now I want to make sure I can charge users for it and so that I can build maybe a sustainable business model or a sustainable company on top of it.

13. Adding Free and Pro Tiers with Stripe Integration

Short description:

To implement a business model where free users can create only five to-dos and paid users can create 10 to-dos, we need to add a free tier and a pro tier to our to-do application. We'll use Stripe CLI to receive webhooks and handle payment information. Even if you can't install Stripe CLI, you can still follow along with the programming and setup. Let's now implement the logic for checking the user's payment subscription and the number of to-dos they can create. By calling the 'to-dos remaining' function, we can determine if the user has enough credits to add more to-dos. If not, an error will be thrown. The function also counts the number of to-dos created by the user and subtracts it from the maximum allowed to-dos in the free plan. This ensures that users are restricted to the specified limits. You can see the remaining to-dos displayed in the application. Try adding more to-dos and observe the error message when the limit is reached.

Or even if it's not sustainable, I just want to see if people are willing to pay for it right now so that I can go and request funding or like I can have a better idea of what am I supposed to be building. So the next step is going to be adding a free tier and a pro tier to our to-do application. So under the free tier, let's say you can only create five to-dos and if you want to go beyond that, you can get the pro tier and in the pro tier, you can create 10 to-dos. Obviously the numbers are very unrealistic here because the point is not to come up with a business model of this workshop. The point of this workshop is that once you have, once you can come up with a business model like that, how do you implement it? How do you implement something like only five to-dos for free users and 10 to-dos for paid users? So that's going to be a fun thing to implement. So I'm excited for this. I hope you all are excited for this and that's why I want to make sure that you all are caught up to this point.

Stripe CLI would be very helpful for receiving webhooks or basically for Stripe to tell us that hey, this person has finished their payment. Do whatever you want with that. Like adding additional processing that we want to do. We can take care of that after Stripe gives us that information. So for that, we are going to need Stripe CLI. You can literally just Google Stripe CLI here and you'll get instructions on how to install. If you have brew, that's very easy. You can just brew install Stripe CLI. If you're on a Linux environment, you can again just install it through CLI. And there's obviously instructions to do everything else here. But even if you're not able to install the Stripe CLI today, you can still follow along with all the programming that we are doing or all the setup that we are doing for this and that's still be very valuable for you, even if you're not able to fully test it in your local environment. Once again, the workshops are recorded. So I highly encourage you guys to go through it again if possible so that you can keep building these kinds of applications. Natasha, thank you very much for adding that link there. Computer version of Xcode is too old and computer is too old to upgrade. Sorry to hear that. So let's see what the Stripe integration looks like.

The very first thing I'm going to do is let's say we can add a function here that says function can add to-dos. And give me one second to quickly check something on the side. I am totally cheating by having a fully built version of this application already in my second monitor. All right, let's call this function to-dos remaining. Now this is a function that let's say we are going to call when we are creating a new to-do. So this is just going to return a promise. A promise of number. Which means in here we are going to const remaining equals await to-dos remaining. And we're basically going to say that if remaining is less than one or if it's equal to zero then we want to throw a new error. You have exhausted your limit or credits. Oopsie, credits. So basically every time you're adding a new to-do component here, we're going to check if you have some sort of credit or balance to be able to create more to-dos. And if you don't, we're going to throw an error. So this function is basically where our logic for checking the payment subscription or how many to-dos they already have and how many more they're allowed to create is going to go in. So let's find out who the current user is for now. So again, we're going to call auth and we're going to get the user ID. Then we are going to get to-dos count which is going to, I need to make sure this is an async function. Wow, my computer is slow today. So this is an async function to-dos remaining. We're going to say await And we are going to count all the to-dos that have been created by this user ID. And let's say that in the free plan, we only want people to create five to-dos. So we're going to return five minus to-dos count. So if I don't have any to-dos in the database, this is going to be zero and the output of this function is going to be five. I can even get rid of this annotation from here. So let's see how this works. I'm going to go in here, add one. Oh, I need to actually run this here, npm run dev. And now I can refresh this. There we go. Let's add a one, two, and a three. So now I have five to-dos here. If I try to add one more, we're going to receive an error. So we actually have no more ways. It's not possible to add a sixth to-do in this application. So now I've been restricted to only be able to create five to-dos. And we can make this information a bit more clear by adding some message here. So let's add a dev here. And in here, let's say await to-dos remaining, and we can say to-dos remaining. And now we should see zero to-dos remaining here. If I get rid of this, I see one to-do remaining. Or if I get rid of all of them, I now have five to-dos remaining. I'm just also going to add a little bit of padding to this so it doesn't feel as claustrophobic. There. A little bit more on the X-axis. Cool.

14. Setting Up Stripe Payment Integration

Short description:

To integrate payment with Stripe, we need to create a Stripe account and obtain API keys. The publishable key can be sent to the client, while the secret key must remain confidential. After setting up the environment variables, we create an upgrade button component and render it on the page. Clicking the button will indicate the user's intent to upgrade to the Pro plan and redirect them to the authentication UI.

Okay. I'm sorry. Things with padding just feel a little claustrophobic to me. How are you getting rid of them? Getting rid of what exactly? The to-dos? We're getting rid of them using this delete to-do function, our delete to-do action that we have already defined. So I can add one, two, three, four, and this will always show me how many to-dos do I have remaining because it's using the exact same function. So now what we need to do here is to somehow figure out if this user has a paid plan, if they have paid for the application so that they have a higher tier or they should have access to 10 to-dos instead of five.

Don't see a way to delete them in the UI. You just have to hover on them. So the X button, you have to hover on them for it to appear. It should be around here. That's the X button. Yeah. So how do we figure out if this user has paid or not? Well, we don't have any payment integration. Let's first build that in. So for this, we are going to go to I'm going to plug it in here. And if you already have an account, that's great. If not, now's your time to create one. So you can go in here. I already have one. I'm going to use my existing account credentials here. I'm going to continue. Once you're in there, you should see this dashboard here. So I'm in test mode here. If I want to access live data, if I want to actually put something in production and make money off of it, I'll need to activate my account and provide business details. So what's the name of my company? What's my bank account? Things like that. But for the purposes of this workshop, we want to continue to be in test mode. So here we can click, once you're here, once you see a screen like this, you can click on developers and we can go to API keys. So I'm going to pause here. I'm going to let everyone kind of catch up to the Stripe developers and API keys. Let me know where you guys are at in, have you already created your Stripe account or are you still creating one? Or do you have, basically do you have your access, your API keys from here? And you'll see that it's the same API setup. We have a publishable key and a secret key. These are the same two things that we had with Slurk because obviously there's a lot of influence there and you can copy these and put them in your env.local file. So just like the same way we have next public clerk publishable key, we can have a next public Stripe publishable key. And this is going to be our publishable key from the top here. Again, the publishable key means this key can be sent over to the client. It's fine if malicious actors get access to this key because they can't really do anything sensitive with it. That's why we have the second thing called secret key. This is the thing that should not leak. So we can add Stripe underscore secret underscore key. And I can reveal this here, copy it, and then paste it here. And I'll go back to home. I'll save my env file and I'll get back to my page. So that was again, a simple one-off setup for Stripe which is we just create an account in here. We grab the API keys and we put them in our environment file. How's everyone doing so far? Although if you are stepping over all the way to step three, just make sure you also have to go into a clerk to create an account. And from your clerk dashboard, you can create an application and you will have your API keys right here. Oh, and also like when we get home, we get some confetti saying that our application now has users.

The next thing we are going to do here, now that we have Stripe environment variables set up, is we're going to just, let's just create a button. I'm gonna have to keep that open, but along with one to-do remaining, maybe here we can show a button to upgrade. So let's go here, we're going to create a new file. API slash Stripe slash, let's say button or not button, button.tsx. Now this is going to go into our app folder. So SRC app, API, Stripe, button.tsx. The reason it's inside an API folder is because it's very soon, we are going to have to create a webhook endpoint, but we're going to see that very soon. But right now, all we want is an export function, let's call it upgrade button. And all this does is return a button that says upgrade. And then in our page.tsx, we are going to scroll down to the, await to-do remaining here. And we're just going to render our upgrade button like that. Let's save it and see how it appears. Do I have it running? I don't have it running. Keep forgetting the most important part, which is running the actual server. Let's come down here and refresh it. The button.tsx, yes. So we are literally just returning a button with text. We haven't added anything on top just yet. But the idea is that clicking this button, what that should do is, that should describe the intent that, oh, this user wants to upgrade their plan to the Pro plan, which means let's send them to some different page, similar to what we did with Auth. This upgrade button is basically, in a sense, the sign-in button. The sign-in button, what it does is that the user wants to sign into the application. So we're going to send them to this authentication UI.

15. Adding Upgrade Button and Stripe Integration

Short description:

Once users are signed in, they can upgrade their account by clicking the upgrade button and completing the payment process through Stripe. The createStripeSession function is used to create a checkout session with Stripe. The session includes information about the payment method, line items, currency, product data, recurring payment, and quantity. Metadata can also be added to store custom information, such as the user ID.

And once they're signed in, in the authentication UI, they can come back to the application. That's the idea with the upgrade button, is that once they click on the upgrade button, they're going to be sent off to this payment, this checkout UI that's managed by Stripe. And then once they have finished making their payment, entering their credit card details, they can come back to our application, and now they have an upgraded account. That's kind of the idea.

I'm going to add... Let's add just a bit of styling to our button, just to make sure it looks good. We're going to add... Now you can add it however you want. I just really like Tailwind, which is why I'm doing Tailwind-y things. And I'm going to give it a slight blue background. All right, maybe 20 is too much. Let's keep it at eight. Okay, so again, starting with this sign in, styling is not a priority for this workshop, which is why I'm just... I'm also not really a good designer. So the styles that I come up with are absolutely horrible, but the idea, you can get the idea here, is that when I have some to-dos remaining, I can see this upgrade button, and clicking on this should send me to the checkout page that we need. All right, so how do we make it happen?

The first thing that we need to do here is we need to create a Stripe session. Sessions is kind of Stripe's concept of, the user says that they want to buy something, let's create a checkout session for them. So we need a function. It's going to be an async function here, called createStripeSession. StripeSession. Now for this function, we are going to load in a few libraries, or really just one for now. So we're going to npm install Stripe. I put it in the chat here. So let's npm install the Stripe library and see what we have to do with the library itself. So this might take a while to load, so I'm just going to continue with the code here. I'm going to import capital S Stripe from the Stripe library that is yet to be installed. Then I'm going to create a lowercase Stripe. And this is, the Stripe happens to be a class. And what I need to pass in here, is process.env.stripe.secret.key. So this is the secret key that we got from the Stripe dashboard and we just put in our env local files. This is taking a while to install because my computer is very, very slow right now. I apologize for that. I hope you're having a better time than I am. And the other thing that we need to pass into this is an object that contains an API version. Now this API version just needs to be a date, which basically means that give me the version of API that was valid at this date. This is the day that I tested this at, so we can just do that. I'm going to copy this whole piece of code, I'm going to put it in the chat so that you can verify if you have all the correct stuff running. This is taking way too long. I'm actually just going to replace this with bun install Stripe because I know bun is faster, but please feel free to continue using NPM for this. All right. So now we have imported the Stripe class, we have instantiated it here and now what we can do in our createStripe session is const session, and this is going to come from stripe.checkout. Well, Copilot knows this better than I do apparently. And we just need to call this function here, stripe.checkout.sessions.create. Now we're going to need to pass in a bunch of functions here. You're also kind of going to see that configuring and getting started with Stripe is a little bit higher of a barrier than something like Clerk or something like Planetscale because I think that tools like Clerk, InverseL and Planetscale align much closer to developing DX or developer experience on top of APIs. Stripe, not so much, which is why configuring Stripe is a little bit of a pain. But luckily we have an hour to do it. The first thing that we are going to need to pass to the session is payment, what was payment method types. So we're going to say that we are going to accept payment by card. That's the first thing that we are going to tell the session. Also at this point, let's just add an exclamation mark here to tell TypeScript that yes, we have set up this environment variable, you don't have to worry about it. The next thing we need to tell Stripe are the line items. So what exactly is the user buying here by clicking this button? So we are going to say that the, so we can basically add more information about exactly the thing that the user is buying here, the user is purchasing in this checkout session. We're going to say currency is USD, United States dollars. The product data, we can give it a name. So this is the name of the product. We can call this the personal premium plan. Unit amount is how much this thing costs in terms of cents. So 100 means $1, 1000 is $10. So for again, for this example, we are going to go with $10, which means 1000 here. Then we need to specify if there's a recurring payment. So we're going to say that recurring interval is month. So Stripe automatically knows that this payment is something that needs to be, that they need to get from the credit card every single month until obviously the user cancels. And finally, what's the quantity? So they are buying one of these products. So this whole thing is our line item. We are saying that this is the, this is the price information or this is the information of the product, the line item that they're buying, and they're buying one of these. We need to give it a few more things at this point. So after the line items, we can give it some metadata. Now a metadata is kind of, the metadata for any session, the checkout session in this case, is kind of this place for us to store custom information. So when we are telling Stripe, hey, create this checkout session, metadata is something that we can use to tell Stripe, oh, this checkout session belongs to this user ID.

16. Creating Stripe Session and ENV Configuration

Short description:

When the user has finished paying, we can look at the metadata to determine the associated user and grant them additional credits. The user ID comes from the auth function. We also need to add a subscription mode to the session, specify success and cancel URLs, and return the session ID. The create Stripe session function grabs the user ID and creates the Stripe session. We check if the user ID is null or undefined and throw an error if they are not logged in. The session is set up for card payments in USD with a $10 recurring payment for the personal premium plan. The metadata includes the user ID. The ENV file contains the database URL, clerk publishable key, clerk secret key, Stripe publishable key, and Stripe secret key. Make sure to sign up for Clerk and Stripe accounts. Clerk is working on automating the integration process. Both Stripe and Clerk are required.

So when at the end, when the user has finished paying, and when Stripe comes back to us through the web hook and says that this checkout session has been completed, it has been paid for, we can just look at the metadata and figure out what user, what user was this checkout session associated with so that we can grant them additional credits. And that means the user ID is going to come from auth like most other server functions.

So there we go. We also need to add a mode of subscription to the session so that this, so that Stripe knows that this is a subscription-based payment. They can also tell the user that this is subscription. This is not a one-off product that they're buying. And finally, we need to give it success URL and let's give it a name, a string right now. And we need to give it cancel URL, which is also a string. So here for now, we can just specify it's ttt localhost 3000 slash dashboard. So once they have successfully finished their payment, we're going to say, come back to dashboard. Or if they cancel their payment, same thing, let's just come back to dashboard. Now, again, ideally, you don't wanna be hard coding these values in here. You wanna get them from request headers to figure out where the application is deployed. But for the sake of time being, I'm going to add localhost 3000 directly in here. So that is all that we need to do for creating a Stripe session, at least on the server. And what we can do now is return

So we have, quick recap, we have a create Stripe session function. On the server, we grab the user ID of the currently logged in user. And at this point we should also probably check if the user ID is null or if it's undefined. We wanna throw in error saying that you have to be logged in to be able to upgrade your account. Then we create the Stripe session. We say that we are accepting card payments. And in USDs, this is $10. It's called personal premium plan. It's a recurring payment for every month. You're buying one of these, and the metadata is the user ID. That's really all the most important stuff there is to know. How's everyone feeling up until this point? Is everyone caught up?

The ENV file, yes. The ENV file, I can get rid of both of these. It's just the database URL for the SQLite file that we have locally. And in the env.local file, I have the clerk publishable key and the clerk secret key. Whoops. I also have the Stripe publishable key and the Stripe secret key. Now, your keys are obviously supposed to come from your own instances of Clerk and Stripe. So make sure you are signed up on and you're signed up on and you need to make accounts on both of those places. Clerk is actively working on making this entire process much easier. So instead of having to manually integrate with Stripe and create these sessions and create webhook listeners, clerks should be able to automate almost all of that very soon. And that's a feature that we are actively working on. But for now, we are going to need both Stripe and Clerk. All right.

17. Wiring the Button and Redirecting to Checkout

Short description:

To wire this into the button, we make an API request to the server to create a new Stripe session. The session function acts as an API endpoint defined with 'use server'. We create a client component called upgrade button client and pass the function as a prop. The client component calls the function as an API endpoint. We install the Stripe.js library for client-side Stripe integration. The button component loads the client-side Stripe library using loadstripe. We check if the library is loaded and throw an error if not. We use Stripe client.redirectToCheckout to redirect the user to the checkout page with the session ID.

All right. So, are we good to move on to the next part where we actually wire this in to the button? That's the exciting part. So what happens on the button click or on the front end now is that when we click this button, we actually need to make an API request to our server to run this function, to create this new Stripe session, which means this Stripe session function is basically an API endpoint. And how do we define API endpoints? We say use server. And then we can now just pass this function in into whatever client components are going to call this. So I'm going to create an additional file here with an API slash Stripe. And I'm going to call this client.tsx. You can call this whatever you want. The name of the file is not important here. What's important is that we start this file with a use client. Now we can export a function. Let's call it client. Or let's call it upgrade button client. So this is the client where, this is the client corresponding version of our update button, or of our upgrade button that we have here. So we're going to put the same thing actually. We're going to say, just return the same button that we were returning. And from our server upgrade button function here, we're going to get rid of this. And we are going to render the client button that we just defined on the other side. Now, if I save everything, if I come back here and refresh, nothing should change because everything is still the same. Oh, I keep turning off the server for some reason. Okay. So the development server is up and running. And now we should once again, see our dashboard here. And the upgrade button should basically stay the same. The only thing we did was that we moved this button to a client component so that we can add additional, we can have like all the traditional React available to us, which means we have an onClick handler. And we have state, we have useEffect, so we can do dynamic UI and things like that. But really all we need this right now is to create that Stripe session and then we need to redirect the user to the Stripe page where they can finish making their payment. How do we do that? Well, we have a createStripeSession function here. This is something that we need to pass down to the upgrade button client. So we're going to say, createStripeSession equals createStripeSession. Everyone with me here so far? We're just taking this function that we had defined and we are passing it down to the client component so that the client can call this function as an API endpoint, as an RPC endpoint. And in our client function, now we can define props and we can say that we are expecting a createStripeSession. And this is not going to take any arguments and it's just going to return a string or a string within a promise. So in here, we can say const sessionId equals props.createStripeSession. We can put an await here, which means this function needs to be async. Let's save this. And let's see what's breaking our application right now. Let's refresh. Okay, there we go. It's just flaky. So now this upgrade button has an onClickHandler attached to it. And when we click this, it's going to make a request to our server, create the StripeSession and then get the session ID. But so what can we do with the session ID now? Well, this is a client component, which means this is running in the browser. And if we want to do Stripe stuff in the browser, we actually need another library to work with that. And for this part, we are going to install, that's npm install, add Stripe slash Stripe.js. Now the Stripe, again, the Stripe ecosystem or the Stripe integration story is a little confusing. There's a little more work to do here compared to something like Clerk, but it's honestly completely worth it. So we just installed, or did I install that library? I don't know if I did. Oh, Stripe.js. Okay, so I'm now going to install Stripe.js. Looks like it was almost already installed and I can just do npm run dev here. So now let's get back to our button component. We're going to import from this new library that we just installed. And we are going to import loadstripe. This is going to load the client-side Stripe library so that we can do stuff like, here, we have a new session. Redirect the user to the session so that they can finish their payment. That's really the biggest thing that we need to do at this point. So let's see how we can do that. We're going to say const stripe client equals await loadstripe. And this is going to ask for the publishable key that we have put. So we can say public Stripe publishable key. This should be the exact same thing as we put in our environment local, next public Stripe publishable key. You can just copy this from here and you can put this in here and you can put an exclamation mark to tell TypeScript that, yes, we have added this environment variable. Okay, so we have the session now and we have the Stripe client-side library. What can we do with these two things together? Well, first we need to check if this exists because we can see that Stripe client is either the type of this library or it's null. So we want to make sure that if the library has some trouble loading in, if Stripe client doesn't exist, then we want to throw an error saying that for some reason we failed to load the Stripe client. Maybe the publishable key that we have configured is not right. And now we can simply say, await Stripe client dot redirect to checkout. So we are asking Stripe to redirect the current user to a checkout page where they can put their credit card details. And then we are passing the session ID as a parameter because the session ID is actually what contains what is the user buying, how much does it cost, what are the payment methods, and all of that.

18. Integrating Stripe Webhooks

Short description:

When a user clicks the upgrade button, they go to the Stripe checkout page, make their payment, and then come back to our application. We need our application to know when they have finished a payment on the Stripe system, which is done through webhooks. In the same Stripe folder, we add a route.ts file that serves as a webhook endpoint. The code verifies the authenticity of the webhook and processes the event object. We focus on the checkout.session.completed event, which indicates that a payment has been made. We can extract the user ID from the session object's metadata.

Okay, so let's see if this works. That was a lot of code without a lot of testing. So let's come back here. Let's refresh this. And we're going to hit that upgrade button and see what it's doing. And hopefully everything is working fine. How's everyone feeling? Where is everyone right now? How caught up is everyone? Let's click on upgrade. And there we go. You see, subscribe to personal premium plan, $10 per month. It has my email and my credit card details already saved. Now, if you're here for the first time, let me actually, can I add a new payment method? Okay, so when you're in test mode in Stripe, you don't, obviously you don't need to fill in real credit card details here because we are in test mode. So all you have to do is to add a bunch of 42s to the card information, and then you can add, as long as the dates are valid, you can add whatever values you want to every other field, and you should still be able to finish payment. So I'm going to come down here and click subscribe. And it's going to mark this payment a success. Like there, there was a green tick, and now it redirected me back to the dashboard. So we added this one function here to create a session, and then we added this onclick handler here to redirect the person, redirect the user to the checkout page. And just like that, we have payments, we have payments basically integrated into our applications. It's not fully integrated yet because the application itself, we don't have any record of what payment plan did the user go on, that information is all with Stripe, but we are going to add a webhook handler to solve that problem very soon. You want to see the button TSX? Yes, please. So create Stripe session. I'm going to, once again, copy this into the chat. If you don't want to deal with copy-pasting or copying it from the screen, just feel free to copy this from the chat, and you can dig into this and really explore the structure maybe after the workshop. But how is everyone feeling so far? I hope everyone's, or at least most of you, are on track here so that we can move on to the most interesting stuff, which is where we integrate the payment with our authentication system. I think you're motivated to build a new side project. That is awesome. That is what I love to hear. Cool. So what's happening right now is when you click the upgrade button, when a user clicks the upgrade button, they go to the Stripe checkout page, they make their payment, and then they come back to our application. The problem is they make their payments with Stripe, not with our application. So we need some way for our application to know when they have finished a payment on the Stripe end, on the Stripe system. And this is done through something called webhooks. How many of you are familiar with webhooks? All we're going to do here is in the same Stripe folder, which just make sure it's inside src app api stripe, we're going to add a route.ts. Another concept but never implemented one. Well, today is the day that's going to change. Now this file specifically has to be route.ts because this is how Next.js interprets API routes, which means we can say export function post. And now this is a function that will be converted into a post endpoint on slash API slash Stripe. So this is a webhook endpoint is basically an API endpoint on your server that's going to be invoked by a separate backend, not a separate frontend or a separate user. It's going to be invoked by a backend. That's really the key distinction between webhooks and your API routes, your usual API routes. Now there's actually a lot of, unfortunately, there's a lot of code that goes into building this webhook endpoint and most of it is not really a concern for us. So I'm going to, once again, copy a chunk of code into the chat here and I'm going to copy that exact same, paste that exact same code inside the post. And the only thing I need is I need to specify that the parameter to this post function is this thing called request. And obviously this is going to be an async function and from our button.tsx, we can export Stripe from here because we're using the same server side library to parse the webhooks. And now we can import Stripe from the button. We can also import next response from here. And the final thing we need is the webhook secret. So another thing that for now, we're just going to define it in line here. Let's make it an empty function because once we run stripe-cli, that's what's going to give us our webhook secret. So what this chunk of code here is doing is basically verifying, it's the form of webhook authentication. It checks that whether the thing that's invoking your webhook, is that legitimately Stripe or is that a malicious actor pretending to be Stripe so that they can hack into your system and say that I'm going to grant my user unlimited credits for some reason. So that's the kind of things that this authentication system prevents against. But again, a lot of this is code that we don't have to dig into right now. What we really care about is this event object. This is really that the core of what the webhook is. It's an event. So there's something that happened within Stripe and the event object represents what just happened. And now we are in charge of processing this event. And a very simple way to check what happened is a switch statement. So we're going to say event.type and we are going to say that case checkout.session.completed. We're going to do something or in the default case we are going to say, whoops, Unhandled event type. Okay, so what's happening here is that the only event that right now we are worried about that we want to process is this event called checkout.session.completed. If it's any other event, we'll just log out of the console saying that, oh, we got an event that we don't care about. But if we got a checkout.session.completed event, now it's basically telling us that someone has made a payment towards our apps and now we need to process, we need to grant them additional credits or whatever we want to do. So when we get this event, we are also going to get the session object, the same session object that we created when the user first clicked the upgrade button. This is going to be an Now, once we're in here, we can grab the user ID out of session.metadata.userid. Now it doesn't really, oh, we need to add a question mark here to verify that user ID is an actual thing. But if you remember back to our button, we added the user ID in the metadata. And now in our Webbook handler, we can grab that user ID back from the metadata.

19. Storing Payment Plan in Clerk

Short description:

Because the Webbook is invoked by Stripe and not by a specific user, we need to get the user ID through the metadata. We store the user's payment plan in Clerk's public metadata by updating the field to 'paid' once the user has paid for the premium plan through Stripe. The session token, which is a JSON web token provided by Clerk, can be customized to include additional information. By adding a 'public metadata' field to the token, we can include the entire metadata field from Clerk in a custom field called 'public metadata'. We can then access this information in our code to determine the user's plan. The 'plan' field in the 'public metadata' can be either 'free', 'paid', or undefined depending on the value in the metadata. This customization of the session token allows us to add extra information to the token and retrieve it when needed.

Because the Webbook is invoked by Stripe and not by a specific user. We don't have access to our AuthUser functions here. So the only way to get the user ID is through the metadata. Once again, if, now, if the user ID doesn't exist, we're once again, just going to throw an error. Throw new error, invalid session object. So, because all the session creations, all the session requests have to go through here, we're basically kind of guaranteeing that they have to have a user ID. And if they don't have a user ID, they were created through some illegal ways that we don't care about. So we're just going to throw an error. But if there is a user ID, now, finally, we are at a point where we need to store some data somewhere that says that this user ID has paid for this plan. And actually one of the best places to store that is Clerk itself. Clerk has this thing called, so whenever you want to interact with the Clerk's APIs or Clerk's data, you can import Clerk Client from Next.js. What we can do here is say, clerk.users.updateUser. We are going to pass in the user ID here. Oh, actually what we need to do, my bad, we need to do updateUserMetadata. So the user metadata within Clerk is kind of this little mini database that we have for each user. This is where we can store any custom information about the user, anything important for authentication purposes. And in this, or authorization purposes, which in this case, we want to store what payment plan the user is currently on. So we have two things here, we have a private metadata and public metadata, we are going to use public metadata for this purposes. We're going to say that plan is, they are on a paid plan now. By default, they should be on the free plan, but now that they have paid for the premium plan through Stripe, we can update the field in the public metadata to be paid. Now this is a completely custom field. The only reason I have this autocomplete here is because we have, in our project, we have some custom definition of what user public metadata looks like. By default, the user public metadata looks like just a record, it doesn't have any concrete types, it doesn't have plan, free or paid, it doesn't have anything. This is something that we can define on top of Clerk we can tell Clerk in TypeScript that public metadata is supposed to be a free plan or a paid plan. And then we can set that here. Okay, so let's quickly recap what happened. When we send the user to Stripe to make their payment, we had, we created a session for them with custom metadata for the user ID. So according to Stripe, the custom metadata is the user ID. When we get a webhook endpoint back from Stripe saying that this payment was finished and now you can do additional processing, we grab that user ID, then we go to Clerk and we say that here's a user that just finished their payment and now the metadata within Clerk is the Stripe stuff. So you can see how the Stripe metadata and the Clerk metadata kind of interlink with each other to give us this experience. Okay. Now, one final thing that we need to do here is go back into our Clerk dashboard and let's go into the sessions tab here on the left. How's everyone following along so far? We have 50 more minutes. I think implementing this session plan will probably be the end of it, but we might have a little bit more time at the end. Something is wrong with your route.ts code. I'm going to copy this entire code or I can... Yeah, I'm going to copy this entire file and I'm going to put it... Oh, I cannot do that. So I'm going to copy one till 30 and I'm going to copy 31 till the end. So I've broken up this entire route.ts file in two different chunks. If you just want to copy paste and try to figure out where was the difference, we can work towards that. All right. So the final missing kind of like missing piece of this entire thing is our session token. Now, if you have been around like any sort of authentication implementations, if you have done your own authentication, like you've probably heard about JSON web tokens, right? These are tokens that represent the signed-in state of a user. And obviously, Clerc uses JSON web tokens. Every time you sign into an application with Clerc, you're going to receive a token that represents the signed-in session that you have. This feature here allows us to customize that token and add additional information into it. By default, we've already actually seen this token. Every time we call this auth function, we basically get the result that we get back is the session token itself. So which means the session token has the user ID and any other information accessible in here. And through this feature of Clerc, through customizing session tokens, we can add additional information that Clerc is now required to put into the session token. And let me quickly demonstrate what that means. So I'm going to add a field here called public metadata. And this is just going to be, I'm going to also find here user.publicmetadata and click it. And this is my new template for a custom session token. Which means on top of everything that Clerc is already adding to my token, it's now also going to add the entire metadata field into a custom field in the token called public metadata. It's going to get more clear once I use it. So let's save it here and let's come back here. Or let's go back to our page. Here we have our todo's remaining function, which we were using to check how many todo's the user can still create. Let's get some more information out of the auth object. Let's get session claims. What do we have in session claims? Const something or let's say session claims dot, we're going to see public metadata in here. And if we press dot, we're going to see plan here. So let's say plan equals session claims dot public metadata dot plan. And here you can see the plan is either of type free or paid or undefined. It's going to be undefined if the session claims just straight up does not exist or it can be free or paid depending on the value in my metadata. Now, once again, the reason we have this type completion here is because we have defined custom types here. So just like we extended clerk's types to say that the user public metadata has these fields now, we've also extended the JWT session claims. So basically the session claims that we get out of the token and we have mentioned that we have a public metadata field that has a plan that could be either free or paid.

20. Using Stripe CLI and Updating Clerk Metadata

Short description:

We can ignore the org stuff for now and operate on top of this. Let's console.log the plan and check the flow. If no plan is defined, the default is the free plan. Now, let's use the Stripe CLI. If you have it installed, follow the steps to establish the webhook listener. Add console logs to see what's happening. Click on upgrade, subscribe to the premium plan, and check the logs. We successfully updated the clerk metadata and injected it into the token. The UI reflects the change. We can hide the upgrade button if the user is already on a paid plan.

We can just, we can kind of ignore the org stuff for now. And let's just operate on top of this. And how it gets populated was the customized feature that we just used. Let's just console dot log plan here and see how this whole flow is working. Let's go back to our application. And if there is no plan defined, we are going to say that by default, you will have the free plan. Because if we have the paid plan, that this should be overridden. There we go. The plan is free. And I have one to do remaining because I'm on the free plan.

And now I'm on the side, I'm going to use the Stripe CLI. So I'm going, now if you don't have the Stripe CLI installed at this point, this is maybe this is the kind of kind of the point that you just can't really follow along anymore. But if you have the Stripe CLI installed, I highly encourage you to go through these steps. So we can type Stripe login, press enter in the browser. That is going to open, oops, I'm sorry. This is going to open a browser tab. We're going to authorize Stripe to have access to our CLI. And now it's done. Now all we need to do, if you can run this command, Stripe listen, dash dash four, word two, localhost 3000 slash API slash Stripe. So this is going to establish that webhook listener. This is going to tell Stripe that everything that happens within Stripe, forward those webhooks to localhost 3000 API Stripe, which basically means forward them to this route that we have just in, that we have just defined here. And if you run it, we're going to receive a webhook secret. That's going to only work for your specific localhost environment.

Now I know this was a lot of setup. So let's actually add some more console logs and see what's happening. Let's add a console log here and say, checkout session completed. And another one here, updated user to the paid plan. So you can add whatever console logs you want. This is, again, I'm just letting kind of copilot do the driving at this point. Cool. Now we can collapse this and moment of truth. Let's see if this works. Going to get rid of this and I'm going to go back localhost and refresh. All right, let's click on upgrade. So we came to the checkout Stripe page, subscribe to personal premium plan, $10 per month. All my credit card details are already saved. Again, it's a fake card. I'm not paying any real money here. So if I click subscribe, let's come back to our logs and see what we're seeing happening. So we saw a bunch of stuff there. This is all the events that are trying to come in from Stripe. We're going to see a 401 error here because actually our clerk middleware is blocking all of these API requests. So we need to tell, we need to actually tell clerk to let API slash Stripe through. Because clerk is only responsible for things that we expose to users. But this is not something we're exposing to users, this is something we are exposing to a different backend service. And because of that, we have our own security stuff that we have defined in here. So we don't need clerk to authenticate the API routes. And now let's try again. Let's try upgrading. We're going to go here again. We're going to click subscribe and let's see what happens now. Okay. So we got, there we go. We got a plan paid. So even though we have a few errors here, we actually ended up with a plan paid in our console. Which means if we go back to this, the todo's remaining function, the, we have successfully updated the clerk metadata to say that the user is now on a paid plan. And then we have successfully injected that metadata into the token, so that in our code, we can now see if the currently logged in user is a paid user or a free user. Which means I can now say const max equals $1. If plan equals paid, then this is 10. Or if not, this is five. And here I can say max minus todo's count. And let's go back here and see how the UI changes. Now I have six todo's remaining. I went from one todo remaining to six todo's remaining because I'm on the paid plan. Let's get rid of this. And let's also quickly see how we can make our UI reflect this. So we're going to add session claims here. We're going to copy this code from up here to down here in our server component. So now we know if the user is on a free plan or a paid plan in our component. Which means we can simply hide the upgrade button if they're already on a paid plan.

21. Adding Multi-tenancy Features

Short description:

You don't need to show the upgrade button if they're already on a paid plan. Instead, we can display 'pro' or even have a button for downgrading. Although we won't implement downgrade or cancel subscription functionality in this workshop, you can imagine adding a downgrade button for users on a paid plan. The upgrade button successfully updates Clerk, Stripe, and our application, allowing us to create more than five to-dos. Once payments are integrated, you can quickly build additional features on top of the existing functionality. For example, you can have multiple to-do lists for each user, different tiers for free and paid users, and more. Clerk makes it easy to add multi-tenancy features, such as organizations, teams, invites, and organization plans. By enabling organizations in the Clerk dashboard, you can switch between your personal account and any organizations you create. The organization switcher component allows you to manage users and roles within each organization.

You don't need to show them the upgrade button if they're already on a paid plan. So if plan equals free, then we show them upgrade button. This should be a question mark. Or we just show them you are on a pro plan. Or actually let's just say pro. So instead of upgrade, we should now see pro. It's styled horribly. I don't like that, but I'm going to keep it that way for now. Or maybe we can change this to a button that says downgrade. We're not going to implement downgrade or like cancel subscription functionality in this workshop, but you can imagine that if they're already on a paid plan, you want to show a downgrade button here instead of an upgrade button. But for now, having that upgrade button, it updated Clerk, it updated Stripe, and it updated our own application so that we can now create more than five to dos. So I can go four, five, six, seven, and you can see that my limit has automatically been increased. So that was a little bit of a tangent, wasn't it? There was a lot of implementation and very soon in the future, this is all going to get much more simplified because we are building payments into Clerk. But for now, even though that took a while, it really like, once you kind of get a handle of these, of doing this, you can basically get all of this up and running in like less than an hour. And then once again, the point was that you can quickly go back to building your projects. So once you have payments like these, just quickly built in, you can just keep building features on top of it. You can have more to dos, you can have different, like for example, we can go overboard here and we can have multiple to do lists for each user where each, let's say that free users are allowed to have three lists, paid users are not allowed to have 10 lists, whatever, however you want to structure the business of this application, that's up to you. But all the tools for you to do that are right here. So how are you guys feeling so far? Anyone in the chat? Feel free to ask, throw in questions or if, once again, if you have been lost at any point, we can catch up. We have 30 more minutes left. We can spend the entire 30 minutes asking or answering questions or getting people caught up. Or if you guys are just enjoying watching and following along, we can add some multi-tenancy features because right now we can have individual users that have their list of to-dos and that could upgrade to the pro plan for higher tiers. But we can also have organizations and teams and invites and organization plans. And once again, Clerk makes it really easy to add all of those things into our application. Let's close some of these open tabs. And if I don't see a lot of questions, I'm going to move on to the next best thing, which is multi-tenancy. How many of you know what multi-tenancy is? How do you define multi-tenancy? Who has built a multi-tenant app? That's one of those very fun things to build every now and then. Never heard of it. Cool. So the idea of multi-tenancy is simply that there are multiple groups of users using your app and they all have kind of this shared state. So even right now, in some senses, our app is multi-tenant because each user has their own private list of to-dos. But usually what multi-tenant means is that we have an organization or we have a workspace or we have a team and that team or that organization has a group of users. Actually, you know what, I'm just going to show you. Let's go to our layout file here where we have our user button right here. And we're going to add a new component here called the organization switcher. Once again, this component just comes pre-built in FromClerk Next.js. You just have to drop it in like that. I'm going to save that and come back here and see what happens. I also need to make sure running. Can you refresh? I might need to refresh. Why am I not seeing organization switcher? Interesting. Is this layout cached somehow? No, it's not. Okay, let's grab both of these and assign in component. That might've been it. Hmm, that usually shows up. Not sure what the issue there is. I'm going to try the org profile. Okay, organization profile cannot render. But organization switcher should be something we can render here. Should deactivate or activate UR write, you know what? You are absolutely right. This is something that I don't do often enough times because I just do it once and I completely forget about it that we have to enable organizations. So yes, in your dashboard, go to organizations and enable organizations like that. Now, if I go back here and refresh, there we go. Thank you very much, Joachim. Apparently you know, Clerk better than I do. That's great to see. Cool, so again, I can get rid of the signed in here. I added this organization switcher component. That's really all I did. And I enabled a switch from the Clerk dashboard. And what this enables into my application is basically multi-tenancy. I have my personal account and I can just create a new organization. I can call this, let's say React Summit NYC. And this is just a new organization that exists in Clerk. I can invite people to it. I can give people the admin or the member role or I can skip this for now. But now I'm in this organization. I can switch back to my personal account or I can switch to this organization whenever I want. And once again, you can go to the manage organization, just like manage user, or just like the user profile, we have the organization profile. You can manage, you can see all the list of users that are in the org.

22. Implementing Multi-tenant To-Do Lists

Short description:

You can now have different versions of to-do lists for users and organizations. By filtering to-dos based on the organization ID, you can display the to-do list for the entire organization if the user is in an organization account. If the user is in a personal account, they will see their personal to-do list. The org ID is used to track which organization a to-do was created under. The to-do component is updated to include the org ID when creating a new to-do. The database schema is also updated to include the org ID field in the to-dos model. By fetching the to-dos based on the org ID, you can ensure that each organization has its own to-do list. Users in an organization can see all the to-dos created by other members of the organization. This allows for the creation of a multi-tenant application with separate to-do lists for each organization. The implementation time for this feature is minimal, taking only five to ten minutes to set up. By replicating the Stripe processing, organization plans can be added on top of the existing functionality.

You can see a list of invitations. You can invite new people. And if you were an admin, you can change the roles. So again, everything fully pre-built. As a developer, I didn't have to do any of that. All I had to do was add this organization switcher. But now I can actually have different version of these lists not only for the users, but also for organizations.

So let's quickly see what that looks like. Let's go back to our todo application, which means our dashboard. And let's try to figure out, okay, how do we know what organization the user is part of or even the user has currently selected? Well, we have our token, right? So let's just grab org ID out of auth. And we can see that org ID is either a string or null or undefined. If it's a string, that means the user is logged in and has an organization selected. So let me just console.log org ID so that we can see what's happening in each different case. Here you're going to see org ID is an actual string. It's the ID of the org that I'm in. If I switch back to personal account, you're now going to see that org ID is undefined. So this org ID not only tells us if the currently logged in user is on a personal account, but also which organization they have currently selected.

So what can we do with this information? Let's say that if you're on a personal account, you are going to see your personal to-do list. But if you're in an organization account, you're going to see the to-do list of that entire organization, which means even the to-dos that other people have added into that organization, you should be able to see. So let's add this filter right here. We're going to say that if org ID exists, we are looking for all the to-dos that have the org ID as the same as this one. This also means we need to update our database schema, which we should probably do right now. We are going to track which org a to-do was created under. This can be optional because let's say that if you created a to-do for your personal workspace, for your personal account, the org ID is just going to be null. But if you created a to-do in an organization, the user ID will still be you, but you also have the org ID that will be the ID of the org, in this case, React Summit NYC. Oopsie. There we go. So we have added a new field to our to-dos model. We can go back here. We can say npm run db push to make sure the schema changes are propagated through. And now we can npm run dev again.

So let's also get the org ID when we're creating a new to-do and make sure that it's put in here. And this is really all we need to do because if the org ID is undefined, it's just not going to be set in the database, which by default means it's going to the personal account. But if the user has an organization selected, we're going to create the to-do with that ID. So we automatically know that it's created under that organization. So all we needed to do was to grab org ID out of the auth and pass it in here. We can do the exact same thing everywhere else. We can add this in the where clause here. And here and here. And when we are fetching a list of to-do's, now here's the thing, here's where it gets slightly more complicated. We're checking if the org ID exists, which means the user is currently in an organization. And in that case, we're going to fetch a list of all the to-do's in that org. If not, I'm going to fetch a list of all the to-do's created by this user, but also where org ID is none. So basically making sure that we are fetching, we are fetching the personal workspace items and we're not accidentally pulling in any to-do's that the current user might have also created in other organizations. So let's see what this looks like. I'm going to refresh this. It takes a little while because my computer is very, very slow today. I apologize for that. There we go. Well, we are in React Summit NYC and we see no to-do's found. Let's add some code. Here we are in React Summit. Let's see, let's add some here, deliver workshop, go to after party, chat with... Oh, and it's now messing with our to-do's remaining logic. So here we also, we can just check for org ID. And if org ID exists, we're just going to, let's just say we short circuit out of here. So basically we are allowing organizations to create unlimited to-do's. Because if we add a new to-do here, this remains at 10. Ideally, what you might want to do is basically do the entire Stripe, have another Stripe plan, another Stripe checkout button, but for organizations instead of just for users. But for the purpose of the workshop, we can just short circuit it out. And we say that organizations can make unlimited to-do's right now. Just like how before we added Stripe, users could make unlimited to-do's. But the point here is that if I go back to my personal account, I only see the to-do's that I created for the personal account. I don't see the to-do's that I just created for the organization. So now each organization had its own to-do list. And if I invite someone into this organization, they're going to see this exact same to-do list that I am seeing because of how we are fetching the to-do's in our database. So that was, we went from a completely user-based application to a multi-tenant application. And how long did that take? Maybe five to 10 minutes. That did not take long at all. And if we can just go, if we can just replicate all the Stripe processing that we did, all the Stripe programming that we did, we can now have organization plans on top of this.

23. Ending the Workshop and Feedback

Short description:

I'm going to end the workshop here for now. Feel free to watch the recording and provide feedback. The completed branch includes all the features we've built, including plans for organizations. The UI looks nicer with free and pro plans for users and organizations. You can add custom pages within Clerk's prebuilt components. You can build applications that handle user and organization differentiation seamlessly. You can also add UI for users and organizations within the Clerk UI. There are no more excuses to build applications that can scale to thousands of users. Thank you all for being an amazing audience. Please let me know your feedback and share what you build with Clerk.

And I am going to end the demos there for now, or I'm going to end the workshop here for now. Could not type and log into accounts at the same time after a while. Well, that's why we have the recording available. So feel free to go and go with the recording. And you're probably, if you're watching this recording right now, please hit me up on Twitter, on YouTube, on Discord, wherever you can, and let me know what you thought about the workshop so far, and if it has been any helpful at all.

We have 20 more minutes and I'm going to leave that up for a bunch of questions. If you are also on the GitHub repository, you can also just explore this completed branch. This is what I've been using as my cheat on the side. The completed branch basically has everything that we have done so far, but it also has plans for organizations. So let me actually very quickly demo that now that we are done with the workshop itself. I'm going to quit that, quit that. I'm just going to discard everything that we have done so far. Thank you everyone for the comments, really appreciate it. And I'm going to check out completed. I'm going to install everything that's needed. And I'm going to run the development server. So let's see what, because there are a couple of steps that I didn't get to do throughout this workshop. Let's see what those look like. So I'm going to refresh. Once again, this is your opportunity to ask questions. So please take advantage of this time. I'm taking notes there. Oh, my glass of water is gone, or there's no more water in there. I'm reading properties. I cannot read properties. This is happening in here. Oh, because this can be... I'm just going to say that they can be empty objects initially. There we go. So this is the completed version that I had. You can see that the UI looks slightly nicer. There's a free version here, and you can see there's eight to-dos remaining for the organization. And I can add a bunch more here, and once I get to one or once I get to zero, I cannot create any more to-dos for the organization. If I go back to my personal account, on the personal account, I'm on the pro plan, so I can create actually 10 to-dos. So I can go up all the way up to 10. Can get rid of that. But if I add anything else, I cannot. And you can see here in the console, you have reached your to-do limit. I can keep deleting these, and then obviously I'll be able to add more. So in the completed branch, I actually have a free plan and a pro plan for both users and organizations. And there's one really nice trick here, is that you can actually add custom pages inside these prebuilt components. So this is the organization profile components that we just jumped, or like embedded in, which is dropped in from the Clerk library. But this plan is a completely custom page that I've added in here. And you can see that it shows all the information about how the organization is currently on the free plan, and I can upgrade to the pro plan for $50 a month. And the same thing if I go to my user profile, I see a plan, and it says that I'm currently on the pro plan for the personal subscription, and I can downgrade. So not only can you build all of these applications where the user and organizations are treated differently, and your application just seamlessly handles all of that, you can also just add all of those UI, add extra UI that concerns users and organizations, right inside the Clerk UI. So you don't even need to have your own custom, fully built pages for all of these things. And all of these contribute to this value that I was trying to come to initially, where I don't really have any more excuses. If I have an idea that might work, I can just build it out, and I can build an application out of it, or you can build an application out of it that can go up to thousands of users for free, or for very little subscription charges for the services that we used throughout the workshop. And I hope this has been valuable for you all. Reading from the chat, it looks like it has. Thank you all for being here. This has been an awesome workshop. You guys have been an amazing audience, and I'm really hoping to see what you guys build with these. I will say at the end, let me quickly plug in my Twitter here, and also Clerk. So this is my Twitter. Once again, if you found this workshop helpful, please let me know, and let me know if you build something cool with this. Also let us know. If you build something cool with Clerk, we would love to talk about it more and share it across. We absolutely love it when people build cool stuff with Clerk. So if you can do that, I hope you guys have a great day. So if you can do that, I would absolutely love to see that. Please hit me up and let me know how the workshop went. And I know there's also an anonymous feedback form that is going to the organizers. So please submit that as well, but also completely feel free to just drop into my DMs and talk to me about Clerk, or about building SaaS applications, or just web development in general. Whatever you guys want. Whatever you wanna chat about. Have a nice day to you too. Thank you very much for being here. Thank you for the help.

24. Building with Clerk and Future Plans

Short description:

I have used Clerk for about six months before joining as a developer advocate. I built a startup targeting government agencies, but it didn't go big. Another project was a conference attendee app with a couple hundred users. Personally, I haven't had the opportunity to build many big things with Clerk, but I'm actively working towards it. Thank you for the question.

What is the biggest website account that you ever build with Clerk? So I got to use Clerk for about six months or so before I ever joined Clerk as a developer advocate. We were trying to build a startup that catered towards, let's say, government agencies. It didn't really go big. And the second thing that I built with Clerk, which was a conference application, so a conference attendee app. So when you go to a conference, you can look at this app. It has this entire schedule for the conference. You can save stuff. And it had like, what, about a couple hundred people using it on the day of the conference. So personally for me, that has been kind of the limit, but that's really only because I don't get the opportunity to build a lot of great things with Clerk. I get the opportunity to build small things like this demo, but not a lot of big things. And that's something that I'm actively working towards. Great question. Thank you very much. Thank you for asking. And again, if you have more questions like these, that's exactly what my Twitter is for. All right, I'm gonna hit that end button now. Thank you guys very much.