In this chapter, we're going to set up the entire project foundation. A Next.js 16 app with Tailwind version 4 and ShadCNUI component library. Clerk authentication with organization support, making our application multi-tenant by default. Finally, we're going to add a Postgres database with Prisma ORM. By the end of this chapter, you will have a working application with authentication, multi-tenancy, and database models.
Let's get started by confirming we have the prerequisites to start building. Head inside of your terminal and make sure that your node version is above 20.8. If you have a version lower than 20.8, you need to upgrade before you continue this tutorial. As per the other commands, it's not exactly important what version you have because they can be different depending on your operating system. Just make sure you're not getting any errors from running these three commands.
Once we've established that we have the prerequisites, we can go ahead and run the Next.js setup. So using their documentation page, we can find npx create next app as the quick start command. But we are going to do a slight modification. So instead of running at latest, we're going to specify a version 16.1.6. The reason we are doing this is so that it's easier for you to follow this tutorial.
Depending on when you arrive on this tutorial, perhaps the newest latest version is a breaking change making it that much harder for you to follow along. So in order to avoid those headaches, I recommend using the same version as me. And then once you complete the tutorial, give yourself a challenge to upgrade to the latest version. Once we define the version, we need to give our project a name. This can be whatever you want.
I'm going to call it Resonance. Let's go ahead and check the preferences. So I'm going to go down to Customize settings so we can select our own preferences. We're going to select yes for TypeScript, we're going to choose SLint for the linter, we're going to select no for React compiler, yes for Tailwind, and this one is important because more often than not the default option here is no. Use the arrow key to change it to yes.
We will be using the source directory. And make sure you have yes selected for the app router as well. I'm not going to change the import alias so I'm going to leave that to be no. And let's give it a minute to install. Once the app has been installed you will see a success message like this.
Let's go ahead and change directory to be inside of that new project. So change directory to your project name and go ahead and run ls command so you can see the output. If you see something like this or more specifically exactly like this you are in the correct repository. This is important because the terminal will be the place where we're going to execute some commands. So it's important that we are in the right repository, which we've just established.
Great. Now let's go ahead and do the same thing in our editor. For your editor, you can use whatever you want. I'm gonna use a popular one called VS Code. If you wanna use cursor, Zed, Nvim, it's perfectly fine.
So let's just go ahead and open and select our project resonance. And I'm just going to close all of these tabs here. And what we're going to do now is just confirm that we have the same file structure. So make sure you have the source folder and inside and make sure you have the app folder. If you have those two, you are practically done with confirming the entire file structure because those are the two most important folders to have.
The second one I would like to confirm is actually inside of package.json. So in here, just go ahead and confirm your next version once again, 16.1.6. Of course, if you want to use a higher version than this, it's perfectly fine. But as I said, if you have a different major version like 17 or 18, I can guarantee there won't be any breaking changes from the code I will be writing in this tutorial. So if you are on 16.1.7 or 8 it probably doesn't matter.
Even if you are on this one like 16.2 again I don't think it matters that much but major versions these ones might be a problem. Great. And another important thing is to confirm you have Tailwind added. So we are using Tailwind version 4. Perfect.
So once we have that established, we can go ahead and run a chat CN in it. So before we run this command, I think it's fair to go ahead and learn what a chat CN is. So chat CN is not exactly a component library. It's more of a foundation for your design system. So it's a set of beautifully designed components that you can customize, extend, and build on.
I'm going to go ahead and go through the documentation here and I will go through pick your framework, next JS and select NPM. And here it is, npx-shatsien-at-latest-init. And I'm going to do the same thing that I did before. Instead of using latest, I will first check what is the latest version. That's what interests me.
So 3.8.5. This way, if you want to, you can use the exact same version as me. So let's go ahead and make sure you are inside of the resonance project and let's just run init. What this is going to do is first of all it's going to install the package and then it will simply ask us a few questions. It's going to recognize all the files automatically so you don't have to worry about that.
You can see it found Next.js, it found Tailwind version 4, it validated the import alias. Now for the base color I'm just going to choose neutral and that's it. You can see it recognizes our globals.css file, it's going to do some updates, it's going to install some packages. So let's go ahead and have our app open here. We can see the changes, right?
So what are the changes right now? You can see that we have some new packages installed. Let me go ahead and expand this. So we have Class Variance Authority, CLSX, Lucid React, Radix, and Tailwind Merge, as well as a ChatCN and Tailwind Animate CSS. So all of these were added by ChatCN.
Additionally, Globals.CSS was completely reworked. And finally, we also have components.json. Components.json is basically a configuration file of the options we just selected. The base color, our alias, the icon library, and all of that. Basically, not something we're gonna manually modify.
I just want to make you aware of your code changes. And we also have the utils folder. The utils folder, as you can see right here, consists of a singular CN function, which we're going to heavily rely on later in the project. It's basically a way to dynamically write Tailwind classes. All right, So that is a chat CN.
So before we actually do anything, let's go ahead and run the chat CN 3.8.5 add button. What this is going to do is it's going to install one of those foundational components inside of source components UI button. So app folder, my apologies, source components UI should now have a button. And the cool thing is you actually have the entire source code instead of it being hidden inside of node modules. This makes AI agents able to work with it, you able to work with it, basically a perfect foundation system.
Let's go inside of our app folder, page.tsx. And what I like to do here is I like to clear the entire thing and let's render that button and let's right click me. We can remove the image since we're not using it. So page is kind of like the root file of this project. We are going to work with layout but in a moment.
For now let's just focus on app folder page. Save this file and let's go ahead and do npm run dev. And you will have your app running on localhost 3000. So let me just go ahead and have that ready here. And just like that, you should have click me button right here.
And you by default have a couple of variants like destructive, ghost, outline, and you even have some sizes like large, small, or even extra small. And the cool thing is you can always command click to go inside of the source code. It's located inside of components UI button. And in here, if you want to, you can add your own size inside of this object, right? Or you can add your own variant here.
So if I go ahead and add purple, background purple 500 and text white. You can see that immediately here I have purple available. And just like that I've added my own variant. So that's what they mean by foundational library. You will be able to customize it to fit your design.
So now I'm just going to remove it from here. I just wanted to demonstrate to you how it works. Great. So once we have that, what I like to do is I like to add all of the components immediately. You can do that by running chassi and 3.8.5 add dash dash all.
We're not going to need all of them, but I simply think it's easier to work by having all of them available and then cleaning up later. Especially if we are going to have to remember the version 3.8.5 during the entire tutorial, I think it's just easier to add all of the components right now. So you can see that it skipped one file which is button simply because we already added it. And in here it's a general warning that a tooltip component has been added and if you want to use it, you need to wrap your project within a tooltip provider. So that's something to remember if we plan on using that.
And by now you should have a lot of new files here. So those are all of the components you can now use. Besides that, you will also see a hook called UseMobile, which will come in handy. It's kind of like a media query responsive hook. Very useful.
We are gonna use that definitely. All right, so Once we've done that, we initialize chat-cn and we added all components. Now we have to do some key changes here. So I'm going to go ahead and do npm run dev simply so we have our app running. And right now the only thing we see here is a button called click me.
So I'm just going to do some changes in the layout file, which are mostly just design changes. For example, in here we have two fonts defined. And just by looking at this return you can kind of deduct what the this route layout file is. It's basically the entire app parents I guess you could call it container which encapsulates the app with HTML and the body and then renders the first page inside through the children. That's called the root layout and it needs to exist in every Next.js app.
You can see in here we have some reserved constants, reserved constant exports for metadata which give our page a title and description. And here is also where we define the font. So I'm going to go ahead and change from Geist Sans to Inter. So let's go ahead and import inter from next font Google and let's go ahead and change this to be inter and let's go ahead and use inter dot variable. So this is a very subtle font change simply because at the moment we don't really have too many text elements here.
So that is the first key change that we have added. So inter font and now I also want to do another thing inside of the layout while we are here so I don't forget and that is to import toaster. You can import toaster from components UI sonar. The reason I'm doing that now is so I don't forget it later. This basically allows us to fire Toasts.
And we can even try it right now. Let's go ahead and just mark this as Use Client, which enables this app to be reactive and give this button on click elements. And let's import toast from sonar package and let's just do toast.success hello world. So now when you click on click me you will see hello world right here. Let me expand the screen so you can see it a little bit better.
Click me. There we go. Perhaps I need to zoom out even more. There we go. Hello world.
So if you forget to add Sonar in your layout, I mean the toaster, right? If you comment it out, you can see that toasts are not appearing. So since we're gonna be using Sonar a lot, that's why I decided to add it right now. So Components UI Sonar in Layouts and in Page, it's just the Sonar package. All right, so we just finished the layout.tsx.
Now another thing I want to do is I want to change the useMobile hook. So we briefly mentioned this. You now have a hooks folder, use mobile. All of this was added by Shazian UI. And the mobile breakpoint here is 768.
I'm going to change it to 1024, simply because that is the breakpoint we are gonna be working with. So just go ahead and change this to 1024. Great. One last change I wanna do is globals.css. So you don't have to do this if you don't want to.
The only thing this will do is it will change the look of your app, right? Ever so subtly, right? It's not going to be anything too drastic. The problem is we're not going to write this by hand. Using the link on the screen, you can access the source code of this project.
And once you are in the source code, Go inside of source, app folder, globals.css and simply copy my file, delete yours and paste the new one inside. So This is a completely new one that I have just added here. You can verify by this. If you have this button not disabled, you have correctly copied and pasted myglobals.css from the source code. Again, link on the screen so you can access it.
What this does is you can't exactly see it now, but there are some subtle differences. You can see the buttons are a bit more rounded. If I kind of Command Z, you can see how the previous one didn't have as rounded ones. So once you copy myglobals.css and save you will see the buttons are now more rounded. So it's a bunch of subtle changes like that.
For example, the buttons now also have a pointer cursor, whereas the previous version, the default one doesn't. So I basically made that file that you can copy and paste here. Obviously, you don't have to write this by hand, you won't even learn anything by doing so, so you can have the exact same look and feel as me. If, for whatever reason, you're having trouble with this, you really shouldn't because it's open source, you can see the link on the screen and just copy globals.css and paste it in your project, you don't have to do it. It's not going to change the functionality of the project at all.
Alright, so those are the three key changes. Global CSS, layout.tsx, and useMobile. NPM run dev works, InterFont is rendering, and Shadow components are importable. Perfect. So what we're going to do now is we're going to protect our app with authentication.
So let's start by running npm install clerk forward slash next JS npm install at clerk forward slash next JS. Let me just confirm that. There we go. And once that is installed, we're going to go ahead and set up our app. So what I want to start with is the layout file once again.
So let's go inside of layout.tsx and let's go ahead and import clerk provider from our newly installed package clerk-next.js And let's go ahead and wrap the entire app around the clerk provider. I mean, let's wrap the clerk provider around the entire app. Like that. Then, let's go ahead and make sure we have our app running. And let me go ahead and refresh.
All right. And in here, you can see that Clerk has now entered keyless mode. So we briefly saw that error about not having keys. So let's go ahead and just add keys so we can immediately claim this application or if you are getting that error and you can't get rid of it using the link on the screen you can visit clerk.com so let's go ahead and sign in and enter the dashboard. So in this dashboard I already have some projects.
If you don't have any you will most likely just see this startup screen. So I'm going to call this project resonance. I will select the email provider and the Google provider. But you can see you have many more options. So feel free to select the ones that make the most sense for your project.
Let's go ahead and hit create application. Let's go ahead and make sure next JS is selected here. So we already installed next JS so we don't have to do that. The only thing we have to do is copy the keys. So I'm just going to go ahead and copy these and then we're gonna go ahead in the root of our app and we're going to create a new file .environment and just paste next public clerk publishable key and clerk secret key.
One important thing is that this .environment file is, if you're using VS Code, it should be kind of grayed out. And in your git ignore, you should very clearly see .environment file as ignored. So it's never committed to your GitHub repository because these are important secrets here. And your gitignore is also now updated to feature the clerk configuration. So that's a hidden folder.
And inside of here, it's simply entered the keyless mode. And now once you've done that let's go ahead and refresh the app again and you can see we no longer have that warning here. So not much is happening right now because we don't really have anything established yet, right? We don't have any rules made. So we have to go ahead and create a middleware.
In previous versions this was called middleware.ts but in Next.js 16 and onwards it's been renamed to proxy which actually more accurately describes what this file is. Just make sure you create it within the source folder. If it's outside of the source folder, it's not going to work. So in here, I'm just gonna go ahead and import the following, following two functions from clerk next JS server, That's clerk middleware and create route matcher. Then I'm going to go ahead and establish the public routes using create route matcher.
The public routes basically means anyone can visit these routes, both logged in and logged out folks. Well, actually only logged out people should be able to visit these, right? And then I'm just gonna add another one, which is the organization selection route, which will be forward slash organization selection. If you're wondering what are these parentheses and asterisks, it's basically just to cover all the additional routes after that. And now in here we have to go ahead and export the actual Clark middleware.
What we're going to do here is we're going to extract user ID and organization ID from ALF. Make sure to await it. And the first thing we're going to do is we're going to allow public routes. So we also have to import next response. My apologies.
We can do that from next server. So if the user hits a public route, which is either sign in or sign up. Let's just go ahead and continue with our work. Then let's go ahead and let's protect non-public routes. So if we don't have user ID, let's trigger auth.protect, which is just gonna do its job.
And then let's go ahead and allow an organization select route. So if the user is logged in and it attempts to visit organization select route, let's make sure we allow them to do so. And now let's do the following. For every single protected route, and all of those protected routes are basically any route besides this one, this one, and this one. So we are using reverse logic here.
So we don't have to manually specify which ones are protected. All of them are protected besides sign in, sign up and organization selection. So if the user attempts to visit any of those protected apps we need to make sure that they have an organization selected. So if the user is logged in but the user doesn't have organization selected, we have to go ahead and redirect the user to our org selection. So make sure you use the new URL constructor here like this and then let's use the next response.redirect and redirect them to the org selection and finally let's go ahead and return next response.next.
Now what we have to do here is we have to add this matcher. And I know, how did I get this? Where did I get this confusing line? Don't worry, you can find this in the actual Clerk middleware proxy here. So we were just following these Clerk API keys and in here you can see proxy.ts.
You can see this one is simpler. The only thing that actually need to copy here is export config. You can copy this entire thing. That's where I got it from. So just go ahead and paste it.
Great. And you can see the warning that I told you about. So previously the file was called middleware but now it's called proxy. Perfect. So immediately what you're going to notice now is that let me just go ahead and I have a bunch of apps open.
I'm just going to close some of them. If you attempt to go to localhost 3000, you are immediately redirected to a login page. So I'm just going to go ahead and sign in with Google here. And once I sign in, you're going to see that we're going to be redirected most likely to a 404 page. But take a look at the URL.
The URL is actually organization selection. So it's working as intended. If the user is not logged in, we can actually see that here. I think this makes the most sense. So we just added the environment keys and We didn't create these pages yet, but we're going to add them.
But here is the flow, the proxy. We have a request, and we check if the route is public. If it's not public, we check if the user is logged in. If the user is logged in, we go ahead and check if the user has an organization. And if it has an organization, we allow it.
So that's the happy path, right? So let's go ahead and do the other thing. If it's not public and the user is not logged in, we go ahead and redirect to a sign-in page. Then if the user is logged in, we check if the user has an organization selected. And if that is no, we redirect a logged in user to organization selection.
So that is what's happening right now. So let's go ahead and create these pages sign in, sign up and org selection. Now you probably noticed that we actually already have sign in and sign up pages but you also probably saw that those are on a different URL so I'm going to show you how you can host your own sign in and sign up pages. Let's go ahead inside of app folder here and I will create a new folder called sign in and inside of here I'm gonna go ahead and add a catch all route sign in like this and then page.tsx. You can see that I'm using the same file name as this page.
So page is a reserved file name in Next.js which basically enables you to do client-side routing. Sign-in is basically the name of the route. So this will be an equivalent to sign-in. And if you're wondering what this is, this is just a catch-all route. So whatever params, whatever dynamic parts, we simply want to make sure that this page is rendered on every single variation of sign-in route.
That's what that means. In here, we're going to go ahead and import sign-in from Clerk Next.js And then we're going to do a default export called sign in page. We're going to add a simple div with flex and minimum height of screen items center, justify center and bg of background and we're going to render clerk's sign in page. The clerk's sign in page will also have an appearance prop. Inside of here we're going to target elements and give the root box a class name of mx-auto and card shadow large.
So let's go ahead and save that file. Let's copy this, let's paste it, let's rename it to SignUp. If you have this prompt, which basically asks you if you want to update imports for signup Well, I just declined it it doesn't matter basically it's cache And make sure you also change the inner one to be sign up. You can select yes, you can select no, it doesn't matter. You can see it will just open this weird .next file.
It really doesn't matter. You can save this, close this and just make sure you close .next. This is just cache. It will regenerate every time. Don't worry about it.
Let's go back and focus on the sign up one. So the sign up will be identical. The only difference is we're going to change these three instances of Sign in to be sign up. As simple as that. Great.
So those were an easy sign up and sign in routes. And now let's go ahead and create org selection. Org selection will be a little bit simpler. It's just going to have a page.tsx. The reason these ones are having this, which I completely agree can be confusing, especially if you're a beginner and never seen this before.
So that's basically just me following the documentation. So let me go ahead and click on something. So I open Cleric documentation here. So next JS SDK reference. And let me go ahead and zoom out just a bit so I can find guides on building your own pages.
Authentication flows and here we have custom sign in or up page. So here it is from their documentation. You can see that you need to render it on a next. J.S. Optional catch or route.
So that's what this is. That's where I got that information from. And why does org selection not have that? Well, because org selection is not a part of official Clerk documentation flow. It's simply a way that we are going to do it.
So that's why this one is simpler. So let's now focus back on the org selection here. Let's go ahead and import organization list from clerk next JS. Let's go ahead and export default org selection page. And let's go ahead and return some JSX inside.
We're going to start by adding a very simple container, exactly the same as our sign-in and sign-up pages. Flex, full height, items, center, justify center, and bg background. And inside of here, we're going to render organization list. We're going to hide personal. We're going to actually you can do just this you don't have to explicitly write true.
This basically means the same thing. I'm going to add after create organization url after select organization url and I'm going to modify the appearance. So elements, root box, and card. Great. And once you save this file, you can actually go back to your localhost here.
And let me just go ahead. And there we go. You will now, after you refresh, see the following. Here's the thing. Organizations are not enabled by default in Clerk.
And thanks to their amazing SDK, you can actually enable it through your app. But just in case you are not seeing this message, here's how you do it through their application. So you simply click on organizations here and you hit enable organizations and select membership required. And let's go ahead and click enable. And then you can refresh this once again.
And in here you can see we need to set up our organization. So this is for our existing logged in user. So I'm going to go ahead and click join organization. And there we go. This is the page we just developed.
Choose an organization. And finally, now we can visit our localhost 3000 with a button which says click me. So that's everything we had to do. Super simple, and we now have extremely secure authentication. We have multi-tenancy built in, and we have brilliant developer experience.
Amazing. So, and I explained the flow. So the user can only use this application if they log in and if they select an organization. So if any of those are missing, we are not going to allow them to use this application. And yes, here is the note I wrote, I forgot to explain it to you.
So the sign in is an optional catch all route for clerk multi step flows. That's why it is required. And if you're wondering why did we need the hide personal on the organization list, it's because we wanna make this app team-based by default. Even if the user is a solo user, we're simply going to force a solo organization then. This makes it way easier to work with, and this is a industry standard practice when it comes to multi-tenancy.
Perfect. Now, what I want to do is I want to modify the page.dsx so you can see exactly how to log and some other Clerk components. So let's head inside of app folder page.tsx right here. And let's import organization switcher and user button from clerk next JS. In here, let's export default function home.
And what I'm going to do is I'm just going to add a simple div with flex full height flex call item center justify center some gap and the background here. Then I'm going to add a simple title which will say welcome to resonance. And let me go ahead and refresh this so we can actually see that. So welcome to resonance. And then let's go ahead and add a div flex items center and gap for and inside the organization switcher and the user button and just like that you can see this user's organization and these users account and this is really cool You can see that by using Clerk, you also immediately have account info, settings.
You can even add more providers from here. You can verify more email addresses. So a full out system. This is more than just a simple out library. This is an entire user management system.
So on your dashboard side in here, you can ban users, impersonate users. So many things you can do from here. You can track their activity. You can help them debug things. You can add social accounts for them, give them passwords.
You can track their devices. You can assign metadata. Basically every single thing you could possibly imagine, Clerk allows you to do that in an extremely secure, compliant, up-to-date way. Basically by using Clerk, it's almost like you hired a professional security team for you. And you can see we even have multi-tenancy enabled here by default, which means we can immediately start inviting new users, right?
So, johndotest.com, you can go ahead and give them a role of member or an admin, send invitation, and they will get an invitation on their email, but also If they just log in into the app, they will see an invite happening here and they will be able to accept it from here. So, so many things was actually added with just introducing Clerk to our project. Brilliant. So, I believe that is everything we needed to do when it comes to clerk authentication. So now it's time for the last part, which is our Prisma database.
So let's go ahead and implement that. So let's start by adding the runtime dependencies. I'm going to go ahead inside of my terminal here and I'm going to install the following packages. Prisma Adapter Postgres, Prisma Client, T3 OSS Environment Next.js, and the PG Postgres. So out of all of these, the only one not exactly specific to Prisma and database is this one.
I will explain what this is. It's going to help us a lot, especially in a tutorial environment where people often forget to use their environment variables. So since we're slowly going to be working more and more with environment variables, this is a package that helps a lot by breaking your app if you forget to add an environment variable. So that's why we're going to add this package. And yes, you're reading this right.
If you're familiar with Theo T3, that is their open source package and it's really good. So again, to repeat Prisma adapter Postgres, Prisma Client, T3 OSS environment Next.js and Postgres itself. Let's install that. And then another thing we're gonna have to do is to install dev dependencies. So that was step one.
Step two is to install dev, prisma, types Postgres, dot environment and tsx. So we're not going to need these at runtime, we're going to need these for development purposes. After you've run these commands, what I like to do is verify them inside of my package.json here. So naturally you have a lot of new files here simply because of all the chat C and cascading but take a look at Prisma adapter Postgres, take a look at Prisma Client, T3 OSS and down here take a look at Prisma and TSX. I believe those along with some types are the ones we have added and Postgres of course.
So it's not exactly important that you have the same versions as me here. I just want to make you aware what my versions are here. The ones that are actually important are Prisma ones, but only the major version. So Prisma recently went up major version from six to seven. So in case you had some older cache, which is still keeping you at version 6, perhaps it would be a good idea to repeat these and just add, add latest, right?
So you have the newest version or you can specify the exact version that I am using at the time of this tutorial. So yeah, if you're on a higher version, I don't think it's that big of a problem. Again, except if you have a different major version, again, that is something that's out of my control. But if it's just some minor changes here, I don't think you will have any problem whatsoever. Great.
So that is the base foundation of packages. So what we have to do now is we have to add a database. Now with Prisma, you actually have two ways of doing that and both are insanely fast. So using the link on the screen, you can visit Prisma Postgres website here and you can see that you have two ways of doing this. You can simply run npx prisma init dash dash database, or you can manually create a database from here.
Whatever one you prefer, I would suggest that you log in first because by logging in, you will basically be able to look at your databases here as well as on your local instance. So once you're logged in, you can see I have some projects here, which I was testing. So again, you can create a new project from here as well. So let's go ahead and try by copying this command simply to see it in action. Npx prisma init –database that is the one we are trying to do.
And if you are running this for the first time you will most likely have to verify your account. That's why I told you to log in first. So you can easily just verify your account. In here, you can select your region. You select where your users are, not where you are.
In my case, I'm the only user, so I'm going to choose Europe, but you usually select where your users will be. I'm going to give this a name of Resonance. That is the name of my database. And now it's creating this project. So in here, it has now added even more files.
You will notice the Prisma folder and schema.prisma with a very simple generator client data source database right here and besides that let's also see what else was added. Prisma.config.ts was added as well. In here, you can see that this file was generated by Prisma and assumes you have installed the following. Npm install save-dev prisma.environment which was our first step before we installed Prisma so we don't have to worry about that. If you want to confirm, it's always a good idea.
Let's search for dot environment. You can see we have it in dev dependencies. Let's search for Prisma. We have it in dev dependencies as well, so we are good to go. And in here, there's something interesting, and that is the data source URL.
You can see it's targeting process.environment database URL which means that this npx prisma init has probably created a database for us and also modified our environment file with database URL environment variable. So let's revisit .environment and that's exactly what's happening this was inserted by Prisma in it, you can see nothing was reverted from our previous changes and basically we now have a database URL. It goes without saying you should not be sharing your database URL with anyone. I'm doing this for tutorial purposes and we'll remove this later. Excellent.
So now that we have done that, let's just go ahead and I'm looking at my generated folder. It doesn't seem to appear anywhere yet. So that's perfectly fine. Let's go ahead and do what they tell us to do and that is to create the first migration. So I'm going to go ahead and run npx prisma migrate dev with name init.
So that should just do a light migration of the current state of the Prisma schema. Let's give it a second to do. There we go. Already in sync. No schema change or pending migrations have been found.
All right. Perhaps we should first modify our schema. I believe that is the case. Okay. So we confirmed that we have Prisma.config.
You should have that too. And you should also have Prisma.schema. So let's go ahead and add the models that we need here. So we're going to start by adding a voice variant, enum. So every voice in our project can either be a system voice or a custom voice.
That's the first thing. Then we're going to add an enum for voice category. What category does a voice belong to? And this can be, Basically, it can be your own list of things, but I'm going to add the following list simply because that is from the data set of voices that I will share with you. So because of that, I recommend that you add the same ones too.
So audiobook, conversational, customer service, general, narrative, characters, meditation, motivational, podcast, advertising, voiceover, and corporate. Perfect. Now let's go ahead and let's set up a voice model. So what we did before were prerequisites in order to build the voice model. Now let's actually do it.
Let's go ahead and make sure that this model has an ID of cuid. Let's go ahead and give organization ID an optional property. So it's a string but it's optional. What this means is that if user creates a voice, we're gonna attach an organization ID to it. But the reason it's optional, it's not because we're gonna allow users to create voices outside of the organization.
No, if organization ID is not present, it simply means it's a system voice. It's built in, right? Because we need to offer our users some built in voices along with their custom cloned voices. And now let's go ahead and just add some generic properties. For example, the name of the voice, the description, which can be optional.
And now let's reuse the enum from above, voice category, which we just built. And by default, we're going to set each voice to be general. Then let's go ahead and add a language which is a required string and by default we're going to use a locale shorthand so N English US. Then let's go ahead and again use an enum from above which is the variant. Is this a custom voice or is it a system voice?
Then we're gonna add an R2 object key. This will be a reference to our storage where we're going to store the audio example of this voice. This can be any S3 compatible volume because we're going to be using AWS S3 packages in this project but you can deduct by the name of this field. I will be teaching you how to use Cloudflare R2 in this project for upload. Don't worry, we're not going to do that in this chapter.
We're just preparing for it later on. And let's go ahead and add created at and updated at, which are date time, and they're using these decorators, default now and updated at. And let's also add indexes on the variant and organization ID to make queries for these more performant. One thing I forgot to mention is the syntax of this file. If you're not seeing these bright colors as I am, go ahead and search for Prisma inside of the extensions and simply install the Prisma extension.
Alright, so now that we have this, we have to do the same for the generation model. So let's start with the ID, which is identical to our voice one. The only difference here for the organization ID is that it's absolutely required. So only organizations can create generations. Generations are basically text to speech prompts.
If I type something like, hello, my name is Antonio, and I choose a voice and click generate, that's what this is. That's a generation, right? A finished prompt. And I just wanna, you know, well, I think it's better that I finish writing and then I'm going to go over key design decisions here. So let's now create a relation with the voice.
So each generation needs to have a voice assigned to it. So this will actually be optional. So voice ID string is optional and the reason it's optional is because of the following line. So what we're doing here is we are establishing a relation with the generation and the voice and again it needs to be optional here. Using the relation decorator we specify exactly what field we're doing the relation to.
So this voice ID will be connecting to ID of the voice. So that's the references. And here's the important part, the onDelete. The onDelete is usually set to cascade. What would that mean?
So yes, you can also use cascade here. What does that mean? That would mean if a voice is deleted from a database, every single generation done with that voice gets deleted as well. In most cases, that's not what you want. That's why we are using set null.
So if someone deletes a voice from a database, we're not going to delete all of the generations. And that's why we need to make voice optional. So what are we going to do if a voice gets deleted? Well, we will have something called voice name, like this, That is always required. So even if the original voice relation gets deleted, we are still going to have something to display to our users, at least in a way like This is the voice that had been used at the time, the voice is now deleted.
And we're also going to have the actual text. This will represent the prompt. What is this generation saying? Like, Hello, my name is Antonio. That's what the text is going to be, what the user will type into the prompt.
And in here, we're going to need an optional R2 object key. Why do we need it to be optional? Not because It doesn't need to exist, but because we will create a generation before the upload finishes. So if an R2 object key doesn't exist, we are very simply not going to display this to anyone. It will be considered unfinished.
If R2 object key exists, it means it is uploaded and ready to listen to. And now we're simply gonna add some parameters depending on the generation. So those are temperature, which is a float, top p, which is a float, top k, which is an integer, and repetition penalty, which is a float. You don't have to think too much about these values. They are mostly relevant to the AI model which we're going to use to generate text to speech, which is open source chatterbox, which we're gonna self-host later.
So now let's go ahead and let's add createdAt and updatedAt and let's add the indexes. And in order to resolve this error we have to establish a relation in the voice model itself. So before the timestamps, let's go ahead and add generations. So a voice can have many generations, but a generation can only have one voice. All right, that's the type of relation this is.
So make sure to put an array here and save the file. And just like that you should have no more errors here. All right, so now let's go ahead and run npx prisma migrate dev. Let me see if I can reuse the name in it. I think I should be able to see because nothing happened last time.
So make sure you save that file and there we go. Applying migration. Perfect. And now let's also do npx prisma generate. This will generate the Prisma client inside of source generated Prisma.
So you will see two things. You now have migrations folder here and you also have generated folder here. So migrations are used well for database migrations. They are something needed. It's a whole another thing to explain, so I'm just gonna stop here.
I basically wanna explain the difference, why we are committing migrations. You can see that these are not grayed out. These are committed. These are part of our changed files, but generated folder is grayed out. And if you look at git ignore, you will see that we have source generated Prisma here.
That is because this is only used for type safety throughout our project. So it's not exactly if I can say so, not used at runtime, right? So without this folder, we cannot develop, but technically the app still works without it, if that makes sense. It's kind of like guessing. If you don't have this generated folder, you could technically have all of your queries working, but you would be guessing if they are correct or not.
That's what the generated folder is for. It's basically like a bunch of types which read from our schema. So once we've added these two models, a bunch of type safety functions have just been generated in that folder and the relations and all of the fields, it's basically almost magical what was just done in that folder alright so now that we have that let's go ahead and do the following let's go ahead and create our database instance so we can actually query some things. So I'm going to go inside of lib folder and create a new file database.ts so right next to our utils and in here I'm going to go ahead and import Prisma Client from generated Prisma Client. So this generated folder is exactly what we were just looking at.
So inside of source, generated Prisma, and in here we have client. So that's what this is. You can see it's a very big type definition. And let's also import Prisma Postgres from app-prisma-adapter-postgres. And then let's go ahead and define the adapter new Prisma Postgres connection string and let's do process dot environment database URL.
And now what we have to do is the following. We have to create a singleton. Basically, let's add a constant global for Prisma. It's going to use the existing global variable. It's going to typecast it as unknown and then as an object which holds Prisma Client inside.
And then what we're gonna do is we're going to define a new constant called Prisma which will use global for Prisma dot Prisma. It will attempt to access it. If it doesn't exist, it's going to create a new instance of Prisma client with the adapter. And then what we're going to do is we're going to check if we are not in production, we're going to store this new Prisma instance inside of global for Prisma dot Prisma. And I know, I know, why are we doing this?
This is very complicated for such a simple database instance. Why couldn't we just do this? Why can't we just initiate? Let me try and show you. Why couldn't we just do this, right?
This is what will happen in production this is exactly how production will look like but because we are using next.js or more specifically because we have hot reload enabled we need to create this trick here. Yes, let's make sure we have this, otherwise it wouldn't work. The reason we are doing this trick, here is an explanation. We are basically doing this to prevent connection pool exhaustion during Next.js hot reload. Basically, when Next.js hot reloads, which is every single time you save a file, a new Prisma client instance would get created.
:02 And this would very quickly reach its limits with the pool and you will get a warning in your terminal here that something's wrong, that you are initializing Prisma too many times. Because of that, we are storing Prisma inside of global because global, which is a shorthand for window.global, that's why it's available, is unaffected by hot reload. So it's a safe place to store it. And this isn't a hack that I made up, this is the official documentation. You can definitely find it in the docs.
:35 Okay, so here's what I want to do now. Here's the thing. This adapter can now work. Okay, I'm thinking maybe it's not the best time to explain this right now. One thing that I want to do right now, I'm jumping everywhere right now, sorry.
:54 But we haven't really touched... Where is the T3 OSS? We haven't really touched this package that I told you to install. So I don't want you to think that I've forgotten about it. I will demonstrate on this example why we need it.
:10 But before we do it, let's actually try and do something. Let's go ahead in here and let's run npx prisma studio. And this will open prisma studio in here. And you can see we have an existing prisma migration, but we don't have any generation or voice. And you should see the exact same thing in your Prisma data platform.
:39 So here it is, actually I'm already logged in. So Resonance, 18 minutes ago. You can see I also have a studio in here. So if for whatever reason your local studio is not working, you can visit the online studio here. But let's go ahead and go to localhost 5173.
:02 Actually not 5173, it's 51211. Okay. There we go. So what I want to do now is I want you to click on Voice. And then I want you to click on Insert Row.
:17 You can leave ID to be an empty string. And you can go ahead and find the name. And let's go ahead and give this a name. Antonio. And Let's go ahead and give this a variant of system and click save changes and click save and you should get the message rows inserted successfully.
:40 So we just manually added something to our database So now we can go ahead and try and query it. Let me just refresh in this other page to confirm. There we go. So this is what I've just added, an Antonio voice. All right.
:57 So now that we have done that, let's go ahead and create a simple test page to learn how to use this new database lib. So inside of the app folder, I'm going to go ahead and create a new folder called test and inside a new file page.tsx. I'm going to import Prisma from lib database and I'm going to export a default asynchronous function test page. It's very important this is an asynchronous function and it's very important this is called page. It's also very important you are doing this within the app folder and in here I'm simply gonna do const voices await prisma and you can see how I have type safety on my models.
:49 So I can go ahead and do find many here. I can even go ahead and open where r2 object key and specify something, right? So you can see how every single property exists here. That is the power of that hidden generated folder. That's why we added it.
:11 So it analyzed this schema and it created an entire type safety environment for us to work with. So now in here we fetch all voices. So let's go ahead and just very simply return a super simple div padding eight. Let's go ahead and add a heading which will simply render the voices and then inside of parentheses render the voices length. And then let's open an unordered list with some spacing and iterate over our voices using dot map and create a list item with key voice ID and list voice name next to voice variant and click Save.
:01 So this is a server component which has access to the database. This is essentially the same thing as building an API endpoint. So this isn't exactly rendered on the client. You don't have to worry about this being exposed in any way. It is absolutely not.
:16 React Server components are in itself a safe environment. Let's go ahead and visit this on localhost 3000. So localhost 3000 forward slash test. So localhost 3000 forward slash test. And in here, you should see Antonio dash system.
:41 If you see an error here, it is because of the database string. And it's just a security warning that the SSL mode prefer require and verify are treated as aliases. So not something that's important for our use case. And in here you can see Voices Antonio system. So if you go ahead inside of your Prisma Studio and if you insert another row here, let me go ahead and change this to full for example, click save changes, Go to variant and select this one to be custom save changes and click save Okay, it needs to be system save changes Okay, it keeps failing I am not sure why Let me try refreshing this insert row Because we just managed to do the first one so I don't see a reason This one would fail.
:48 Let me try bar. Maybe that's the better one. Okay, it's failing. Oh, oops. I understand why.
:00 Let's go inside of voice. It is because it cannot have the same ID. So let's just change the ID to 123. And let's change this to Foo. And let's change this to Custom.
:15 And let's try again. There we go. Now it works. And if you refresh, you now have two voices. So Antonio and Fu.
:24 So we are officially able to query our database. Amazing! And now I can demonstrate what I wanted to do. So let's say for example inside of your lib database you made a mistake and you accidentally wrote database URI instead of URL. So what this would basically do, what it's supposed to do, it's supposed to break your app, but I think cache is saving us right now.
:56 Let me go ahead and try and do rmrf.next. Npm run dev. This is definitely supposed to fail. There we go. You can see now my app is failing so if I fix this back to database URL and refresh I think I have to do it this again.
:21 Rmrf.next and pm run dev. Rmrf.next is basically a super simple like cache purge. Now it's working again. So that's one example where this can happen. But the scary thing is, there is no error within our IDE that we made a mistake here.
:41 But that's not the only place this can happen. For example, if I go inside of dot environment here, what if I change it here? Right? There are many places where this type of error can appear. And this is where our t3 package comes in handy.
:00 So let's go ahead and add it inside of lib we're gonna go ahead and create environment.ts let's go ahead and let's import z from Zod you already have Zod installed you can confirm that by searching for Zod and it came installed with chatzn so that's why you have that And then let's go ahead and create environment here. And let's add some server environment variables. I'm gonna add database URL, z.string and the minimum length of one. Experimental runtime environment will be empty and skip validation will be true by using double exclamation points by checking for process.environment.skipEnvironmentValidation. This will be important for production.
:16 So production doesn't break because production can't read from the local environment file. It reads environment file differently. All right, so now that we have this environment file, which has database URL defined as it needs to exist, we can go inside of our db.ts and instead of using process.environment with not type save database URL, we can now go ahead and import environment and have type save database URL, reducing our chances of making an error. So if I attempt to do the same thing, you can see that now we have a big error in our editor, but that's not the only thing. Now also if I can demonstrate this, if I go ahead inside of my dot environment and if I do a mistake here you can see I get an error invalid environment variables so because we expect database URL to be here I need to make sure that my environment variable doesn't have a typo either.
:33 So we now have both validation within our code editor but also within the actual dev server. You can see it's telling us that we are missing something, right? Invalid environment variables. Amazing. So now let's just go ahead and just quickly go over our database schema decisions here.
:58 So most of them I think I've explained there. Basically, if a voice is deleted, we're going to set null on the generation relation and we're going to rely on the voice name to tell the user what the voice was at the time. And then for the organization ID, it can be optional on voice because that's a system voice. Organization ID is required on generation because only organizations can do them. And R2 object key is Cloudflare R2 path, but as I explained, this can be whatever you want.
:35 This can be any S3 compatible object. And that's the infrastructure. So, T3, environments.validation, database.ts.prisma.singleton and finally, Prisma Postgres adapter with a direct Postgres connection. We are able to migrate, we are able to run Prisma Studio and test page renders the voice list. So we have officially finished chapter one.
:02 Before we end the chapter, what we have to do is we have to create a GitHub repository so we can keep track of these changes. This will be very, very good for you to do. It's not required. If you want to, you can just pause here and go to the next chapter of course, but I would highly recommend following me and see how I'm going to add this to GitHub. Because I'm also going to show you how you can immediately deploy this so you can see the changes on a real production instance.
:36 Before we can push this to a GitHub repository, I suggest doing the following thing. Again, if you want to avoid GitHub repositories and all of this, you can. But I think it's always a good idea to have a fully buildable and no lint error app before each push. So I'm gonna go ahead and do npm run lint to see if I'm getting any errors here. For example, you can see that some components from chat-cn have errors, which means that my npm run build will fail in the same fashion.
:17 Well, actually it didn't. Looks like they've made some differences between what is passable or not. My apologies. But I still think we should fix this, especially if you plan on doing some agentic work here, it's going to be very annoying that you have failing lints. So let's just do this together.
:36 And these are actually very simple fixes. So source components UI sidebar, you can find it here source components UI sidebar, as you can see has some errors. Using this I can find the error right here, and here's what I like to do for these. I just like to use quick fix and disable React, disable whatever it is for the entire file. So you can see that now I just have that at the top of my file.
:05 And now I no longer have any errors inside. So let me just go ahead and do npm run lint. And I'm going to do this until everything works. And it looks like that was the only thing. There seems to be one warning here.
:22 And let me do npm run build to see if that will pass or not. Looks like that is passing too. If you want to, what I sometimes suggest, it's just going through these files. If you're using VS Code, it's automatically going to notice an error, right? You can see how they are becoming orange.
:47 That's because we have some warnings, but warnings don't break things. So it looks like ShadCN 3.8.5 mostly has fully functional, I mean, all of them are fully functional. I meant to say 99% of them are clean when it comes to linting. It is because Shazian uses erratics and some other dependencies which sometimes skew version wise and that's where these errors appear. So since we added all of these components, I think it's a good idea to just, you know, make sure that IDE is not throwing any errors.
:26 Looks like sidebar really was the only one with that simple math dot random error with react purity. So there we go. We are clean. Everything here works just fine. Beautiful.
:43 So let's go ahead and let's create a new repository here. And I'm going to go ahead and select my organization here. I'm going to call this resonance. I'm going to keep it private. I'm not going to add anything.
:00 I'm just going to go ahead and click create repository. Since this is an existing repository, I'm just going to go ahead and click create repository. Since this is an existing repository, I'm just going to go ahead and copy this. And I'm going to do Git. So what we have to do before pasting these three is we have to actually stage these changes and commit them because right now they're just changes.
:21 You can of course use this interface too but I would suggest learning git CLI so you don't depend on an IDE. So let's use git add dot to add all of our changes. So 76 files. Then let's do git commit. I'm gonna do 01, and this will be project setup, Authentication and database.
:50 There we go. So we just committed all of those things. Perfect. And now we can go ahead and copy these three lines, paste them here and that will synchronize that entire thing. So you can go ahead and refresh and there we go.
:09 We just did the very first commit, zero one project setup, authentication and database. Now that we have the repository set up, we are ready to deploy this application. The cloud service of choice is going to be Railway. Railway owns and operates their own hardware instead of reselling AWS like most platforms. So you're not paying that middleman markup.
:37 They have a free tier to get you started and their hobby plan is just $5 a month. And for $5, you're getting way more what you'd get from a random $5 VPS. We're talking auto scaling, one-click databases, background jobs, cron jobs, deploy previews, all built in. And unlike serverless platforms where you hit timeout limits and can't even run things like live chat or real-time features, Railway lets you deploy your entire app in one place. Thanks to not being serverless, it means no cold starts and no limitations you often hit with serverless platforms.
:12 You just pay for what you use by the second, meaning no horror stories of bills being $40, 000 because you got viral. Alright, so using the link on the screen, go ahead and create an account and let's go into our dashboard. In here I'm going to click on new GitHub repository and let's go ahead and find our newly created repository. Once you find your project go ahead and select it right here and what we're gonna have to do now is the following. It's immediately starting to deploy this So it's just added CICD to GitHub.
:49 That's another cool thing. When you refresh here, you will soon see a website you can visit here and you will see the status of your CICD, if it's failing or not. Here it is. You can see deployments are happening. But this first one could fail.
:06 Maybe not. Maybe it will. Because we need to update the variables so using the raw editor here we can share our entire .environment file here so let's find .environment right here and let's copy the entire thing And let's go ahead and paste it like so. And here's what's important. We need to add one more thing that's not in our dot environment.
:34 So if you remember in our environment lib we have this skip environment validation. Let's go ahead and add it here at the end. So skip environment validation will be set to true. So make sure that you've added this, otherwise it is going to keep failing. And click update variables.
:55 And then in the corner here, you can see apply four changes, deploy. So now it's applying those four changes. You can see the previous build has failed. And now we're gonna see why it failed. So let me see.
:11 Oh, I think we also forgot to do one thing. Yes, you can see the error. Can't resolve generated Prisma Client. That's my fault. I forgot to tell you one more thing that we have to do.
:27 So, go inside of your package.json. The problem is this generated folder right here is not committed, which means deployed app doesn't have access to it, which means it's going to keep failing. So we fix this by simply adding post install to Prisma generate. So what we have to do now is save this file, make sure that's the only change, post install. Let's do git add, git commit.
:00 I'm going to keep it at 0 1 and I will just do package Jason post install. And let's go ahead and do git push. And since we've connected the railway with our repository now you can see that that has immediately triggered another redeploy. So that is what we have just achieved by connecting Railway with our project. And I just want to make one thing clear if for whatever reason you don't have access to a free tier or you simply wish to deploy this project somewhere else, you can of course do that.
:37 You don't even have to create the GitHub thing. You don't even have to follow through my cloud provider service here. I would highly recommend that you do, but it will not interfere with you being able to complete this project. Don't worry about that. You will, of course, be able to have this working locally.
:57 Great. So we have two failed ones. And now let's go ahead and see if this one will succeed. I believe npm run build was just successfully built, which means we are on the right track. So let's go ahead and wait a bit for it to complete.
:16 And there we go, deployment successful. So now what we have to do is we have to add a public URL. So let's go ahead and first see whether everything was successful here. Great. And now let's go inside of the settings here.
:36 You can also change the branch this is connected to, of course. And then in here, you can go ahead and find networking. Go ahead and find public networking and click generate domain. And in here you can see it you need to add the port your app is listening to and this is actually not 3000. So you can see inside of deployment here that the app is actually running on 8080.
:09 So whatever they suggest here is actually the correct one. So let's click generate domain. And yes, 8080 is the correct one. So let's click generate domain. There we go.
:21 And let's go ahead and open our app right here. And there we go. Sign in to resonance. I'm going to go ahead and sign in. And since I already have an organization, There we go.
:33 Welcome to Resonance is officially deployed. And now here on our GitHub, you can see that our last commit has successfully passed. So going forward, every single chapter we do in the future, the moment we merge it is automatically going to be deployed. So you can immediately test all of your changes in production. For example, we just tested that clerk works, but how about I go to forward slash test?
:04 Let me zoom in so you can see. So my railway production instance, let's go to forward slash test and here we are. I can see the list of voices. So we successfully tested our app locally and we deployed it on railway. Amazing, amazing job.
:22 So this was heavy foundation, but we did a lot. A lot of these things was just, you know, running commands, doing some slight changes, enabling authentication, multi-tenancy. But we did most of the complicated things in this first chapter, so don't worry. What we're going to spend the next chapters on is mostly UI and design. So we kind of relax our brain a bit from all of these things.
:52 So yes, definitely the next chapters are not going to be as feature heavy. So don't worry about that. Amazing amazing job and see you in the next chapter.