
Build a Full Stack SaaS Application from Scratch with Next.js, Clerk, Convex, Elevenlabs & More Sign-up for Clerk here: https://go.clerk.com/Ximb431 Sign-up for Elevenlabs here: https://try.elevenlabs.io/ghybe9fk5htz Sign-up for Convex here: https://www.convex.dev/ Start the Repo! https://github.com/developersdigest/ai-saas-starter In this comprehensive video tutorial, learn how to build a full stack SaaS application entirely from scratch. The video guides you step-by-step in leveraging Next.js for both frontend and backend routes, Clerk for authentication and billing, Convex for real-time data sync and storage, and 11 Labs for advanced text-to-speech capabilities. Additionally, tools like Cursor and Claude Code are utilized to efficiently accelerate the development process. Follow along to set up your free accounts, establish your project foundation, and implement key features like user authentication, billing, and seamless data management. By the end of this tutorial, you'll have a robust starting point for your own AI-powered SaaS application that can be pushed to production. 00:00 Introduction to Building a Full Stack SaaS Application 00:32 Setting Up Authentication with Clerk 01:07 Integrating Billing with Clerk 01:36 Leveraging Convex for Backend 01:55 Using ElevenLabs for Voice Cloning and More 02:16 Building the Frontend with Next.js 02:18 Accelerating Development with Cursor and Claude 03:05 Creating and Configuring the Application 03:50 Setting Up Clerk Authentication 05:36 Building the Landing Page with Cursor 09:19 Implementing Billing Features 17:00 Integrating ElevenLabs API 18:40 Creating API Routes and Components 27:14 Gating Features Based on User Plans 30:04 Subscription-Based Endpoint Management 31:01 Setting Up Convex for File Storage 32:14 Configuring Navigation and Frontend Gating 41:22 Defining Database Schemas in Convex 44:00 Implementing Text-to-Speech Functions 46:34 Storing and Retrieving Audio Files 57:51 Enhancing UI and Final Touches 01:00:31 Conclusion and Next Steps
Weekly deep dives on AI agents, coding tools, and building with LLMs - delivered to your inbox.
Free forever. No spam.
Subscribe FreeNew tutorials, open-source projects, and deep dives on coding agents - delivered weekly.
--- type: transcript date: 2025-10-08 youtube_id: tfMvT-8Q-TE --- # Transcript: Build a Full Stack AI SaaS Application in 60 Minutes In this video, I'm going to be showing you how to build out a full stack SAS application completely from scratch, leveraging Nex.js, Clerk, Convex, 11 Labs, and I'm also going to be showing you how you can leverage some tools like Cursor as well as Claude Code once you've built the foundation to really accelerate the project of whatever you have in mind. What I'm going to do within this is I'm going to show you step by step all of the different component pieces to set up a good foundation on how you can really quickly build out a SAS. in particular leveraging some of the latest features from Clerk. You might have heard of Clerk before, which is a great platform for managing authentication. It allows you to easily set things up like OOTH, being able to integrate things like social login with Google or Facebook or GitHub. Basically, all of the different providers that you'd like. It makes it super straightforward to manage users. all of the different ins and outs that goes into setting up authentication like having to set up all of the different relevant emails for whether you forget a password or sign up or what have you. All of that is going to be managed by Clerk and they do a phenomenal job at that. Now, in addition to that, what they've also recently released is the billing capability. And where this is really nice is if you've ever had to set up Stripe before, you will know that there are a ton of different pieces that go in doing this. Well, there are a number of different web hooks that you have to manage. There are a lot of events that you have to consider. All of the different changes within billing when people upgrade or downgrade. All of that comes with different intricacies and if not done properly, it goes without saying is prone to bugs. And then on the back end, we're going to be leveraging Convex. And Convex is one of my favorite platforms. There's some really cool features like being able to real-time sync the data between the UI layer as well as the back end. All of this basically comes for free as a part of the platform. And one nice thing with it is you will be able to also host files. And then next up, we're going to be using 11 Labs. And if you're not familiar with 11 Labs, it is an amazing platform where it allows you to clone your voice. It allows you to leverage pre-existing voices. There's even music generation as a part of the platform. And all of these different features, it goes without saying, you can actually build quite a bit of different applications based on their technology. And then next up, we're going to be leveraging Next.js. So, we're going to be using this for the front end as well as the backend routes for our application. And then finally, I'm going to be focused on showing you within cursor, but I'm also going to show you some demonstrations on how you can leverage Claude. But most importantly, the way that I'm going to be setting this up is going to be effectively from scratch. So, you're going to see all of the important pieces of what you do need to set up before you begin to lean into some of these coding tools to do the other groundwork. But once you have things in place, you will be able to strategically use some of these anticoding tools. I've personally been really enjoying Claude Code lately, and that will help speed along the process of everything from research all the way through setting up UI elements all the way through a ton of other features, which I'll show you throughout the video. First things first, in order to set this up, you will be able to set up free accounts on everything that I'm going to show you. So, you're going to be able to spin up a free account on Clerk, on Convex, and you will be able to get some free credits on 11 Labs as well. I have an empty directory within cursor here. And as a first step, what I'm going to do within this is I'm going to use bun to set up our application. You can use npm as well. If you want to mpx create next app, pmppm, you can do that as well. So, in this video, I'm going to be leveraging TypeScript. I'm going to be using ESLint. We are going to be using Tailwind. We are not going to be using the source directory. We are going to be leveraging the app router. We're going to be leveraging TurboAC. And then we're also going to import aliases by default. Once that's all spun up, we can go ahead and start our development server here. Next, what we're going to do is we're going to create a new application on Clerk. You can make a free account. I don't have a paid account on Clerk, so everything that I am showing you here is going to be for free. Now, within here, you do have the option to select the different providers. So, within here, for now, what I'm going to do is I'm going to set up email as well as Google. We're going to go ahead and click create application. And then once that's done, what you'll see is we have the different frameworks of what to choose from for the different installation steps. As a first step, what we can do within here is I can go ahead and I can install clerk/next.js. Next, what we're going to do is we're going to create av at the root of our project. We're going to go ahead and envin here and we can paste in our environment variables. Once we have that, we're going to go ahead and set up a middleware.ts file. And within the file, we can go ahead and paste in what they had configured in the example there. Next up, what we're going to do is we're going to be wrapping our layout with a clerk provider. Within here, if I go over to app and within our layout, you can go ahead and highlight everything that's within it and we can go ahead and replace that with everything from clerk. Within here, we'll see that we have our header. We have our sign-in button, the sign out button, so on and so forth. And then once we have that, since we do have hot reload set up, we can go over to our application. And now we see we have a sign in as well as a sign up button. I can go ahead and try and test this out. So if I click to sign up, now we have this really nice modal that will pop up. You've probably seen this before in a lot of different applications that you've used. And then within here, you can test this out with Google if you'd like, or alternatively, you can put in an email and password. I'll go ahead and I'll click continue. I've received the confirmation email. So I'll just go ahead and plug in the code to verify that. And then as soon as we have that, we can see now that we have the icon that shows that I am in fact authenticated. So now what I want to show you is now that we have some of the foundation in place, now we can leverage cursor a little bit. And in this case, what I wanted to do is specify for it to begin to update the homepage. Our homepage here is within our app directory and within the page.tsx. And within here, what I'm going to say is I want to create a landing page for my SAS application. I want to call it developers digest texttospech app. And I want to have FAQs. I want to have a pricing section. Additionally, I want some global elements. I want a navigation as well as a footer. And let's have a handful of placeholder links for both of those. What we'll see is the agent begin to think through what we asked of it. And then once it's determined that this is a Nex.js JS application and it understands the overall structure. It will go and it will isolate and find the page.tsx as well as create the folder for the components and create the components for both the header as well as the footer. Once that initial generation is done, I'll circle back here and then we'll begin to build out some of the core pieces of logic. Now, within this, I'm going to be leveraging Claude for Sonnet. Now, you can also use GPD5 if you like. You can use Gemini. basically whatever model that you'd like within cursor. Most of them do actually perform quite well with this type of task. I have tried this on a number of different models before. Now, as soon as we send in that request into cursor, what we'll see is it will break that task into a number of different individual items. So, within here, we have this nice to-do list where we will see the agent begin to work through the process of building and creating and editing all of the different files that are required. What we see is it's created a components directory and then from there it's gone ahead and it's created our navigation. Now, one nice thing with this that I do want to point out is if I take a closer look at the code here, we do see that it did take into consideration that clerk logic that was already set up within here. We can see that we have dev digest TTS. And if I just scroll down here a little bit, we have our authentication buttons. Now, if I go over to our to-do list here, you'll see as one of the last items here, we have update the layout.tsx and previously where we had just added in our prior step. This is where we originally put in the logic for having that signin button. This will make our app a little bit more composable as well as scalable to actually have that navigation broken out into its own component. Now, if I take a look at the agent, I see that it's creating a footer component. And the thing to know with this is depending on the model that you're using, it will take a little bit of time to work through all the tasks. So instead of just following along exactly what the agent is doing step by step, I'm going to trust its intuition and then we'll circle back with what it has generated. So here is the initial generation of what it has created for us. So within here, if I just scroll through, I see that we have the pricing table. We have that beautiful hero section at the top. We have these functional FAQ questions. And then if I scroll down to the bottom, we also have the footer just like I had asked for. Now if I hop back to the code just so we see exactly what it did here. It created a components directory of all of the different components that it needed. On the page, we can see that we have this nice homepage here that's referencing all of the respective components. On the layout here, I can see that we do now have the components properly used that are wrapped within the respective tags. We can see that it's maintained that clerk provider like it had within the reference, it was able to understand and keep what we had in place. And also if we go over just like I had showed you within the navigation, we have all of that preconfigured logic from clerk within our navigation. Just to quickly show you, we do still have this functional button with clerk, but now we actually have to worry about setting up the other aspects of our application. We still need a backend to store all of our files. we still need to set up billing so users will be able to pay for our platform and leverage it for generations and what have you. What you're going to be able to do is you can go to configure your application once it's set up. So it will detect once you've added it into your application and what we can do is we can go over to the billings tab. I can click settings for the billings and then within here all that we need to set up billing is I can go and I can click to create a plan first. it will create a free plan and then we can also go and create the different paid tiers. So, and then within here I can say this gives access to be able to generate files more than 100 characters long. And then what you can do within here is you can specify the price for the plan. Let's say this is going to be a $20 a month plan. I can go ahead and add that in. You can also add an annual discount. So, for instance, if you've seen those pricing pages where there's a toggle between monthly and annual, you can go ahead and turn that on, for instance. And then what you can do within here is you can just specify whatever the annual discount is. Let's say it's $15 or $18 or whatever number respective to the monthly plan. You just have to make sure that it is actually less. And then within here to add a feature, let's say in this case we want to extra voices. For instance, if we want to be able to generate maybe on the free tier just one default voice and then on the actual paid tier have maybe 10 voices or a 100 voices or whatever it is, we can go ahead and do that. We can say this gives access to 100 premium voices, let's say. So, we'll go ahead and we'll create that feature. And where this is helpful, where we'll see in just a few moments here is we're going to be able to specify things based on the key within the logic of our application. But additionally, what we're going to be able to do is we are going to be able to reference the key for the pro tier. Say if users within clerk only have access to the free tier, we can specify actually rendering out components based on that key alone. Some really cool stuff that you can do this that I'll show you within the application. So now what I'm going to do is I'm going to go back to plans here and I'm just going to quickly create a premium option as well. Let's call this premium. Then within this, we're going to say unlimited generations. And then you can say whatever you want. We can say something like Slack support maybe that you want to offer for some of your premium users. And let's just price this at $200 a month. We'll also give an annual discount of let's say $150. And then what I'll do is in this case I won't actually add any new features. We'll just go ahead and save this out. Now that we've set up our subscription plans, all that we need to do at this point is we have to enable billing within the application. And all that we need to actually have this rendered within our application is we can reference this pricings table component here. Specifically, just to show you how to set all of this up is I'm going to quickly go over to the documentation. And if I go to billing for BTOC SAS, if I just scroll down here, we're going to see this example here. Let's go ahead within our application. Let's copy this piece of code here. And then what we're going to do is we're going to create a new pricing page within our application. So I'm going to call this page.tsx. tsx and then within here we can just go ahead and paste this in. Once we have that, what we can do now that we've made this page is I can go to /pricing and now we have these respective tiles. The one thing that we will have to do within here is we just have to add some margins. Now that we have that, what you can do within cursor is if I highlight this piece of code and I command K and I can say I want to use Tailwind instead of hard-coded CSS. I'll go ahead and I'll send that in. And for a lot of minor edits like this, instead of actually manually writing out all of those different Tailwind classes, you can go ahead and save that out. Next up, another thing that you can do within cursor is I can go and I can specify for a hero image. And now, additionally within cursor, what you can use is command K. And then within here, I can say I want to create a beautiful hero area that reads pricing at the center. I'll go ahead and I'll send that in. And then what we'll see within here is it will create that pricing table. And now it has that context of knowing to leverage Tailwind. One of the nice things with this is it gives you really a sort of tactile feel of while you're leveraging AI, you are largely seeing all of the different pieces of what actually is going within your application. Now we have this hero area that is embedded within our pricing page. And then what I can do within here is just to test out this functionality is if I go and I click to subscribe. Now, how pricing works within Clerk is you are going to be leveraging Stripe. So, you will be able to connect this to your Stripe account and then once payments begin to come through is it will ultimately be routed and paid out through Stripe. So, all of the disputes if there were any and all of that is going to all be managed by Stripe. Now, how this works within Clerk is at time of recording, they take 0.7% of the transactions that are earned. And honestly, for not having to worry about setting up authentication and being able to largely do a considerable part of this w without having to worry about all of the different code and maintaining the web hooks and making sure that everything is both secure and doesn't have the different bugs, you can effectively offload all of that onto Clerk. Now, in terms of testing this out, it has excellent UX. All that you need to do instead of typing out the 4242, etc. like you might have otherwise done to test out the test credentials within Stripe, you can just go ahead and have a test payment. And what that will do as soon as you send in that test payment. What you'll see from your application from Clerk is you'll have a receipt. So for all of those different transactions that go through, they even have all of those email pieces set up for you. It really is a turnkey solution for setting up billing within your application. And it honestly makes it dead simple to set up. What I can do within here is I can click continue. And now that we have this set up within our application, I'm just going to begin to scale out some of the features. Now, what I'm going to do is I'm going to swap out the pricing component that is on the homepage here. Instead of the AI generated one, we're going to now use the functional pricing table from Clerk. What we'll see within cursor is we'll have that autoimp import. And then if I tab over, we can remove that import from the one that we had previously. Now, additionally, we can just go ahead and we can delete that pricing component that we had as a placeholder. So, now if I go back to our application, we have this pricing table. And then what I can do within here is if I just highlight this pricing table, I'm going to say I want to give a max width of 1,200 pixels in tailwind around this table. We'll accept the change. And now we can see that we have the pricing table completely embedded within our homepage. Additionally, within the pricing page. Now, the one thing that we have here is we do have the pricing link, but it is a hard link down to this section here of the page. So, instead of having that within our navigation, I'm just going to pop over to our navigation here and instead swap this out to /pricing to get the billing page to actually route to that page that we had set up. Okay, so now we have our homepage all set up. We also have the functional off working. We can log in, log out. We have the pricing table. Now, another nice feature of Clerk Billing is you will be able to switch to the different plans if you'd like. And what's nice with this is they'll also elegantly handle the logic of keeping the current plan that you have and just switching to the different plan at the end of your cycle. Again, there's a lot of little nuances that go into monthly billing that they just handle all of just quite beautifully in my opinion. Okay, so now we made pretty good progress with Clerk. Now, some of the other pieces that we still have to set up are both 11 Labs as well as Convex on the back end. So, next what we're going to do is we're going to sign up for a free account on 11 Labs. You will be able to get a number of credits for free to try all of this out. And then what we can do within here is if we go to the bottom left hand corner, we'll see the number of credits that we get each month, which at time of recording is 10,000. From here, what we can do is if we click the developers tab, we can see that we have this option to create an API key. From here, we can go ahead and we can create our API key. Now, the one thing to note with this is you do just have to make sure that you specify the correct access. One nice thing with 11 Labs is they do give you the ability to specify only the access that you need for that key. Additionally, you do have the option to restrict the key if you'd like. Once you have your key, what you're going to be able to do is go over to the env. And then what we're going to put in is 11labs_appi_key. Once you've set up your API key, we can head on over to the 11labs documentation. And within here, there are a number of really cool features that you can include in an application like this. If you want to have music generations as a part of your SAS application, you can do that. Additionally, in this case, I'm going to show you the texttospech capability. So now within the text to speech tab here, we can go over to the developer quick start. And if we toggle on to TypeScript, we'll see that we can install the 11labs package here. So what I'm going to do is I'm going to copy that installation step. I'm going to go to our terminal and I'm just going to bun install 11 Labs. Now once we have that, there is a code example here. And what we can do with this is we can copy this. And now what I wanted to show you is if we go over to our app and if I create an API route, if I call this route.ts and then with if we command K, a really cool thing that you can do also is if you take a snippet of code is if I say I want to create a post request endpoint with the following from 11 Labs. Specifically, I also want to have as a part of the payload of the post request the text that will actually be used to generate the audio. And then within here, I can paste in that code. I can go ahead and I can send this in. And what it will do is it will import the client for 11 Labs and it will set up all the proper syntax for actually setting up that post request within here. And if I quickly go through the code here, we see that we're grabbing our API key from the API keys. We're importing 11 Labs client. We're importing our API key to be able to leverage the 11 Labs SDK. Within here, we're checking to see if there actually is text as a part of the payload for the post request. And then from here, this is going to be how we make a request to the 11 Labs endpoint using their SDK. Now, one thing to know with 11 Labs is we see this key here and this is going to be associated with the respective voice that we want to leverage. Say for instance within our application, if we want to start with one voice, we can have that and we can ultimately scaffold this out to be other keys that we might select for instance from a drop-down within the UI. We can swap that out to actually be a part of the payload a little bit later. if we'd like within our application. From there, we're going to be passing in whatever the user sends in as a part of their request. This is going to be what we have within the UI, which we'll see right after this. And then from there, we have the model ID. They have a few different models on 11 Labs. Some have higher quality, some are faster. Depending on the use case, you can go ahead and play around with that. And then additionally, we can specify the format as well. Now, the one thing to know with this is sometimes we see that AI doesn't quite get things right. But one thing to note within cursor that I do quite like is we will be able to actually even fix little things with AI. If there are little syntax errors, say for instance, we might not have an interface set up within Typescript for the different values that we need. The fix tab is actually really good with helping out with a lot of the different syntax, especially from what I found in working within Typescript. If I just go through here, what we're going to do is we're going to get the stream of audio coming back. Then once we have all of this, we're actually going to be returning this back to the client as an audio file. This isn't going to be a JSON response. This is in fact going to be an audio file that we can read on the front end. Now, what we're going to do is I'm going to show you how you can begin to gate some of the features leveraging the pricing within Clerk as well as gate it based on the authentication as well. What we're going to do is we're going to create an API path. And within API, I'm going to create a new route. And in this case, I'm just going to call the route 11Labs API route. We're going to call this API/1Labs/oute.ts. What we can do within here is once we have that code snippet from 11labs, what I can say is I want to create a post request leveraging the following. Another thing that I like to leverage command K for is if you have a small code example like that, I can say I want to set up a post request leveraging the following. And then within here, I can paste in that sample from 11 Labs. Additionally, what I want within here is instead of having the text hard-coded, I want to have that as a part of the post request. Okay, so I'm going to go ahead and send this in. And then within a number of seconds, we'll see that it will scaffold out everything that we need for our route. Here we've imported the 11 Labs client. We might actually have to specify our API key. It could potentially be inferred, but just in case it isn't, we'll just specify it just like that. Now we have our post request. We're checking if the text is a part of the payload. If it is, we're going to be passing that in to 11 Labs. The one thing to know with 11 Labs is we see this key here and this is going to correlate to the different voice model that you want to leverage. They do also have a number of different models. Some are faster, some are higher quality. You can go ahead and leverage any model that you want within here. You can also specify the format. One thing that I want to show you within cursor. So if you have any errors here, like we see these squiggly lines here, what we'll be able to see are the errors within the problems tab here. And one cool thing with cursor is if you rightclick on the error and you click to fix it with AI, what it will do is it will take that error as well as the context of that section of code and it will go ahead and fix it. So in the case of TypeScript, this is really helpful. If maybe you had a piece of generated code that didn't infer the right types or what have you, you can go ahead and leverage that fix tab to easily work through some of these different bugs. Now, within our agent, I'm going to say I want to create a new page that leverages the 11 labs component. Specifically, I want the component to have an input and also show the results of the media file once it has come through. I also want to make sure that this component is gated and only authenticated users can leverage this feature. Okay, so I'll go ahead and send this in. Another thing that you can do within cursor is you can mention different pieces of context. So say for instance, if I did want to reference that 11 labs file, I can specifically mention it with an app mention. But what we'll see cursor do is it will go through and it will read through the route. It will understand what is happening. Additionally, it will look at the middleware to understand that clerk functionality within our application. And then finally what it will do is it will create that client component with that functionality where we can send to that post request that we had just created and then ultimately have the starting point of a component. And this is something a lot of people have mixed feelings on agentic coding. In my opinion if you leverage it and you do read through the code and you are cognizant of all of the different changes it is an extreme accelerant. But I wanted to show you how you can leverage it with still having a lot of control over the process. Knowing which different services that you're including within this, like you saw me go through and actually look through the different documentation, set up the proper keys. A lot of that you will still have to do. Cursor isn't going to come and save the day and go and fetch your API keys, at least not yet. So you do still have to go through. And where this is nice is you'll know for sure that you are referencing the proper bits of documentation as well. Okay. Okay, so within here I can see that it's gone over and it's created the 11 labs texttospech component. If I go and I click keep here so we can read through this a little easier and just read through the client compon. Okay, so now if I just read through the client component here. So if I look at the JSX here we have enter a value we have a text area of what will be submitted and then based on that we're updating the state so on and so forth and then we're ultimately handling that submit. So here we can see that it's referencing correctly the route that we had just set up and then a part of the payload that it's sending in is we do see that it is leveraging that state of the text that is going to be set as a part of the text area. Now additionally we have the blob that we are going to be and then additionally we have the media file that is going to be returned as a part of what comes back from the 11labs route. So if I just scroll down here I can see that it's created a gated page here. Now if I just scroll down here I can see that it has now also created a page for us. So if I go to the TTS page here I can see that now we can see that it is gated in fact by the authentication at the top here. Otherwise it is going to redirect the user and then within here we're referencing that 11 labs component within here. Now within our application if I go over to TTS I can see there is the component but we have an issue where we can't actually read any of this. I'll go ahead and I'll send that in. And that is yet another use case where AI is quite good where it is able to just swap out for little changes like that. I save this out. So we have these couple elements here that have been swapped out. Now if I go back over to our component and if I go down to our JSX, a similar thing here is if I just highlight this and I command K. And then additionally within here I can say the text is not readable over dark mode. Make sure that it is an accessible color and readable. I'll go ahead and I'll send that in. And a similar case here is it's just going to go ahead and update all of those different Tailwind classes so when it is over a dark theme that it does function as intended. So I'm going to go ahead and accept that. And so it is looking a little bit better, but I am just going to add in a little bit of margin at the top here. So we'll just do maybe try margin top 10 just to space it out a little bit from the top here. So now if I go in our component and I test this out and if I say hello world, we can go and click generate audio. And now here we have the audio file. So if I click here, hello world. There we go. If I click hello world, we can see that it is in fact working. There are a couple other pieces and considerations that we do want to have as a part of this. If we take a look at our route, one thing to note with our route is we do have to make sure that this is in fact authenticated based on the user because say for instance if we just didn't have this portion within our application. The problem with this is people are going to be able just to hit this endpoint and you are going to be build from 11 Labs. Just make sure that you do have authentication set up and further I'm going to show you how you can set this up and tie it to the specific users tiers when using this. Now, additionally, what's nice with 11 Labs is the way that their billing works is they actually base it based on characters. You can do a similar thing here. Let's say for instance on the pro tier you only want to generate a thousand characters of text whereas maybe on the premium tier you want to be able to do 10,000 or something like that. Whatever the numbers are you can do a little bit of determination within that and you can set it within the back end of your application to actually gate it to make sure that it won't actually accept the request. Now to quickly hop back to clerk and how the pricing works. How this works is we're going to be able to reference this has method and within the has method we can determine whether the plan is say a free plan, a pro plan or in our case a pro or premium plan. Now if I hop back to the clerk documentation, what you'll notice is we have this has method that we can access different information about the user and in the billing context where this is helpful is we can check the different plans. In our application we have a pro and a premium plan. So we can check for the lowercase value of those keys. So lowercase pro and lowercase premium to gate different features. This can be on the front end as well as back end. Additionally what we can do within here is we can gate different features or even show different UI elements based on the particular access. Now just to show you this within practice. So currently we have this post request gated by whether users are authenticated. But what we can do within this is we can actually gate this with the has method and within the has method what we can say is user has pro plan for instance. If I just copy this over and if I comment this out just to show you how this can work instead of using the user ID what we can actually leverage is the has method from clerk. ones we could do since we do authenticate as well as handle billing all with clerk is I can actually reference the has method in addition to the user ID but all I need to do actually is I can reference only the has method and then once we have the method what I'll do is I'll have declare the variable for has pro plan and then instead of gold is I'll just swap this out to pro and then what we can do within here is go ahead and return that they're unauthorized in this case if I go back to our application and I click through and we have that high authenticated. What you can do is if I swap this to premium, what we should get is this endpoint now failing because this account, if I go back to our pricing page just quickly, I'm only subscribed to the pro tier. So, this is being gated by that pro key that we set up within the configuration here. Here we see the plan key. And now if I go back here and if I say hi and I click to generate the audio I can see that we now have unauthorized. That is the really cool thing with this is you can have different endpoints and depending on the subscription status some can be pro some can be premium. Say for instance we might limit the text on the front end but just to add that additional logic to make sure that it isn't bypassed if someone was getting a little creative on the back end. We can also just make sure that text is either trimmed or denied if it is over that specific length. Overall, you saw me go through step by step in how easy it is to set up billing with clerk. And now that we have this set up, we have it actually functioning and gating based on that. The one thing that we now need is actually to set up convex to store all of those respective files for each of our users. Now, what I'm going to do is I'm going to bun install convex into our project. And then as soon as we have that, I can bun convex dev. And what that will do is you can log into your account if you don't already have one. And then within convex, it's super straightforward to get started. You can log in with Google or you can log in with your GitHub account. As soon as we have that all set up, we can hop back to here. We can create a new project. I'm going to call this 11 Labs SAS. We'll continue. We'll select we'll have a cloud deployment. And what this will do is it will create a new convex project for us. And the really cool thing with convex is if I go and I fold some of our directories here. Now at the root of our directory, we have this new convex folder. And the really cool thing with this we'll see is we're going to be able to define our tables and where all of the different data whether it's user specific data or things like the different audio files and all of that is going to live within this directory here. Now what I want to do is I want to quickly clean up the navigation component. If I go over to our nav here we have the pricing page. In this case I'm actually going to remove the features. We're going to remove the documentation and the FAQ. and then instead of support and we'll change it out to text to speech just like that. So for now we have the pricing page and then we also have our text to page. So now that we just have the two navigation items I do want to show you just a quick demonstration of how you can leverage this. What I can do is if I just import within cursor to use O is similar to the backend functionality. We can also gate this on the front end. Say for instance we can check whether the user has the pro plan and then based on whether they have the pro plan we can determine for instance if we want to show different things including the navigation item. Similar demonstration here to what we had on the back end is let's say the user doesn't have access to the pro plan and we don't want to show different navigation elements or different UI elements. All that we need to do that is we can gate it similar to how we did on the back end and then we can conditionally render these UI components based whether or not they have access to this. For instance, if I change this out to premium, now we see that we no longer have access to that navigation item. But since this user does have access to the pro tier, if I whether they do have pro access, I can render that navigation item. Again, as you might imagine, this makes it that much more powerful cuz you can gate different components at any level. Again, if I go within say the 11 Labs component or further, if I actually go to the page for text to speech, maybe we have a premium component and then we also have a pro component. And then a similar thing here. So since we already have this page checking whether a user is within our server component page for the texttospech page at the top level we have if the user isn't authenticated to redirect them back to the sign-in page. Now, additionally, what we can do within here is if I dstructure the has method, what I can do within here is let's say I only want to show this component if it's a pro component is I can specify to have only if the user has the pro plan to render this component. If I go to text to speech here, I see that the component is rendering since I have access to the pro plan. But let's say for instance, this particular component, again, just like the other examples, was a premium component. If I swap that out now, if I try and access this page, what it will do is it will just route me back to the pricing component. It's really flexible with how you can use this. And now we don't necessarily even need to redirect, right? You can do whatever you want. You can swap out different components. So maybe you have a premium component, a pro component, and then what you can do within here is instead of redirecting is you could just return some JSX. I can say return a premium section for instance. And then within here we can just have an example of that premium section. And obviously you can swap in your components here. And where I think this is cool is let's say we have our pro plan just like this. But let's say we also have a premium plans. If I just swap out the pro here for premium just like that. And then basically what you can do and have control over this is just based on having that authentication as well as billing logic we're going to be able to have these really nice abstractions where we can say okay based on whether the user is a pro member or have the pro status render that type of component or page or if they're a premium user render this entirely different section. Otherwise just render the following. Overall there are a ton of different ways in terms of how you can leverage this. I just wanted to give you a handful. The last thing that I want to show you is how we can easily store all of these different audio files and correlate them with each respective user. I'm going to show you how to set up Convex. And then once that is done, we basically have an application that you can begin to push to production. Once it's ready, we can push the Verscell app. We can deploy Convex. And additionally, we can go ahead and push Clerk live. And that will effectively allow us to have a SAS application. Let's dive into the Convex portion. Next up, we're going to be leveraging Convex within our application. And specifically, what we're going to leverage within it is both their database as well as their file storage system. All that we need to do to add Convex to our application is we can MPX Convexdev. And then once we do that, you will be prompted to authenticate if you haven't already from their CLI. All that you need to get started with Convex is you can npx convex dev. And in this case, I'm going to create a new project. Now, if you aren't authenticated, you will just have to go through and in this I'm going to call it clerk 11 labs SAS. And what we'll do within here is we'll select the cloud deployment and it will create this project on convex. The really neat thing with convex and one thing to understand with how it works is now all of the functions for the backend are going to be running on their server. This isn't actually running similar to a nex.js route within our local application. Here we see the convex functions are ready. And how this works is we have this convex directory and within here we can define all of the different schemas that we need as well as everything within it TypeScript for how we want to integrate our backend. Before I dive into this, the one thing that I am going to do is I'm just going to consolidate our environment variables. So this is something I probably could have set on the outset. Nex.js has a convention for the most part of having your env.local. I typically have it in a DMV, but since Convex has generated the local file, I'm going to go ahead and just consolidate them all within there. And just to quickly touch on Convex. So, what will happen is we'll have the URL for our Convex dashboard that will print out here. And what we can do within here is we can check out all of the different tables once we define them. All of our server functions, all of the files, any cron jobs that we have. There's also extensive logging. One thing to know within here is as you're within development, we will have that on the cloud while we're working within development. And then as soon as we're ready, we can push that to production. We do also have the option for the first time that we actually select production that it will provision that instance. And now we can toggle back and forth. And we have the two different environments. And then once we have that, we're going to set it within here. We're going to define a small little configuration under settings and environment variables. This is going to be, as you might expect, where you put all your different environment variables. Now, one that we're going to get right off the bat, just so we don't forget this, is we're going to get the clerk JWT issuer domain. And this effectively tells Convex which provider to trust. What we're going to do is we're going to head back to Clerk. Once we're within Clerk, we can go over to JWT templates. And within here, we can add a new template. All that you need to set up the JWT template is you can name it convex. And then there's also the ability to select convex within the template here. And then as soon as we have that, we can go ahead and save this out. And then the one value that we are going to copy is this issuer domain. Now once we have the issuer domain copied, what we can do is we can go back to the environment variables within convex here. What we're going to do is we're going to type clerk JWT issuer domain and then we're going to paste in our issuer domain just like this. Once we have that, we can go ahead and save this out. Now within our project, what we're going to do is within the convex directory here, we're going to create a new file. In this case, we're going to create an off.config ts file. And then within this file, what we're going to do is we're going to define the provider within this is going to be the environment variable for the JWT issuer domain. And then the application ID just like you saw me set up is convex. That's going to be the identifier. This has to match exactly that JWT template that we created on clerk. Once we have that, we can go ahead and save this out. And the other thing that I do want to flag is, let's say we stopped convex. What we can do to restart this is I can mpx convex dev. And that's going to be how we have the convex server running in the background. Next, what we're going to do is we're going to create a providers.tsx. Within here, what we're going to do is we're going to define this to use the client. And then we're going to import convex react client, convex provider with clerk, as well as use o from clerk. And then from here we're going to set up a browser client for convex that uses our convex URL. And then from there all that we're going to do is we're going to define this providers function. And then within here we're going to call this convex providers with clerk. And then we're going to pass in the props of client with convex as well as use o from the clerk package for nex.js. We can go over to our layout.tsx. Within here what we're going to do is we're going to actually add in that provider that we had just defined. For instance, what we can do is within the main content, we can actually wrap the providers that we had just defined around our main contents of our application. So within here, this is going to be where potential client components do render. And of course, we do just want to make sure that we import providers from providers where we had just defined. Now comes the fun part with convex. Within Convex, what we can do within the directory is we can define the schema for the different tables that we want to define. All that we need to do to define tables within convex is we can do something like TTS logs and then you can say define and then you have this nice intellisense autocomplete here where we can define our tables and then within here we can specify the different values. Say we have user ID. you'll be able to reference V for the convex values and then we can define the type here within here we have all of the different types that we can go ahead and select from say if this is a string we can go ahead just like that and then add in our values one by one okay so for the TPS logs table what we're going to have is we're going to have the user ID we're going to have the text we're going to have the bytes in terms of how big the file is we're also going to have the milliseconds for how long the file is as well as the time that it's created and then we'll also set up an index for the users for when it was created as well as for user ID as well as created at. We're going to call this TTS files. And then within this, we're going to have user ID. And then within this table, we're going to have the user ID, text, format, bytes, storage ID as well as created at is you are also going to be able to reference and upload files directly within the platform. You don't need to connect something like an S3 bucket. you're going to be able to add different media files super easily to Convex for when your application does call for it. Now that we have our tables defined, the really great thing with this that I absolutely love, as soon as you save it out, if you go and you look at the Convex dashboard, there are your tables. There's no need to run migrations. Everything is automatically all set up. Everything is type- safe. Everything is defined. The nice thing with this is we have everything predefined within the code. If we take a look at this, we have about 20 lines of code here. Now, all that we need to do to actually have this defined within Convex for their database is we can save it out. And if I hop over to Convex, we can now see we have those two respective tables. Now, if you think about this, it allows us to move really quickly, but also have that type safety throughout our application. There was no need to run migrations. we don't need to worry about previous migrations or different SQL that might have been run on our database where we can potentially lose context if you're not careful and you don't actually remember to add all of that within our application. The one nice thing with this is we have the single source of truth within our schema here and as soon as we update it here it will be reflected within the database. Now additionally another cool thing with convex is what I can do within here is I can also define different functions. Say for instance if I want to have particular texttospech functions. Now that we have the tables defined how do we actually use them? What we can do is we can create a new file and this can be named whatever you want. In this case I'll call it tts.ts. The first thing that we're going to do is we're going to import query as well as mutation. Then from there what we're going to do is we're going to import v again for the different values. And then finally what we're going to do for our first one is we're going to read our users recent logs. This is going to allow within our application, say for instance, if you want to list out all of the different audio files, you can begin to scaffold something out just like that if you'd like. Now, in terms of how to query data within convex, so we have our arguments and then we have the handler. The key piece within here is you see we have the identity of the first thing that we're going to do is we're going to ge
Technical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.