Now let's go ahead and set up a database and drizzle ORM in our project. We're going to set up a Postgres database using NEON. Using the link on the screen, you can get on their homepage. Once you're here, I would highly recommend creating an account first and then once you have an account, go back here and go into the documentation. The reason I'm telling you to create an account first is because once we run this one command setup, it is going to ask you to authenticate.
So you need to have a NEON account ready. So just go ahead and sign up and then go back to documentation. Let's go ahead and use Neon's one command setup to add a database schema and even drizzle ORM to our project. So inside of our terminal let's execute npx neon latest init. Let's select database for the option and then let's select install with defaults meaning MCP server and agent skills.
This will also install a VS Code extension if you are inside of VS Code. And now you have to authorize the Neon CLI. So that's why I told you to create an account first. And then go back here. In here it will output which user you are currently logged in with.
Once it finishes, it's going to look something like this. It's going to tell you what it did, such as confirming whether Neon CTL CLI is up to date, that it added a Neon MCP server for VS Code, and that it installed some agent skills. It will then give you the next steps. Copy the following into your agent chat. If you have a Cloud Code opened at this moment, I would recommend restarting it.
Or if you're using a visual extension like me, go ahead and do reload window so a brand new Cloud Code session opens. And then let's go ahead and copy this and add it to our agent. So paste that prompt into Cloud Code, Open Code, Codex, whatever you are using. And let's see what's going to happen. If you're using Cloud Code, it can sometimes be a bit careful and it's not going to automatically do what we told it to do and it will slightly explain that here.
So it says the following, I see a Neon onboarding prompt suggesting I run Neon CTL Init Agent. Before I run anything, I want to check with you. That command kicks off Neon's cloud onboarding, creating, linking a Neon Postgres project to your account, which is an outward facing action I shouldn't take on my own. So it's basically being careful and just making sure that we want to do this. So what I'm going to do is I'm going to select the first option, run Neon CTL in it.
If your agent did a similar thing but didn't give you these options, you can just manually tell it, run Neon CTL in it or just proceed with the instructions as told, right? You don't need explanation, you don't need to skip anything, so just make sure to run Neon CTL in it. Let's take a look at what it did. So the first thing it attempted to do is run Neon CTL in it and that failed because Neon CTL isn't installed on our machine. So what it did then is it decided to simply run it using NPX.
Then it reloaded my organizations. Since I have a brand new account, I only have one organization. There we go. Only one organization, so it will automatically use that organization. If you happen to have many organizations on Neon, it will probably ask you which one you want to use.
Then it did the same with projects in this organization. Since I have no existing projects, it will simply create a new one called browser automation which matches this directory and my package json. If you would have a lot of projects it would probably ask you if you want to use an existing one or if you wanted to create a new one. And then it created a brand new project for me as you can see here and it updated the neon context file with the information about the organization and the new database here. It also wrote the environment variables to environment local and it also installed the Neon serverless driver.
Brilliant! Looks like it also did a very simple select one against the database to verify that it worked and once it verified the connection it finalized the onboarding and here is that exact summary. So you should have something similar. And the next question it's asking me here is do I want to set up a schema or an ORM next? Before we proceed with setting up a schema and ORM, I want to bring something to your attention.
So right now, if I try to invoke any Neon skill, it actually says I have no matching commands. That is because if you take a look at the files which were added here, we do have some skills, but they were added inside of .agents folder. So these are skills which are available to all other coding agents except Claude. Don't ask me why we don't have one standardized pattern for this but it is the way it is. So that's why we actually can't see any of these two skills.
So to fix that very easily go back to Neon, go into develop and find the AI section. You can click on AI for agents and down here in the table of contents find agent skills. Then click on this right here and that will tell you, that will show you the NPX skills for Neon database. If you wanna use this page here, you should also be able to find it through here. There we go.
Neon database agent skills. Okay, so let's go ahead and simply run this npx skills. Let me close this, let me close that. Npx skills add neon database agent skills and now it's going to ask us which one we want to install. So select neon and select neon Postgres.
Why those two? Because those are the two which have been added in our agents. Okay, so now we want to do the same for Cloud Code. So that's why we want those two. Even though technically we no longer need Neon skill because we just installed it, but still, let's keep it inside just in case.
But the neon postgres might be the more important one. And make sure that the cloud code is selected this time. And make sure you choose the project installation scope and symbolic link. And proceed with the installation. And now you should see .cloud skills for neon and neon postgres.
And now if you go here and if you do reload skills, ignore this, I've been testing whether I have some skills or not. You will see that for me it says no changes because in this exact session I actually did add Neon Postgres but I reverted it but for you it might tell you that you now have one more skill. If you try it will still not be visible until you do reload window or restart your Visual Studio code. Try then and there we go. You will now have Neon and Neon Postgres.
So if you're using the CLI, simply restart the CLI and you will have these two available. So now that we have Neon skills installed, let's go ahead and use one of the prompts that they actually recommend. So this is really cool. At the bottom of this page that we are on, we actually have example prompts and one of them is set up drizzle ORM with neon. Pretty cool.
Let's go ahead and try it out. So set up a drizzle ORM with neon and it might be a good idea to tell it to use the neon postgres skill. Once again, I'm in auto mode, effort high with Opus 4.8. Let's see what it's going to come up with. Let's take a look at the output of this prompt.
So it detected that we invoked the Neon Postgres skill. So the first thing it did was loaded that skill. Then it inspected the current project state. And this is where I was getting a little bit worried because I didn't know where is it going to get the knowledge about the Drizzle ORM because we don't have any drizzle skills, right? So I was worried that it might use some outdated training data.
But look at this. Thankfully, we invoked this skill. That is exactly why they were so confident with giving us this as the example prompt. So it basically analyzed our app. We have app router, Next.js 16, no source folder and we use neon database serverless package, which was installed in this chapter.
We didn't do it manually. We did it using NPX Neon Init. Okay. And it also detected no drizzle yet. And then it said let me fetch the current official Neon Plus Drizzle guide so I match their latest convention.
And would you look at that. Thanks to the Neon Postgres skill, they actually direct the agent to use this guide which teaches the agent how to set up a Drizzle ORM with Neon. Wonderful, wonderful guide here. So it had the entire plan to implement this. So it first decided the code is going to live in the lib folder.
It decided the driver it's going to use. It decided which files it's going to create. So we have lib database schema and lib database index, which exports the database util, a separate drizzle config file, some environment variables like database URL, and it even added scripts like generate, migrate, database push, and database studios. So the first thing it did is it installed all of the packages like drizzle ORM and it installed a developer, sorry, development dependency drizzle kit and .environment. So three packages in total.
Then it wrote the drizzle config.ts, wonderfully I assume, and the database index.ts and then a very simple schema. And then it created all of the scripts here. So overall a very, very, very smooth integration here, better than I expected. Let's take a look at the actual code here. So in here we have the drizzle config.ts, which is just a usual define config.
It targets our schema file in lib database schema, our out in lib database migrations, our dialect set to Postgres, and our database credentials set to be the migration URL, which is essentially reading from the environment file, database URL unpooled or database URL. So yes, that was also added for us. If you do find your dot environment dot local, you will find the database URL and database URL unpooled. So you don't have to worry about that either. So drizzle config, definitely good.
Then let's go ahead and take a look at the other file, which is instead of the lib here, so we now have database folder and in here we have a schema. So this is our schema. It doesn't matter if yours looks like this or not because obviously we are going to change this but it probably did create some schema and it also added an index.ts which is basically just the database util so we can basically import it if we want to import it somewhere. Brilliant And if you take a look inside of your Neon dashboard inside of your project here, so browser automation, that's the project I'm in, go inside of tables and in here you will see that it has successfully pushed the migration of that new schema. So we have ID, ClerkID, EmailName, CreatedAt, basically exactly what's written here.
ID, ClerkID, EmailName, and CreatedAt, UpdatedAt. Brilliant. So I'm very happy with this. One thing I'm not too happy about is that it automatically added migrations. Migrations are obviously the production way of doing things but in development they cause more problems than solutions so we are mostly just going to use database push instead of doing the whole migration so what should you verify that you have?
Remember you can always actually look at this chapters branch to verify things but in the lib folder you should have a database with index and with a schema. We should also have a drizzle.config.ts file which basically exports define config and make sure that the schema and out match the lib folder which we verified a second ago. At this point we already have 14 uncommitted changes. So it would be a pretty good idea to create a checkpoint by adding a commit so that we can safely develop the schema to the way we like it. So let's simply go ahead and do git add, git commit, and let's go ahead and do git push.
So we are not going to need the users schema at all. Qlerq can handle the users database. What we are going to be maintaining is the workflows table. So let me go ahead and remove everything here and let's replace it with this. So from Drizzle ORM Postgres Core, make sure you choose JSON B, PG Table, Text, Timestamp, and UUID.
Go ahead and create a new workflows table with an ID, which is a primary key, an organization ID, meaning that each workflow will be scoped to its organization. So other organizations cannot see some other organizations workflows. The name is what each workflow is going to have. The graph is going to be stored inside of a JSON field because it can be very dynamic and very modular and can change very often and it's just easier to keep that data in a JSON field. And then some classic created at and updated at fields.
And then in here, let's go ahead and do an export type workflow, which is type of workflows infer select. So just by adding this schema, we didn't exactly update our database because in our database we still have that users migration. So this is what I plan on doing right now. First off go inside of the database folder and you can just remove the migrations folder entirely or maybe a better idea is to just clean it okay the reason I didn't remove the folder itself is for a very simple reason that we actually reference it in the drizzle config and I'm not sure what happens if that folder is missing so what we can do here is we can add a git keep file inside of migrations so even if it's empty it's going to be committed okay because usually when you delete all files from a folder git also removes the folder If you add a little .git keep file inside, then that folder is also gonna be committed. So the reason we remove the migrations is so that we can safely override the database.
Let's go ahead and attempt to do this. So in package.json it says that we have database push. So let me go ahead and do two things first. So I'm first going to try npm run database generate. There we go.
So okay, so that creates the migration file and let's now go ahead and do npm run database push. And let's see if it's going to work. So it looks like it's asking us if workflows table is created or renamed from another table. So it looks like it's still in this migration process. So let me just try something.
Let me once again go inside of migrations and let me once again clear it entirely except git keep. So empty migrations and let me try just doing npm run database push. Nothing more. And now let's go ahead and just say create table and it's asking us what we are going to do here so we are going to drop the users table and we're going to create the workflows table and you can just pick yes I want to execute all statements. There we go.
And now if you go inside of your Neon database, you will now have workflows here with ID, organization ID, name, graph, created at and updated at. Great! And we don't have the migrations here. I just think it's easier because if you keep track of migrations, which obviously you should do in production, but in development they can just cause conflicts and then you have to resolve those conflicts, which you can now do with AI, but honestly I think it's just easier to just do database push every time we change our schema, even though we're not going to be changing it anymore. And later in production, when you do changes, you should do migrations.
But for now, it's just easier to do it this way. Now let's go ahead inside of our features workflows and in here let's create a new data.ts file. In here we can now import and, descending, and equals from drizzle orm package and we can import our database package from lib database. So make sure that your database is actually created in the lib folder and that you have an index which exports the actual database here. Let's also make sure that we actually import workflows from lib database schema.
Now let's go ahead and add a very simple list workflows which accepts an organization ID. The reason we are doing this by hand is so that we have a base, a pattern to teach AI how to create other functions for us. So that's always a good idea. When you didn't exactly specify how you want something created, first create it by hand And then use this as an example of how to create other functions using agentic coding. Now let's go ahead and highlight this function and let's open our editor.
And let's make sure that we are in a new conversation. If you are using Cloud Code in Visual Studio Code, you can see that we have seven lines selected. If you don't have this functionality, you can always manually tag data.ts like this. So I'm going to tell it to do the following. Use the existing list workflows as an example on how to build data functions.
And then I'm going to tell it to create a create workflow data function that takes an organization ID and the required name and inserts a new row into the workflows table and returns it. So very simple method here of highlighting an existing function which we wrote by hand and then teaching the agent how to produce other functions based on that pattern. Obviously this is a super simplified way of doing it but I think you get the gist. As we move on and as our code becomes more and more complicated this will be a very valuable skill to have. And Just like that we now have create workflow as well.
Let's remove the end which is unused for now and let's just add an empty space at the end here. Now if you remember from the demo or perhaps you remember in the pictures, each of our workflows will have these automatically generated unique slugs and names. So let's go ahead and create a little helper for that. So inside of workflows I'm going to create a new folder called lib like this and then I'm once again going to open Cloud Code and this is what I'm going to tell it. We can open a new chat for this because it's not exactly connected.
Create a generate slug util that uses unique names generator package to return a random hyphenated name made from an adjective and an animal like brave otter. Develop this inside, let's just use features, workflows and then lib folder. And that will generate a handy little helper for us, which we can then use and you will see how we're going to use it in a second. So we should have that created any moment now. And there we go.
A function generate slug, which simply returns unique names generator from the package, which I told it to use, uses adjectives and animals, uses the dash as a separator and keeps the length to two. So that is exactly what's going to generate these cool little names like dominant, VASP, right? Brilliant. So just like we've added a data file inside of our workflows feature, let's also add actions.ts file. And in here, let's go ahead and create a brand new action which is going to use this create workflow data function.
So I'm going to go ahead and tell it to create a create workflow action server action that takes a name, gets the active org ID from auth, and throws if there is none. Calls create workflow to create the row, revalidates the layout, and redirects to workflow ID. Develop this in actions.ts, use the functions from data.ts. Use auth hooks, actually auth utils from clerk packages. Let's proceed with this prompt.
There we go, exactly as we described. So it added a use server directive because this is a server action. It imported out from clerk next JS server and it also imported revalidate path and redirect. It imported the create workflow from our data package here and it developed a new server action create workflow action which only accepts the name as the parameter because organization ID is derived from the auth tool right here. If the organization ID is missing we throw an error no active organization and then we create a new workflow by passing along the organization ID and the name which in return gives us back a workflow which we can then use to redirect to.
Brilliant! We need to call revalidate path here even though it currently does nothing. The reason it currently does nothing is because we never used the list workflows function. So that's what we're going to do now. So let's remember where that is.
So inside of dashboard layout we use something called app sidebar and remember that whole requirement which I made where I purposely wanted to separate the workflow nav from the app sidebar so app sidebar can be a server component. Well it's because of this. It's because I want AppSidebar to be able to call this function list workflows. So make sure that you don't have user client anywhere in here. In dashboard layout, there shouldn't be use client.
And instead of AppSidebar, there shouldn't be use client either. Okay, so you shouldn't have any use state or anything like that in the app sidebar that's why we removed all of that inside of workflow nav which is a client component which is expected which is perfectly fine. And this is the prompt that we are going to execute next to make AppSidebar actually load the data. So use workflows data.ts list workflows function and add it in AppSidebar.tsx and pass, maybe we should say pass the result along as a prop to workflow navigation component, replacing the dummy workflows with real data. And I also gave it the following information.
The list workflows function requires organization ID. So do a simple ternary using clerks out function and fall back to an empty array if organization ID is not available. So if you take a look at the workflow navigation, you will see that it doesn't accept any props, right? So that's what we are going to change. And we are going to replace this workflows array as well.
And I'm also going to tell it to do the following, remove the dummy active workflow, set active workflow functionality from workflow nav as well. We will implement active item later. Okay, so I don't want it to go into some rabbit hole of changing the code so that it works with real data now. Great, so I'm very confident in this prompt. So the app sidebar will now call our data.ts list workflows which is going to return basically a list of items.
Let's go ahead in full screen and let's execute. Let's take a look at the summary of the changes. Components app sidebar, it made it asynchronous, brilliant. It fetched org ID using clerks auth and it loaded real workflows with list workflows org ID falling back to an empty array when there is no org ID. And then it passed the results to workflow navigation workflows prop.
It modified the workflow nav to accept those same workflows and it used the type from database schema, brilliant, and It renders each workflow ID and workflow name. And it dropped the dummy list and it also dropped the active workflow, set active workflow state and it removed the unused React import. So active item highlighting is left out for now as requested. So whether you had this part or not, I don't know. I just wanted to remove it because it's dummy and we are later going to use the URL to highlight an active item.
But I think it's easier for us to actually take a look at the code rather than read about it. So instead of components app sidebar here, let's see what happens. We now imported list workflows from features workflows data. We made the app sidebar asynchronous. That's why we can't have use client.
We attempt to extract org ID from await out which we've imported from Clerk Next.js server and then we attempt to load the workflows by checking if we have organization ID then await list workflows and pass in the org ID because that's what it accepts otherwise fall back to an empty array. And once we got those workflows, we simply passed it as a prop to the workflow nav component. Those are all the changes in app sidebar. You can also verify that using the diff. Now let's go ahead and take a look at the components workflow nav.
So in here we now have workflow nav props, which uses workflow and then an array. So an array of workflows. And it got this type from lib database schema, which is an inferred type workflow. Now sometimes agents don't do this. I'm very happy that our agent recognized that I actually have a workflow type inside of lib database schema but sometimes they can actually do this.
They can create their own workflow type and then give it an ID, right? Which makes no sense and it's code smell. It's bad. So here's what you can do and what I'm gonna do anyway, even though this worked as expected. What I'm gonna do is I'm gonna go inside of my agents.md and I'm going to add the following.
You don't have to pause and copy this, you can simply use the link on the screen or if I forgot to add the link on the screen, just go to the GitHub repository, select this chapter or the main branch and then simply go ahead and find agents.md and copy whatever content you have inside. So this will basically instruct your agent to never handwrite custom or partial shapes for table rows. So it can always add an export and infer from the database, which is exactly what happened here. So if yours didn't do that, you can go ahead and literally give it this in your conversation. So tell it, do not make custom types and then tell it to do this instead.
And then I guarantee you it will do the right thing. Brilliant. So inside of workflow nav props, we also removed that dummy list. And we also simplified, there is no longer that logic highlighting which item is selected. So the only thing that this do now do is load items from our database and pass them here.
And if you now go on to localhost 3000 and make sure that your app is running, you will notice that we have no workflows. So it's time to finally enable this plus button. We can enable the creation using a prompt like this. Modify the workflow navigation component to enable creation of new workflows by using the create workflow action from its location workflows-actions.ts. Keep in mind that this is a server action and workflow nav is a client component.
It will most likely have to be passed as a prop through components app sidebar, which is a server component, which means it's compatible with the server action. Also make use of generate slug lib from generate slug to create a unique name before passing it to create workflow action. And let's go ahead and run this prompt. Let's take a look at the output. The wiring is complete.
App sidebar imports create workflow action and passes it as on create workflow to the workflow nav. Workflow now now accepts that new action as a prop and handle create workflow, which is a new function inside of workflow now, generates a unique name with generate slug before calling it inside a use transition. So is pending disables both new workflow triggers during the request. The action itself revalidates and redirects to the new workflow. Both the collapsed popover button and expanded sidebar group action now trigger creation.
Let's briefly take a look at that code. So instead of app sidebar component, we import create workflow action from features workflows actions and then we pass it to the workflow nav under the name onCreateWorkflow. Inside of workflow nav, we now accept that brand new action and we also added use transition from React. The reason we've added this is so that we can call that function within start transition because that will enable us to maintain the is pending state so that we can disable the button to create new workflows while one is already creating. So let's take a look at localhost now, let's refresh and let's go ahead and click the plus button and what happens is we are redirected to a 404 page because we never actually implemented the workflow ID, but if you delete this and go back to workflows, my apologies, if you go back to localhost 3000, you will now see a brand new workflow visible here.
And inside of your NEON database, you will now see a record has been inserted into workflows. And what's important is that it's tied to its organization ID. So if you actually go back here and switch to another organization, you will see that it's no longer available. So that is only accessible through a specific organization in which it was created. That brings us to the end of this chapter.
We have successfully set up Neon, we got a Postgres database, we have Drizzle ORM set up, and we even implemented data functions and server actions which we wired up into our app sidebar as a server component and in our workflow nav component which is a client component. Brilliant job. You can always check out this chapter, this chapter's branch on GitHub, and don't worry if the code is slightly different than mine. The goal of this project isn't to copy and paste what I do, it's to learn how to use a gentic coding to your advantage. It's to learn to be in control and use AI to your advantage and not just let AI do what it wants to do.
It doesn't have to be exactly the same as it produced in my code base, right? Subtle differences are completely okay as long as the functionality works and you are satisfied and understand the code that was written. So let's go ahead and do git add, git commit, and git push. After you push your changes, your railway deployment will most likely fail. And that is because we have to update our environment variables.
As you can see in this exact error, npm run build, fail to collect configuration, and then in here we have the error database URL is not set. So go into your .environment or .environment.local and go ahead and copy the neon branch database URL and database URL unpooled. All of these were added automatically using Neon's quick start and agentic prompts. Then head into variables, raw editor and simply add those new variables here. Click on update variables and then redeploy.