The React, Bun & Hono Tutorial 2024 - Drizzle, Kinde, Tanstack, Tailwind, TypeScript, RPC, & more

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
we're going to spend the next 3 and 1/2 hours building a full stack modern react app using my favorite webtech in the back end we're obviously going to be using bun and the hono framework which works with all JavaScript runtimes so I use this in my serverless environments as well as on my bun servers the front end is going to be a v react app which will be all client side rendered no serers side rendering or react server components and all of this will work with react 18 or 19 whichever one you want to use Tailwind for Styles and all of the tan stack libraries for querying routing and forms the front end is going to make HTTP requests to the back end using hono RPC so we get typescript safety there the front end and the backend are both going to use Zod for validation we're going to be using kind orth as the fully managed orth service for this and I've been using kind for a while it is my favorite or service and they were nice enough to sponsor this video so thank you kind and we're going to be setting up authorized routes on the front end and the back end so only logged in users can perform certain tasks the database is going to be a relational database and it doesn't really matter which SQL database you use for this but I am going to be using neon postgress because it's what people voted for and we're going to use drizzle omm which is the best way to interact with a relational database from a typescript app then towards the end of the video I'm going to show you a little bit more advanced stuff with front-end caching and optimistic updates that makes the front-end client side rendered application feel really nice and Native and of course we're going to host this thing so in development we're going to be using V proxy to proxy requests to the back end so that in production we can deploy this whole thing to a server and I'm going to be using fly iio to deploy this stuff just cuz it's really easy to deploy there but really you could deploy this to any virtual private server and all of this drawing was done on eraser. and I'll use this a few times in the video to explain certain Concepts here and there and they were also nice enough to be a sponsor on this video all this Tech is really awesome I love working with all of these things and to make sure we really focus on the programming and on the tech we're going to keep the application fairly simple so we're going to be building a basic expense Tracker app where you can see expenses you can create new expenses you can delete them you get little UI updates it's nothing special but the tech behind it is really awesome this tutorial is meant to be for any webdev Junior or Advanced that wants to learn how to build a modern react app using this text act with bun and hono in the back end and react in the front end but if you're brand new to react check out my introductory videos on react and I'll put a link to those in the description we're going to be using bun as the JavaScript runtime which is basically the alternative to node here so I'm going to install this by going to their homepage and copying this straight into my terminal and this will install bun for us then I'm going to create a new directory for this application and in here we're going to set up a new bun server so I can actually do that by running bun in it and I think I'm just going to stick to the defaults here and then I can open this up in VSS code and this is just going to be a default bun project right now we'll have a TS config a readme a package Json and this index.ts file which is going to be the entry point of the application so I can run this file just by running bun index.ts and it's going to give me that console log but really what I want to do is bun watch the index.ts so that we can make changes and it will just update that for us instantly so I could just change this and that just updates for us straight away and what I'll probably do here is immediately create two scripts we'll have the start script which will be fun index.ts and the dev script which is going to watch for those changes is and restart every single time so bnat index.ts and that should be scripts and I need a comma here so now that allows me to just run this with Bund Dev and that'll start the server but it's not actually a server just yet I think there's a section right here somewhere yeah on setting up an HTTP server so to set up a server in bun is super easy this is all the code you need uh I just want to steal one that has a port number to so I'm going to go into index.ts I'm just going to dump this code in we're going to run on process. mv. port or 3000 and actually as I'm typing this out I can see this comment here that says by default bun will just do that for me anyway it will either choose the port environment variable or Port 3000 so that's a pretty cool default I didn't actually know about so I can just delete that it's going to run on Port 3000 uh I'm not going to have a host name in here although if you're running this from a VM like if you're running it on Windows through WSL it can be helpful to manually specify that you want this to be the host name to bind to but I'm going to leave that out so this is a bun server this is all I need uh I'm going to respond just with hello from bun server here and I'm going to change this console log just to say server running so I can see the server is running here if I go into my browser and just go to Local Host 3000 I can see hello from my bun server and that function that I have in the index.ts this fetch function this is going to handle every single HTTP request to This Server so it doesn't matter what endpoint I go to it is always going to be served up by that function so obviously I could write a bunch of if statements in here to check what kind of request we're getting what the method is what the URL is but instead we're going to use a really nice server called hono and this is going to make it much nicer and much more familiar so I'm going to take this default code here and I'm going to go back into the app and set up an app. TS file and just paste that code in here and it needs hono so we can bun install hono here Bund Dev and then back in here we're exporting app okay so I'm going to delete these comments so in my index file what I need to do is import app from app and then instead of implementing a custom fetch function here we're going to use the built-in hono app. fetch function so all HTTP requests are now going to be handled by the hono library so in here we can set up our endpoints in that really familiar way where I can go app.get so we're going to accept one maybe at the root route or maybe a test route this gives us a context object when we pass it a callback function and I'm just going to write a response here to get rid of those warnings so c. Json message test and this context object that we get passed in here is going to be responsible for handling the HTTP request and response so we can get the request data from this object but also respond with like Json or HTML or whatever we want we're just going to respond with that and make sure it's working right now so if I go to/ test we can see that custom message and if I go to a route that I haven't defined we get the hono 404 message and if we go back into the docs and check the context section we can see all of the things that this is capable of doing so we can grab headers we can set status codes basically all the stuff that you would expect to have in an HTTP library and we can also really easily add middleware in this Library so I'm going to come to middleware and I'm just going to look for a logger middleware right here cuz this is pretty standard this is going to be app do is it app.use yeah app.use logger I'm just going to copy this so I don't get it wrong now we're going to have some logging middleware in here so if I go back and I make a new request let's go to the test endpoint we're now going to be able to see that in the logs the requests coming in and the response going out this just a little bit nicer when we're trying to debug the HTTP requests this is going to be an expense Tracker app so we're going to need routes to get expenses create a new expense all the crud operations so I'm going to put this in a new file and I'm actually going to make a new folder here I'm going to call this rout and I'll put uh expenses. TS in here so we're going to import hono from hono and then to create a new route is exactly the same as creating a new hono app we just create new hono so I'm going to call this something different I think I'm going to call this expenses route and we're going to export this from here and then we can just add all the get and post requests to this and we can do this individually but it's going to be a little bit more helpful later on if we just chain these all together so I'm going to have a get request which is going to respond with all of the expenses so I'm going to have it be a JavaScript object like this so we'll c. Json this so This endpoint will return an object with the key expenses and then an array of the expenses that we get from the database I'm just going to stub all of these in right now so if I make a post request to that same endpoint this is going to create a brand new expense and I'll just fill this in with an empty object for now and we'll also add in the standard ones to delete and put but I'm going to add these in a little bit later let's just focus on the get and post for now so I'm exporting this route I'm going to Define all the expense routes in this file and then in my app. ts I'm going to import this route so import expense route from routes expenses I'm going to have my app serve that up over route SL API SL expenses and then pass in that router there if I go to/ API expenses I'm going to expect this endpoint or this endpoint to be the one that handles the request depending on if it's a get or a post request so if I go into the browser and make a get request at that endpoint I would expect to see expenses empty array let's just test that's working and there's my empty array and then if I made a post request to that endpoint it would be handled by this post right here and obviously this is going to be getting and inserting data into a database but before I get that far I'm just going to create a fake database a fake expenses array and this is going to have all the properties of an expense let's make a type for that type expense is going to have an ID which will be a number a title which will be a string an amount which can be a number for now I'm going to make this an array of expenses and then I'm going to ask co-pilot to make some expenses for me if I am making the get request I'm going to serve up that array of fake expenses and then if I'm making a post request I need to insert a new expense into this fake database array so the way we get data in the post request is through c.r. Json and this is going to give us any Json data that was posted to this endpoint and obviously I'm going to need these to be async so what I'm expecting is for someone to post a new expense object to this endpoint and then right now I'll just send this back down in the response and I'm going to console log this for now just so we can see what's going on and I'm just going to test this post request out quickly using Thunder Cent so I'm going to make a new post request to localhost 3000 API expenses and the body here is going to be Json I need to input well I don't want to put in an cuz that's going to be generated by the database but I'm going to put in a title some expense and an amount which is going to be some number and then if I hit send here I'm going to expect this not to give me a 500 error let's see what happened this is not going to be res obviously is going to be wreck okay that was a stupid mistake let's try that one again and hit send okay so I'm sending this data it seems to be working it's giving me the response back I can see that it's console logging out expense here some expense 1 2 3 4 but there's a few things wrong with what's going on so right now if I hover over this I can see expense is any type and even when I had that messed up I think it was still just giving me oh it's unknown type so I need a way of checking that the data that is coming over the post request is actually what I expect it to be in this case a valid expense object and I only want this code to run if someone has posted a valid expense type and the way we're going to validate this data is to use the Zod Library which is pretty much a standard tool for any typescript developer now it is a typescript first schema validation library and there's a lot of documentation here but I'm just going to use some of the basic things first thing I'm going to do is install Zod so Barn install Zod and then in my project I'm going to bring in the Zod library and before I use this I actually want to talk a little bit about why we need a validation library because we have typescript so I could say that the response coming in has a certain structure I could say that the user is definitely going to post an expense object and now in my typescript code I get to use this as an expense object so I could try and access certain things that I know only an expense has so we could try and grab the amount property from it and my code isn't going to give me a warning because I've told it it's an expense object but this is only a compile time check and you can see it's really easy to just tell the compiler whatever I want to tell it so it doesn't really matter what what the user posts here I could post whatever I want I could add some random Properties or I could misspell things whatever it doesn't matter if I send this my code will still compile I'll still get the sense that this is working correctly and I have safety but nothing in the JavaScript runtime is going to check if this is actually the correct data being posted so with Zod we can create a new validation object so I'm going to create a new create post schema and this is going to represent the structure of the data that I want when someone tries to post an expense so I'll make this with Z doob and then provide the properties that I expect this to have so when an expense is posted I really just need it to have a title and an amount and I'm going to say title has to be a string and amount has to be a number and then I can use this to validate my data so let's say this is the data coming over the post request and in order for this to be an expense it has to pass that SCH validation so I'm going to pass this object into create post schema only if this meets those requirements it has a title and amount and they are those types then I'll get this expense object with the typescript type that I expect it to be and if it doesn't then it will throw an error in this case so now if I make that request and I have incorrect data I would expect a internal server error to happen but if my data is structured correctly then I get a 200 okay so this is going to be a runtime check to make sure my data is structured correctly and I can go even further here I can say that this string must have a minimum length of three characters and a maximum length of maybe 100 characters I can say the number has to be an integer and that it has to be a positive integer so I get this runtime validation of my data that also gives me the correct typescript types when that validation passes but since doing this at the HTTP layer is such a common thing we're going to use a built-in function from hono so if we go to the validation section it has a zard validator bun add hono zard validator so I'll add that in here and then we can bring that into the application so I'm going to go to the top of the route and I'm going to bring in the validator and then for every route that we want to have this s validator we just use the Z validator function for my post request I want to make sure that the data that comes in already has that structure so I'm going to pass this in as a middleware function I'm going to say that this is going to be Json data and that it has to meet this requirement right here this will get run before my route function even happens so I know the data is correct before this even happens uh and then instead of using c. rec. Json I'm going to use c. Dov valid Json and this is going to contain anything that was validated before this ran now I should be able to see data has that schema of title and an amount and then I'm just going to add this into the fake database so fake expenses. push d structure this and give it an ID now this should push that into my fake database so I should be able to create and get these expenses let's post a new one if I change this to a get request we should see all three expenses that exist or four I guess and I did that wrong it's supposed to be length + one not that it matters that much now I'm going to continue adding some more mpo points here so one of the other ones I might want to have is if I go to slid going to get a specific expense object out of the database ID is going to be a dynamic path parameter so I'm going to go over to routing and you can see this is how you create those Dynamic path parameters uh I'm just going to copy this bit of code you can see there's different ways of doing this here and I'm going to put that in here so we're expecting an ID to be passed in so first thing I want to note is that ID has Type string because any path parameter is going to be a string by fault so we need to convert this to a number which I can do with number. pass in because I actually do want to pass it into an integer since my IDs are always going to be integers but there's nothing stopping me from passing in just any random string I could go SL API SL expenses SL Sam for example if I just wanted to so I need to First make sure that this is an integer before I try converting it to that integer type so here in the routing section I'm going to scroll down because we can pass in regular Expressions this is a really common thing to do in the path prams and this one should be exactly what we want actually so I'm going to say ID has to be a number it has to be one or more numbers so this is going to make sure that it's definitely a number in order for this route to get run so if we don't pass in a number here a route just doesn't exist for that if I went SL samam it just wouldn't go anywhere it' be a 404 not found so now I know that this is definitely going to be a number I can check the fake database so let's say fake expenses find expense where the expense ID equals the p in ID if the expense doesn't exist 404 otherwise we'll return c. Json and I'm to pass that expense back in an object so the key is expense the value is going to be the value of that object it's giving me a warning so actually messed up here we don't return statuses and actually there's a built-in not found function that I can return here if we haven't found an expense so this should just be able to get me an expense Let's test it out and see if it works SL expenses SL samam should just give me a 404 not found SL1 should get me the first one two should get me the second one 20 should get me a not found okay so that route is now working so we can do something similar to be able to delete an expense so I'm going to put this delete in here and it's going to be very similar so I'm going to copy all of this again we only want a number if we can't find the expense we'll give a not found however if we do find that expense let's delete the expense from the array and I'm only a little bit embarrassed that I've forgotten how to splice and I need co-pilot to do this for me so that looks good for deleting and I think I'll just leave put alone for now updates a kind of version 2.0 features anyway as long as you can get post and get a single item even deletes are nice to have really but this all seems to be pretty full it seems to be exactly what I want right now except post should give me a status of 2011 and up here I'm actually going to improve this s stuff just a little bit because I've got a tiny bit of duplicate code here I'm creating this type but really this type kind of exists as the Zod validation object so instead I'm going to create a new Zod object so this is going to be expense schema this is going to be the object as it exists in the fake database and I'll create a zard object for that this will be good because I'm going to end up using these Concepts when we set up a real database later anyway so the expense schema is going to have all of this plus an ID that is going to have to be a number that is an INT and is positive maybe with a minimum value of one and then my create post schema is going to be the expense schema except I don't care if someone passes in an ID or not so I'll just emit the ID this creat post schema is still the same as it was before but I can infer the type of an expense from this schema here so I'll Define the type really using Zod and all of these Zod functions uh and then when I want an actual typescript type here expense I can say this is z. infer type of expense schema and this will now give me the expense as a typescript type from this sod object so this is the source of Truth for the schema of my logic here and this doesn't matter too much for a fake database but we are going to keep all of this in mind when we get to a real database later on so now what I want to do is set up a react front end so that we can actually connect to these API end points and get some communication between a front-end application and the hono server so what I'm going to do is first I'm actually going to organize this code a little bit because I'm going to put my front end code within the same directory here to make it easier to share the types script code between the back end and the front end and some projects might go far enough to create an actual mono repo for this but I think just dumping the front end project into this project as it is is more than sufficient to get this working so I am going to start by just creating a server folder and any code that is specific to the server so that's app. TS index.ts and my route I'm just going to put in This Server folder so let's put that and these two in the server folder everything else should really just be configuration or information files so that all seems fine I'll probably have to update the start script so this is now server SL index server SL index the module is server SL index and then within this project I am going to create a new front end react application so I'm going to do this with bun create V at latest uh I'm going to call this frontend we're going to select react and just type script from the top there and now I can CD into the front end application bun install and before I run Bun Run Dev there's one more thing I want to do if we go over to the bun homepage over here and I check the guides I think there is a vit oh right there it just has a couple of different scripts that we can use so instead of just running vit for the dev script we're going to run bunx bunv and we're going to do the same so here's the front end code here's the package Json so I'm going to update the dev script and I'm going to update the build script too to be the bun version of that bunx bun V build because obviously we want bun handling the typescript stuff instead of TSC here so that's good now I can run bun Dev just to make sure this is working and it's really important that you have the frontend dev server the vit server running at the same time as your backend server so I can run buev on the back end this is my server running it's running on Port 3000 this is my front end react app and it's running on the V Dev server at 5173 so make sure those two are running at the same time to make sure they can actually communicate with each other so now if I go back into my application I can see that I have a front end and a server folder so this is where all my front end logic goes the server is where all my backend logic goes obviously so in the source directory of front end I can see I got my app. TSX my basic react app stuff right so I am going to just make sure that's working let's go to Local Host 5173 and yeah I can see this is the basic V react app that's all working that's fine now before I go any further I'm going to install Tailwind into this project so I want to use that for styling and I will go over why I prefer Tailwind to using just CSS or other things but I'll do that after I've installed this so we're going to grab this and instead of mpm or MPX we're going to use bun and Bun X cuz this is a bun app so make sure that when you are installing your front end libraries you are in the front- end folder install developer dependencies tailwind and we're going to need this line two and instead of MPX we're going to run bunx so this will set up Tailwind for us then I also need to make sure I've got this content set in the Tailwind config JS so just paste in their defaults there that's good and we need to make sure index.css has these three things so we'll go to index. CSS right here in the source directory and delete everything and just put in those those three Tailwind things and then always for good luck we're going to restart the front end just to make sure that all works before I actually start adding Tailwind I'm going to explain why I like using Tailwind so if I go into app. txx I'm going to delete a bunch of this code so I'm going to get rid of this top stuff here I'm going to get rid of this stuff at the bottom and I'm going to leave this part in here that has the button that you click to make the counter go up and down but I just want to leave this part in and change it a little bit so we're going to have the count in a P tag and I'm going to say this button is up and this button is down and this is going to reduce the count by one and I can delete this heading here too so now this looks like this it's still functional it doesn't really look that nice now I want to style this and the first thing I don't like is that my styles are in a separate location than my actual markup because my styles are very closely coupled to my markup so if I go to my app CSS here I can see a whole bunch of styles for things that I just deleted because they are separate so I now have to make sure I come in here and delete all those things that I deleted in the markup so it would be nice if those two things were together the second thing is if I want to style the buttons here that's fine I can just say I want to style the card buttons so card button let's just give this a background color light blue so that now Styles these buttons and I'm going to make this Flex and flex Direction column and that styled exactly as I want it so that's fine then I go back in here and I decide that I want the down button to actually be light red all other buttons in a card they can be blue but the down button I want that to be red so then I have to come up with a class name here and naming sucks naming is hard I just don't want to have to think about it at this step but fine I'll add a little class name in here we'll call this down button maybe it's good maybe it's bad I don't know come into app. CSS different file make a new rule for this let's just give it a background color of light coral and this seems like it should work I have the class name I've specified the rule but if I go to the browser this isn't going to change the color of the button and the reason this isn't going to change is because it isn't specific enough so doing something like this would actually make it change and sure understanding CSS specificity isn't super difficult but it's another thing that I don't want to have to think about when I'm just trying to style a button I just want it to be red so one solution to all of these problems that I've identified right now would just to use inline Styles instead let's go Styles equals give it an object and say the background color is coral and that's actually just style and now that updates that single element so this would solve all the problems but everyone knows inline styles are bad and you may or may not know why they're bad but everyone knows that inline styles are bad one I'm going to focus on is that there is no opportunity for pseudo classes if I want to change this background color when I hover over the button I can cannot do that with inline Styles so in order to get the best of both worlds where I get everything that I want I can use a library like Tailwind where I can say that the background color is let's go red 100 and the background color of this one can be blue 100 and maybe this is just Flex Flex call and I'm going to remove all of these styles from my app.css so now I'm going to get that styling that's kind of what I wanted but I can also add the pseudo classes so when I hover over this I actually want it to be BG red 300 and now that just works and I don't hate the utility class names that it uses and there's a lot of other benefits to using something like tailwind and it gets even better when we use a good Tailwind component Library so we're going to use Shad Cen for this because this is a nice component Library it's very simple very minimal but it also has a lot of accessibility features built in so you don't have to really worry about that and if you don't want to generate a UI you can use v.d which is kind of good at building a UI using AI so we're going to use this I'm going to go into let's see probably docs and installation and we're going to set this up to work with the vit application so we've already got vit set up we've already installed Tailwind it does actually suggest setting up a few other things which is super nice like having this path here so I'm going to follow these instructions we're going to edit the TS config Json file in the v application so let's go to TS config make sure you're in the front end TS config in the compiler options we're just going to add this in so the base path is the current path and then we're going to specify that we can include any module in the source directory of the front end uh by using at slash which is kind of nice I do like that so we're also going to have to update the V config right here to resolve that Alias so I'll put that into the V config wherever that is right here so just WR there that looks good but we're going to need to import path from path and I think this wants to be meta do import meta all right cool then we're going to actually set up Shaden so we're going to run this with bun X from the front end directory so over here bun X Shaden UI latest in it and we're going to go through a couple of options here cuz it is going to modify the application a little bit so yes we're going to use typescript just use the default I quite like neutral but you can pick a theme so if you go into shadan let's just look at the homeage page maybe no themes you can see some of the default themes it has and you can customize these if you want uh I'm just going to use the neutral theme so let's select that where is the global CSS file source index.css source index. CSS do you want to use CSS variables for colors that's optional I'm going to say yes I like that are you using a custom tail Wix prefix nope where is your Tailwind config.js located that is just tailwind config.js and we'll put all the Shaden components in app SL components utils can go there are we using server components definitely not right configuration yes okay so this will set up the project to use Shaden which does a few custom things so if we go to the Tailwind config it did a lot of customizations here for the theme these are all cool things you can change them if you need to um but we're just going to leave all the default settings I am just going to add in the index.html page cuz I want any Styles I put in that page to also be picked up by tailwind and if we look at the index. CSS file it has created a lot of variables for the default theming and this is cool because it allows us to add more consistent styles to everything for example if I go to app. TSX and I wanted to make sure this button's text color was the default themes text color I can just go text foreground and that is the themes foreground color and I could make this entire card here be G background so it's just the default background color the default foreground color doesn't really matter what they are that's just going to be those colors so the foreground color is just black and the background is just going to be white there but this will also pick up if we change things to dark mode and we can do that by toggling the dark class on any of the elements I'm just going to cheat right now and I'm going to go to the index.html file and I am going to set class here for everything to be dark so this is just going to be dark mode we're not going to be able to toggle it we can change that later uh but I would just rather have everything in dark mode right now and I think I probably need to restart vit to get all that working and then I should probably use some more default colors so for the background of buttons for example I might want to choose BG actually we could see what kind of options we have in the index.css so we have a uh default card color default popover color primary secondary let's see maybe secondary or I could choose any color really and in fact for a button we would end up just using the Shaden UI component so they have a button component for example if I want to include that in my project I can just copy this for bun and I'm going to come over here and paste that in so we don't install the actual Library we just add components as we need them so this is going to add a button component into the project in Source SL component SLB button I could manually update this if I wanted to I can delete this whenever I want it only exists if I need to use it so I'm going to come back into app here and I don't need any of these things but I can import the button from and now I get that at slash so at slash gives me straight into Source then I can go into components SL ui/ button and that's going to give me the button from Shad CN so then I could replace all of these with the default button there and that's going to give me much nicer defaults for all of this so I'm going to delete the class I'm just going to see what this gives me right now and I have extended this all the way to the edge of the screen so it doesn't look that great maybe I'll make this a little bit nicer I'll say uh Max width MD maybe we could do margin Auto uh and maybe we'll do a gap y of five and this is what the component currently looking like and everything still works so we're going to use the Shaden components just to make this a little bit easier and the first thing I actually want to show on this page is the total amount spent in my expenses app so I'm going to create a little card over here from shadzi and so let's go to the card component and I just want to have the total show up in something like this so I'm going to bring in this with bun we're going to bring in the card component and I'm going to go over to app. TSX and we can bring in actually I'm going to copy maybe some of the code they have here so we get all the different parts of the card I think I'm just going to copy some of this so I don't have to type it out all by hand so we're going to take the card and the header and the content I'll stop there cuz I don't need any of the form thing so in this app we will have closed these out a little bit the title I think is going to be total spent total amount spent and in the content here I'm just going to create a new state variable let's see total spent set total spent we'll default this yeah sure to zero why not and throw that in here and I'm not using the set function just yet I'm not using some of these things let's just get rid of them so that though should display the total spent let's actually let's Center this a little bit so margin Auto and okay so we got the total spent here on the home page we'll style this we'll flesh this out a little bit more but right now I do want to just display the total amount spent in this bit right here so for that I'm going to need to make an HTTP request to my API to get the total spent and I don't think I actually put that endpoint in the API so let's go back to the back end so I'm going to go into the server and routes expenses and in here somewhere I'm going to make a new endpoint so if I make a get request to SL total spent I would like to send back just the number the total amount that I've spent in all of my expenses and in a real database this is going to be like a select sum of all of the expenses but for now I think I'll just get code pilot to get the total from my expenses I probably should be able to write my own reduce function but let's see accept that okay this is exactly what I want so if I go to get SL total spent it'll give me a total amount okay perfect so this is the endpoint SL API SL expenses SL tootal spent this is the endpoint I need to hit to get that number into this page so in my app. TSX I'm going to need to make that fetch request to get that data and this is going to need to go inside of a use effect so that this happens only when the component first loads and we will clean this up we will make this better a little later on but right now we'll just put this in a use effect and then we need to make a request to the back end so this could potentially mean making a request to http localhost 3000 / API expenses but this is only going to work right this is the server that exists on Local Host this will only work in development I have to make sure that this works in development and in production so I need to figure out how I'm actually going to host my react app and my server before I start messing around with this because trying to fetch from my server like this I mean first of all it just won't work if I go to the browser right now and try and do that in a use effect I'm just going to get all these cause warnings because I can't just make a request from one origin to another so we need to fix this issue but we also need to figure out how the front end is always going to make requests to the back end in development and in production if you're making a client side rendered single page application and you're using V in this case to make a react app then you get luxuries like being able to write typescript and jsx you get to import node modules straight into your project you get hot module replacement so that everything updates the moment you make a change and a bunch of other things all running on the V server at localhost 5173 and this is all part of your development environment you get all of this in development but when it's time to deploy your app you build all of those files into plain old HTML CSS and JavaScript files this is webdev 101 HTML CSS and JavaScript files and these things you deploy to production so in development you need this server running on this port so you get all these special features but in production you don't need any server side logic to serve up these files you just need to host them somewhere your user can get these static files so these can be hosted in an S3 bucket or GitHub Pages or wherever you like but it's really important to remember that these are just going to be static files now in development you might also have an API server running on a local host Port so in this case Port 3000 and this is going to be where you accept all of the HTTP requests so this is going to be serers side logic running on Local Host that you will have to deploy to a real server at some point so in production you are going to have to take all of this code and run it on a real server with a real domain name like my app let's say this is https and this is going to be the origin of the server in production and you might decide that you want these files the front end part of your application to be hosted on the same origin and there's a few ways of doing this but the easiest way of achieving this would be to just put those front-end files inside of your API server so if you make a request to SL API SL whatever then you're going to get a j on response but if you just make a request to my app at the root path then you can get the front end app so someone in their web browser can just make a request to your server and start using your web app and then behind the scenes your frontend application is going to make HTTP requests to your API but it's all going to be at the same origin now in development we have two different servers running we have the front-end V server and the backend API server and these are both on separate Origins because we need that for development but if our plan is to put them on the same server in production then we want to mimic that in development and we can do that by proxying requests from the front end to the back end so we can say that anytime we make a request to the front-end V server at/ API we want to forward those requests to our backend server running at Port 3000 so that way in development we'll have our browser and we will make all our requests to Local Host 5173 the V server so we can request the front-end application from the frontend server and then any API calls that that application makes to Port 3000 will do that through Port 5173 so in development everything will go through this single server through the single V server but then in production we'll deploy this backend server and everything will go through the backend server instead and this means that all our requests from our front end can just start with/ API since they're on the same origin anyway and we won't have to deal with cause issues in our local Dev environment or in our production environment when we actually deploy the app and the way we do that is to go into the V config and we're going to add a new Option here so this is going to be server proxy and then we're going to say that any request that starts with/ API we are going to forward to the backend server running on Port 3000 so now any EST that goes through the V frontend server at/ API will just get forwarded to the backend server so this means that anytime I want to test an API endpoint I'm not going to use Local Host 3000 anymore because they can all go through my frontend web app so I could test this out by going to SL API expenses and even though I'm using my frontend V server here it's going to forward that request to the backend server so it's really important that in development we always resort to using the 5173 Port the V server and not the 3000 Port anymore we're going to do everything this way in development and then in production it's all going to run the other way around it's all going to go through the backend server and I'll show you how to set up that part in just a moment but let's see if we can get this working now so if we go to Local Host 5173 and I actually save this file we should be able to fetch the expenses here so let's see it's not doing anything there but in the network tab I can see it is grabbing the total from the backend now it is effectively getting that from the HTTP request so now I just need to store this in the total spend so that we can actually see it on the page here so to do that I'm going to make a new async function to fetch the total then we can await the Fetch and get the data as res. Json which also needs to be awaited and and then data should be this Json object here so it has a total and that's going to be the amount so we're just going to set the total spent to be data. total and right now this just has any type we're not handling error or loading States or anything like that we will make this much better in a little bit I just want to get this working first so then as soon as this gets called we will invoke this function make that request and hopefully we should now see that amount there there we go so as soon as it makes the request I think I have okay let's stop throttling there uh yeah so the moment the page loads it grabs that data shows it on the homepage perfect so we can now make HTP requests from the front end to the backend hono server now let's see how we can do the other part of this so when we're ready to actually deploy this app and I think we'll deploy it now and then we can keep updating it as we go through the app when we want to deploy it we have to build it put the files into the production API and then host that somewhere first thing is that we have to have a plan for building and actually serving up the react app from the hono application and if I check the hono docs for serving static files in bun we'll get right here so this is what we need so I think I just need a couple of these I'm going to copy all of this and then we'll delete some of it well actually we could build this first so when we're ready to deploy the front end instead of running Bund Dev we're going to run Bun Run build and this will build those files into the static asset that I was talking about into the disc folder right here in front end so right here we have the index.html I have the index.js and I have CSS and it's all been transpiled down and minified any unused code gets cut out it does a whole bunch of advanced things to make sure we get an efficient set of files here and we can even go further and break these apart even more and download them separately but for now this is the simplest version we have this disc folder if we serve up the disc folder then that is the entire front end application so we need the backend the server to just serve up that folder from the front end directory in app I'm going to paste this at the bottom and we're going to need to bring in serve static so let's see right I needed to import that from hono slbn and then I only want the first one and the last one I can actually make this a get request and I'm going to use a star for both of these and this first one the root I am going to give the path to frontend slist so we're serving up the entire disc folder and we're always going to fall back to frontend dis index.html so basically our server is going to be able to serve up any API route but if the request doesn't match any of the API routes that exist on the server we will always just serve up the front-end react app in this case and I guess I really don't need this test route anymore so if it's going to an API handle the API request everything else we will go to the front end application so that means that if we type in a URL SL something that doesn't exist instead of handling that server side we can still serve up the react app and show a custom 404 page instead of just showing like the hono 404 message anything that's not handled by the API will be handled by the react app which gives a bit of a nicer user experience so now I can test this is working before I deploy it by trying to visit the backend API and the only time I really want to go to Local Host 3000 is when I'm testing stuff to deploy it so I'll go to Local Host 3000 I can see the application the react app seems to be working if I were to go to an API endpoint I should get that API but if I go to any random thing it should still give me my react app and we'll have to handle these random paths with a custom 404 page in the react app it's just nice that the react app is always being served up so now all I need to do is deploy This hono Server somewhere and there's tons of different places that you could deploy a hono server I have a video that I'll put in the description on how to deploy any node or bun app to an ec2 instance but for this tutorial I'm going to use fly IO just because I found it really easy to deploy to this service but really you can deploy wherever you want so if you want to follow along with this sign up for an account currently I'm logged in I don't have any app set up yet so I'm going to go back to the homepage and just follow the instructions for the speedrun launch on fly iio and here are the instructions you would need to follow if this was your first time using fly iio you need to sign up and log in and I'm just going to run fly launch from the rout directory of my project so this is going to be the root directory make sure you don't do this from the front end so fly launch is going to figure out what my application is set up a Docker file and some other files for my app and then it's going to help me deploy that to fly iio so it's giving me some options here this is my organization the app name is app region is Seattle machine so I don't really need to tweak any of these settings I don't need postgress or reddis I do want to change the name of the app though so I'm going to change that my expense tracker demo maybe just confirm those settings okay so it's configured some stuff for my application if we go back in here I can see there's a fly toml file and there's a Docker file and this is just the set of instructions it is going to use to set up an environment for my application so you can see it's setting the node environment to production it's installing dependencies and it is running the start script that I have set up for the back end so now all I need to do is run fly deploy and it is going to deploy it to fly iio which could take about a minute to run that just finished deploying here is my URL let's go visit and see if my application shows up it's not working I have a feeling I know why but let's just check the logs first so I think it's fly logs yeah it will show me what's going on it actually doesn't seem to be showing me the logs that I thought it would but I think the reason it's not running is because on fly iio I'm pretty sure we need to say what the port is so I'm going to go back and I'm going to say this is process. m.p or 3,000 and to make logging a little bit better I'm also going to create a variable for this and when we say the server is running we can also print out the port that it is currently running on and then I'll try deploying that again I just have a feeling that maybe flyo actually does need this port configuration even though it says it's running on Port 3000 and this should default 3000 I just don't trust it so let's deploy that again okay I honestly have no idea what was going wrong there out of nowhere it just started working I don't think this was the thing I even added the host name 000000 nothing seemed to be getting it to work then out of nowhere it started working so I have no idea what happened there but it is currently working it is on fio this is a URL I can share with everyone so my application is deployed that's awesome and basically every single time I want to deploy an update I would go through those steps I would build my front end application so that my disc folder gets the newest updates then I would deploy my backend and all of this stuff in the dock file would make sure that my backend application with my front end code gets deployed to fly iio but there is something else we can do so if we go into the dock file and we modify the code here where it's building up the application we can add in this block of code right here so that every single time I hit deploy so fly deploy in this case it will build the front end scripts remove any of the other front end code within the doc container so there's not extra code being uploaded and then deploy Allin one go so if we add a little script to do the build step for us which I have here then the build process is just as simple as calling fly build or whatever you're using to deploy your application you just have to make sure that you build the front end every single time you want to deploy the app but now that's set up deploying should be pretty easy so now we can get back to developing the app and the next thing I want to f focus on is this HTTP request because right now when we make the request the data coming back has an any type and we're just making a request to anything so I could really easily misspell anything in this URL string and when we get the data back we actually don't have a strict type on that so this kind of sucks given that we have the HTTP endpoint over here we know exactly what it's going to return we know exactly what the string should be so when we have a setup like this with typescript in the back end and type script in the front end there's no reason we shouldn't be able to have some sort of type safety with the HTTP requests and this is where RPC Library comes in and you can use something like trpc but any RPC library that works with typescript is going to help in a situation like this and hono has their own way of using RPC it's in the guides here right here RPC so in our back end all we have to do is export the type of the API routes that we have in the back end so when we export that type we can then import that into the front end and since this is just a typescript type and it's not a runtime thing none of the backend logic will actually be executed we get to just import the typescript type for compile time checks then on the client side we can use the hono client library and then any HTTP requests we make will actually be calling a function on the client side like this so we'll know which endpoints we can actually execute and the response from those will have the proper type on them so let's implement this to see how it all works in the server right here so I have my expense route what I'm actually going to do is go to the appts on the server and I'm going to refactor this just a little bit so I'm going to say any routes here that are part of API so I'm going to set a base path here/ API and then attach the route to that at SL expenses so anytime I have more API routes that I want to append to this I'll just add another route so right now it's route expenses I could have route categories or anything else I might have in my API and then I'll store this in a variable called API routes because anything at the API endpoint I really want to expose to my front end because those are the end points that my front end application needs to be able to make requests to and then from this file I can export type API routes which is going to be type of API routes and and this is just a typescript type that contains all information about any of the API endpoints so now I want to import this into my front end application here so I could import the API routes type from and this is going to be in The Parent Directory to parents up/ server slapp so we're importing that type straight from the server code and then in the client I'm going to need the hono client we're going to do something like this and first we need to create a client object so I will say the client is API routes and this is going to be slash because they're on the same origin and then down here instead of making a fetch request the client is going to make the fetch request for us so we await client. API and you can see how it knows which endpoints we actually have access to so expenses and then the total spent and if I misspell something here it will complete PL because it has that typescript safety and then we just need to call. getet on that path to actually make a get request and then everything else is going to be pretty normal here except now the data has the type on it we are safely only going to endpoints that we know exist and we know the type of the data that's being returned from that endpoint so this is how we're going to end up using RPC with hono now there's a couple things I want to clean up here first this is going to be shared code a lot of the different components are going to want to make an API request so I'm going to take this out and I'm going to put this in an api. TS file in my lib folder and we are going to export const API equals client. API so anytime I want to make a request to the API I can do it like that and then in my app. TSX I can import API from /lib API and then just call it without the client there so API expenses now it's complaining again because I just moved this to a different directory which means I need to do another dot dot slash but this kind of sucks this is pretty terrible so instead of doing it this way I'm going to add another path to the configuration so in package Json not in TS config Json I'm going to add another path here so if I want to go to the server directory instead of having to go to all the parent directories and then find the server one I'm going to Define an at server path that exists in the parent SLS server relative to the root of the frontend project and then I need to do the exact same thing in my V config so I'll Alias this as at server and paste that in here but it's going to be SL server in The Parent Directory and now that means that anytime I need to access any of the server files instead of having to do that mess I can just go at server and I think it can't find this because I probably need to restart my frontend application so that's my backend running run Bund Dev on the front end that didn't fix it let's see I missed a slashstar here that might be it yep there we go okay so now that's pointing to the server directory SL theapp dots file right there so we can import that correctly and in app. TSX everything's working so API SL expenses tootal spent make a get request to that and the data we get back is this so that means that I can only access the proper properties on this if I try to access something else it would complain so that's what all of that was for so we have that safety with the HTTP request and everything should still be working in the exact same way I need to go to the local version but now the total spent is a 500 internal server error because my proxy is not working now because I change this to bind to 000000 so here's a little trick for you Local Host will sometimes not work but 127.0.0.1 will always work so let's change it to that and everything should be working now perfect okay the app still works nothing has broken but we now have RPC in there for typescript safety so now I want to fix up this HTTP request a little bit when we make an HTTP request we need to handle all the different states it can be in so it could be successful we get the data as we expected but it could also be in a loading State while it's getting the data from the server but that could fail in some way and we could have an error State and if we're in react 19 we could potentially replace all this code with a use hook pass that in and then just get the data that way and we could Implement an error boundary and a suspense boundary use not use effect but then there's also other things to consider like caching responses so maybe we never want to grab the total spent again unless the user creates a new expense maybe we want to share this data with other components so we need some sort of context we might want the ability to cancel a request to make sure we avoid race conditions and there's a whole bunch of other things that surround requests for data but also just async State management in general so what we're going to do is use tan stack query which used to be called react query and this will handle all of those issues and more for us and it's a really easy library to use so I'm going to go to the installation we're going to install this into the frontend and make sure you're in the front end directory and then I'm going to go to quick start and I think I'm just going to copy a lot of this code and delete it as needed so in the main. TSX that's the entry point for our entire react application we're going to import the 10 stack query things we're going to create a new query client and we're going to wrap the entire application in the query client provider and then I don't actually need these hooks right here so the main. TSX is going to look like this this where our entire application now has access to this query client which is named the way it is because it was probably originally just for making HTTP requests making queries but this really is a powerful asynchronous State Management tool for a bunch of front-end Frameworks and I've used this for managing State between different components and Pages even when no HTP requests were involved is a really cool Library so it's always my go-to for any front-end single page application so now that we have that set up we're going to use the simplest thing here which is use Query oh there's an example right here okay so in the app. TSX I'm going to import I really just need use Query right now from 10stack react query and then I'm going to copy this line of code from the example and it's going to look something like this where we use Query we give it a key so this is going to be for getting the total spent from the server and then we're going to give it the query function which is basically going to be this but I am going to wrap it in a different function so I'm going to delete all of this and I'm going to come up here and I'm just going to make a new async function that is uh get total spent which is going to call this so we'll get the result by awaiting that actually it's going to be just like the code we had in the use effect and then I'll return the data here so this is just responsible for making the request passing the Json and then returning the data and the reason I wanted to put this in a separate function is because fetch by default doesn't throw any errors if the status code isn't 200 so I want to make sure that the response was a 200 status code and if it wasn't we will throw a error server error is good enough for now and that just means that this function will either get the response and return the data or it will throw an error those are the only two two results of calling this function so now I'll pass this function to the use Query hook and that's all we need for all of those cases that I talked about earlier so I can delete all of this code here and the query returns a whole bunch of things if you haven't already you should check out the documentation here there's a lot that this Library can do but let's come down to examples and just take a look at the simple example because I think this contains everything we need we're going to use is pending error and I don't think he need is fetching but we can bring it in anyway I'm going to copy these pieces of code to then the total spend it's not going to be that it's going to be data do total you got to love typescript for that so basically is pending is going to be is it doing the initial request to get the data if so we can return loading uh if there was an error we can return error has occurred otherwise we have the data so we'll just display that as normal and is fetching will be true if it's ref fetching the data so if we have data we could show something to the user but I'll just leave that out for now and we do have the option to use Query or we could use suspense with react query we could use suspense query here and still use suspense boundaries and error boundaries but we have a lot more options with this 10stack query library and we'll use some of those later on uh right now I'm choosing just to put the loading and error States within this component just to keep it a little bit more simple for now so that should mean that if we go back everything should still be working as normal actually I'm going to change this one instead of just returning when it's pending let's come in here let's say uh if is pending then we can just do like a dot dot dot else will display total amount and then I just want to simulate it pending so I'm going to go into the back end here and when we try to get the total spent I'm just going to add a fake await here so I'm going to make this an async function and we'll await new promise set time out resolve after I don't know let's just say 2 seconds and now if I refresh the page we should be able to see that loading state but then after 2 seconds it loads in the normal stuff so kind of get a glimpse of what's going on there so that's kind of nice and if there was an error State we would see the error page instead so I'll remove that before I forget to and now we have in our application a nicer way of using RPC and async State Management library to help with all that other stuff we don't need use effect or use State anymore now I want to add the ability to get all of the expenses and allow the user to create a new expense but before I do that I want to add a routing library because I think every single page application needs to be able to handle routes every app has some sort of sense of pages or screens or scenes or Windows or whatever and in the browser when we go to a new page the URL needs to reflect the new page we're on so right now I see the total spent but if I wanted to see a list of expenses or a form where I could create a new expense those would be different sections different pages of the application and the URL should reflect that that means we keep track of where the user is within the application using the URL and if they refresh the page then they still stay on that page and if a user goes to share a URL with someone like when people share YouTube links or Twitter post links that allows the user to enter those applications at a specific place on top of that we want to be able to handle which Pages a user can see when they're authenticated when they're logged in and we might even want to go as far as handling data fetching and Page loading States directly from the router so there's tons of reasons that you probably need a routing library in your spa and currently the best routing Library I know of is tanack router so we're going to be using a lot of the tan stack things in this application they are all really nice I love this routing Library mainly because it uses filebase routing which I've come to really like after using next for a while and it's all very type safe which we'll see in a moment when we get this set up so first we need to install this Library so I'm going to bun install this into the front end and then I'm going to go to the quick start and we're going to need to install a v plug in here so I'm going to put that into the front end as well and this is going to be a Dev dependency and we need to update the V configuration because this Library will actually create a new file for us and modify some of the route files we'll see how this works in a moment but I'm just going to make sure we have this in our plugins so we have react and we have tan stack router vit and now we can start implementing some of this code so I'll just follow through the quick start guide and we'll modify it as we need to the first thing we're going to do is set up the route in Source SL routes so in Source we'll create a routes directory and in here I'm going to create uncore uncore root. TSX and this is going to be the entry point into all of the routes we have so this is kind of like the main layout where we can put the navbar and then this Outlet thing here is going to load any of the other routes that we have in the routes directory so every page will have the Navar and then they can also have its own custom content for each page and right now I'm just going to comment out the dev tools you can install the 10stack router Dev tools and that can help with debugging things and seeing what's going on but we really don't need it right now so this example has a homepage and an about page so I'm going to copy the code for that too and this is using lazy routes but I'm not going to use lazy routes I'm just going to use normal routes for this so that was the about. TSX page so each page is going to be defined in this routes folder so I have an about page here I'm going to change lazy file route to just file route and I don't know if you saw there but when I saved this file this is what that plug-in is doing if uh I put any string in here if I save it it's going to make it the correct string so this is the path and this is currently needed just for the typescript part of this so it's going to make sure that this is always up to date and always working well with typescript and the other thing the V plug plugin is doing is it's creating this route tree gen. TS file where it defines all of our routes and this makes sure that we get a little bit of type safety when we create links to our different routes here so if I go back into the root route I have a link to go to the about page and because I have an about page this is all fine but if I were to misspell this or try going to a page this should actually give me a typescript error but it might not be because I haven't got it all set up just yet so let's finish going through the step by step so I have uh an about page that was actually the index page I copi that's fine let's put in the about page and I'm going to take the index page and actually make an index. TSX here so this will be the homepage index page and then I need to replace this with just a normal route instead of a lazy route so lazy routes will separate out the JavaScript for each of the pages and all of the dependencies in the pages so they all get downloaded separately it's kind of a cool feature I just don't want to use use that right now so we have about an index and that's being managed for me and then we also need to add this to the main. TSX so I'm going to copy all of this stuff right here and this needs to go into the entry point of the app so that's main. TSX so I'll put that all here and then we need to render the router provider so instead of rendering app. TSX we're going to render the routes this is going to be the entry point to our application I don't need to import app. TSX anymore and that is all set up in main so I'm going to close main I'm going to close a couple of these files now that's set up the application is going to load in the route so immediately it's going to load in the root route and we'll get our navbar and then it will try loading in any of the pages so if we're on the homepage it's going to load the index. TSX so we'll see welcome home but if we go to SL about we'll get the about page here and we'll just see hello from about and again I was talking about that type safety I have about here because that is a valid page that I'm going to link to but if I put in something else typescript will complain saying hey you don't have this page here so we get that type safety which is really nice and this extends to Dynamic path parameters or search parameters it has a whole bunch of really cool features but we're only going to be looking at the basics here so now if I go and view this in the browser we should have lost the homepage yet but now we have this new homepage so if I'm just at the root route we get the homepage if I go to slab it Chang es the path and we see the about page and we got to make sure that we use these links because then it just navigates to the different components it doesn't do a full page refresh and then if I were to refresh the page I'd still be at the same location so this is really cool but now we obviously want to load in that homepage we already created so that was in app. TSX so what I'm going to do is take all of this code out of app. TSX and put it into index so in instead of using this index function I'm going to have the app function I'll just rename this index and I'm not going to export that I'll move this down here so everything is as it was before for the app route but now we're exporting the route using tanack router so now this should be the homepage and then I can delete these two files I'm surprised I didn't already delete app.css and now the index page is what we already had set up so it should just show the total amount spent and then the about page is still just a fake about page so let's see if that's working so the homepage gives me the total spent and the about page just is a fake about page so I now have routing set up which is really cool because now I can create some more pages so I want another page for expenses so this is going to show me all of the expenses show all expenses and another page page for create expense I'm just copying and pasting about here but I'll change these names just to make it a little bit more clear so create oh that's really cool I didn't even know that was a feature so if you create a new file and then just save it it's going to autofill with some code for you that's kind of cool okay uh so I'll just call this create expense create expense okay so I now have four pages index expenses create expense about some of the are just stubbed in oh right I didn't change the route to actually have that in the navigation so I might just separate this out too uh I'm going to create a function called uh maybe Navar that can manage all of the navigation Parts here and then this just having the component right here I'd actually prefer this to be its own component too so I'll make function root and return that here and then I could put these in separate files if I wanted or do whatever I want really uh but I'm just going to leave it all right here so my route shows the navbar and then the outlet and then I'm just going to have that be the component that is loaded so now if I go back I have my home and about still right I didn't add the new links I should add those so let's make a link to expenses and this going to be exactly the same as the other one but expenses and then I'll create another link to create expens es and this will be create and it's not create expense it's create expense so nice that it complains about that okay cool so now yeah got all the pages expense create home awesome okay so now that I have all the routes set up super easily I actually want to implement the create and the expenses page so I'll start with the expenses page when I go to this page I just want to render a list of all the expenses that I have on the server so I'm going to go into that page into expenses and 10stack router there's a bunch of cool features in here I do encourage you to look at the Docks and check this out a bit more you can provide a loader function here to actually load in your content before this page is seen and then you can use suspense boundaries to make sure that people see a loading State before everything loads but I am just going to continue to use react query to load in all the state here so I'm going to copy this from the index page since I already basically have this setup so what I need is all of this code in my expenses page so I'm just going to copy and paste that in but this is my expenses component and then instead of getting the total spent I need to import the API from at lib API so instead of getting the total spent we're just going to get all of the expenses so that's API SL expenses and we're going to make a get request there everything else here is going to be the same and this should get us uh an object that contains an expenses property and that is going to be an array of these expenses so then down here we're going to use Query and I need to import use Query from tanack react query and you need to make sure that you update the unique key here too as well so this is going to be get all expenses and I should probably rename this function get all expenses but that's basically the same we're going to use usequery to go and grab that data from the server and then I just want to present this quickly so I'll say is pending else uh let's just Json stringify the data for now just to see what's coming back and then we'll present it a little bit nicer in a moment so now on this page let's see so yeah if I added pre- tags to this too it would look a tiny bit nicer okay so there's the expenses coming out as uh Json objects not what we want but the data is correct so that confirms that that is working and I can see the fetch request working over here it is grabbing that data correctly so now I want to actually display these things I'm going to go back to Shad CN and we're going to look for a component to display all these things in I think table looks exactly what we want there so I'm going to copy this code in and display it in the table so first we're going to add that to the front end by using the shadsy N CLI and now I should be able to just copy this code and put it into my app so let's see what this code looks like uh we got a bunch of table stuff I think I'm just going to copy this table stuff out of the table demo thing and I'll move this all the way up to the top and I'll put the table in here so right now this wants to display information about invoices so I guess this is going to be uh data do expenses is going to be that array and each of these is going to be expense so I'll say the key is expense. ID and expenses right now just have a title and an amount so that's all I really need what did this table look like okay so I'm going to display the ID the title and the amount in this table and not worry about anything else right now so then here we're going to display ID title amount let's see what that's currently looking like okay ID title and amount cool and then I should really get rid of this Json stringify stuff here so I think what I'll do is I'll move the is pending up a little bit so if we have the data I'm going to put it here if it's pending we won't show all of this data unless it's not pending so that see what that looks like formatted okay so if it's pending show this otherwise show all of the table row stuff and I'm not going to worry about the total right now I'm going to keep this as simple as I can uh and I'll just change this a little list of all your expenses okay so that's just grabbing the data and displaying it in a table which I kind of like maybe I'll put a Max width on this and margin Auto so it's just kind of centered okay that's cool yeah I like that I like how that looks and if I refresh for a brief second you can see those little three dots there we actually simulate a longer delay if when we try to get all the expenses we await server side Actually I don't even need to do this server side I'm going to leave my server code alone back in my client side stuff not using table footer when I make this request I'm going to simulate it taking a while by saying await new promise and then we'll set a timeout for let's do 3 seconds so we can see what it looks like so it's going to show those three dots when it's loading that kind of sucks and then we get the table that's nice so if we go back to Shad CN we should be able to see a skeleton here so we can use the skeleton to have placeholder stuff for the table so again I'm going to bring this component in using the CLI tool and then we can copy a little bit of this code so we're going to need really just something like this so I'll import it up here and then in my table I'll keep all of this the same but if it's pending I'm going to take a table row and maybe I'll display three rows by default so let's say array three let's fill that and I'll map it grab the index and dump in a table Row the key is just going to be the index in the array and then all the cells are going to be the same except instead of loading real data it's just going to show a skeleton and I'm going to leave the width off I think that should be okay so here and here each of those is going to display that skeleton so if it's it's pending show the fake version if we have the data show the actual data so now if I go back and refresh that 3 seconds we get the skeleton that looks way nicer and then the other stuff actually loads in and then if I remove this stuff right here to do that fake loading it's kind of janky because it's really quick to load in but it's a little better than having those three dots and if it does take a little while to load that skeleton is going to look nicer so we can get the total spent on the homepage I can see a list of all the expenses on expenses page now let's add the ability to create a new expense and for this we're going to need to send a post request to the server that contains a title and an amount and for this I'm going to use a couple of the Shad CN components again we're going to grab input to get a consistent looking UI so let's grab this for bun I'm going to bring that into the front end and then we're also going to use the label here so I'll grab that and also put that into the front end code and then if I go to input I think there's an example somewhere with label so I'm going to grab this I'm going to copy the Imports and the actual label and input so I'm going to bring this into the create expense page and in this page maybe I'll keep this in a heading and then I'll throw the placeholder input code there import the stuff I need up here and wrap this in a a form so we're not going to use email the first is going to be title type text and placeholder I guess we just have title there and then we're going to have another one for amount and this is going to be number and we're going to also need a button so I'll import button from component SL ui/ button this going to be type submit and we just say create expense all right let's see what that looks like okay not bad out of the box not bad let's set the max width and do margin Auto and then I think I'll just add margin top of four on the bottom give that some space okay that looks fine that could probably be much thinner too let's just go Excel okay that's cool so if I input a title and an amount and then hit submit I need that send a post request to the server to get this working I would need to store the inputs in state listen for the submit event on the form and then send a post request to the server but there are a lot of other things that we have to think about when submitting content through a form so when we hit submit it's going to be in a pending State as it sends that data to the server when the server responds it could be in an error state or a success State and we're going to need to notify the user of if things were successful or if they weren't and then on top of that the inputs might have validation criteria and we want to tell the user if they've input something incorrect like for example the title might need at least a few characters to be valid and maybe the amount needs to be a positive integer for it to be valid and we only want to allow the form to be submitted when these things are valid so there's a lot of things surrounding forms that we don't want to have to implement from scratch so instead we're going to use a form library and since we're already using tan stack for querying and for routing it would make sense that we could also use tan stack form so I'm going to go to the docs here and we're just going to do the same thing that we've already been doing we'll start with installation install tan stack react form bun add I think the overview has pretty much everything we need so I'm going to bring in these Imports here and at the top of the component we need to create a new form using use form that's going to have some default values here if we want so we'll have a title which can default to an empty string and we'll have amount which will default to zero and then here's where we're going to handle the onsubmit right now we'll just conso log to make sure everything's working uh and then we'll implement the rest of this code here so we need to wrap the form in a form provider so right here I'll wrap that there and then we're going to use this onsubmit in the form that I've already got set up and I need to close the form provider and then for each of the fields we're going to wrap that in a form field so I'm going to copy one of these examples and then remove everything that I don't need so the first form field is going to be for the title and it was complaining because it knows that I don't have a last name in my schema here so I'm constantly getting that type safety from these libraries so the first thing is going to be the title and then within this children's section here I'm going to put my own label and title we're going to copy the defaults here so instead of assigning my own IDs and values and managing State on my own uh I'm just going to copy the example here so the ID should be the field. name and it's going to generate an ID for me so I put that in these two places I can remove this label now and then actually everything in this input is going to be basically the same so I don't even think I need that placeholder so I'm going to take that out I'm just going to replace the default input with the Shaden input and that should work so the ID is going to be whatever the field name is the name is going to be the field name the value is going to be managed by the form State and then we're setting on blur and onchange so that it can do validation stuff and it wants us to use this field info component that was defined at the top here which is the most basic implementation of showing someone the validation state of the current thing so if we're saying that title must have a few characters and they don't enter anything this section right here will handle displaying any validation errors to the user so each of my Fields is going to be wrapped in in a form field and then I still use the label and input as before and then my form has to be wrapped in the form provider so I'm just going to copy what I did here and do the same for my amount field where I wrap it in the form field but this is going to be amount and then underneath the input I'll close this off in the same way but I also need to close the fragment input is going to look exactly the same actually I'm just going to copy both of these and paste them in so the label is actually going to be amount the type of this input field is going to be number not text and since everything in the input is text by default not a number I need to convert this to a number you can do this in your favorite way possible you can use plus you can use double tilder uh probably better off just using number though so now that's going to be a number and then if I wanted to show validation errors to the user I could put that here even though right now we're not validating these inputs at all we will do that a little bit right now we're just going to leave this in even though it won't really do anything but if I submit this form now all the state is being managed for me so when I console log the values of the form this should just work so let's put in uh a title leave the amount as zero and open the console and when I create expense I can see that those are the values being logged to the console so I get both of those values now so one more thing I want to do before I actually submit this to the server is I'm going to copy the code from the example for what they have for the submit button because this will allow us to check if the form can be submitted and if it is currently submitting to the server so we can have our own loading State here too I am just going to make this button the Shad CN button with the same styling I can delete this button here so this will still look the same still a submit button but let's say it's taking a while to submit to the server so let's put in another fake await for let's say 3 seconds so if we submit to the server and it takes a little while this submit button shouldn't be able to be clicked multiple times so it should be disabled and we've also set it to be a little loading State you can set that to be whatever you want but every single time I submit it's now going to disable that button and show me indicate to me that it is currently submitting to the server then if there were errors or validation errors we could display that to the user but now let's actually send this to the server I'm going to leave this a wait in just so we can see what a long task would look like so I don't need this and I'm going to bring in the API from at lib API from and then right here we are going to make that request so await api. expenses. poost and we need to pass in the values is that the correct no it's saying oh we need to tell it that it's Json that we are passing to the server and then it's validating yeah that is the correct type that it's expecting on the server another thing I would want to do is just make sure that if it's not okay we're going to throw an error in here saying that cons res res. okay saying that something went wrong and then we don't really need to console log the values but essentially in here we're going to make the request and we're going to make sure that it always throws an error if anything went wrong and then our form submission will be able to handle those States for us so now when I submit it should be successful we should get a new expense created so let's put in an amount here let's say six so I submit that to the server if I check the network tab we should see 2011 it seems to have been created and then if I go to expenses I should see that in the list there's a title and then if I go to the homepage that has been added to the total spend and if I create uh another one let's make this 10 and submit that I've got that fake loading for 3 seconds then it's successful and then I go to expenses I can see another one okay so that's all great that's working but I kind of want to change this Behavior just a little bit because on successfully creating a new expense I should either clear this form and let someone create a new expense again or maybe redirect them to a new page so I'm going to redirect them to the expenses page when they successfully create a new expense so from react router I'm going to bring in the use navigate hook which we're going to call right here this will give us navigate and then we're going to use that in the unsubmit we're going to say navigate to uh and then we'll go to expenses so on successful submission navigate to expenses and now if I go in here a title some amount I've still got that fake loading in there so for 3 seconds but then it should send us to the expenses page and there I see my new expense so that all seems to be working we can go to the different pages I can create a new expense I can view the expenses that's all working we've got everything set up pretty well to expand on this more too so adding this form provider now will make it much easier when we start adding more to this a little bit later when we actually have a real database and we'll actually validate all of these fields first and having these routers set up is going to make it really easy when we add or to this and we make sure only a logged in user can create an expense can view their expenses and view their total expense so we still got quite a bit to add to build on to all of this we've got a pretty decent Foundation at this point and right now I just want to make sure I can still deploy this to fly IO so I'm going to go back to the back end of my application and I'm going to run fly deploy and this should build my front end in that doc file it should build the front end set up everything for the back end and the front end to work and then deploy that to the server so once this is done that should all be working I should be able to navigate around the different pages and we should be able to see this thing live so that's now deployed it comes with the default expenses in the array in the back end but I can navigate to all the pages so the routing is all working in production I can create a new expense at least I hope I can let's say 100 uh oh I left that fake loading in there that's okay it redirected to this page I have all those expenses it gives me my new value so this is all working so now the next thing is to add orth to this so users can sign up and log in and add a real database to this so it's actually persisting data and becomes a real application for or we're going to be using kind which is an or service and I've been using kind for a while now I really like it it's a really good service and the reason that I always like to use an orth service for my applications is because orth is difficult to implement correctly but implementing a service like is really easy if you want users to be able to sign up using their email address or a phone number then you need to have some sort of email service or text messaging service so that you can send them forgot password links or if they don't use passwords then you have to send them orth codes you also need a way of managing things like two- Factor authentication and it's really nice to have a user dashboard where you can see how many users you have how often they're interacting with your application and if you need to you could potentially suspend a user or delete a user so all the things surrounding orth and user management can be managed by a single service so the first thing that you should do is sign up and log into your account I am already logged in so I'm going to go to my businesses and we're going to add a new business for this expense app so I'm going to call this my new expense app and the region I want this to be in is the one where my users are going to be so I'm going to put this in USA and click save this can only contain letters and numbers that's fine let's just save that and if I was doing this for a real application I would actually set up a custom domain there but I'm just going to select this application that I made and then we're going to walk through some of their setup steps so we're going to use kind with an existing code base click next and then it has some options to help us get set up really quickly by providing us with front end and backend Frameworks that it can integrate easily with and in the front end there is a react option but I don't want to use an or service from the frontend application because that makes it much less secure orth should probably always be done through the backend if we have a backend server so instead we're going to have the backend hono bun code manage everything to do with orth and then the front end can just communicate with the back end if we need to log in or get user details or anything to do with the user so they don't have a bun option I don't know many services that do already have bun or hono because because they are fairly modern so instead we'll just select no. JS it doesn't really matter it's just going to help with the setup and we're going to have to do a custom setup anyway and then here we want to decide how the users can sign in so by default it's selected email but ideally we would just have users log in with a social provider and then we wouldn't have to deal with their email addresses think about passwords or two factor auth or anything because if a users already set up their Google account and have a strong password there and two Factor authentication it's way more secure for them to just have that single sign on and use that for every other application however it's really common that people want to be able to sign up with their email address or you're working for a client and they want to allow people to sign up with their emails so we'll select email and I'll select Google login as well and then we'll just explore on our own right now so here is the application that represents the app that we're working with right now and if we set up another server for this app or maybe a mobile app or something we might add another application but this is all we're going to use right now so I'm going to view the details and if I go to Quick Start I'm hoping yeah so it gives me the environment variables that we need right here so the first thing we're going to do is copy these into the server project so that when we go through and start coding we'll have the details that we need to get set up with all so I'm going to copy this and in the root folder here I'm going to create a new EMV file and then paste all of those environment variables in and by default bun will'll be able to pick up that file so I'll leave that now and the next thing we need to do is actually implement this in the application and they don't have an SDK to work with bun or with hono but if we follow the typescript docs we can set it up just from their typescript library and their typescript examples so the first thing we'll do is install the typescript SDK into the backend application then we're just going to kind of follow through the steps here but I'll tweak the code as we need so the first thing we'll need to do is actually set up a kind client and then use that for pretty much everything so I'm going to copy this code block and in my server I am just going to make a new kind. TS file and paste that in and this file is going to manage all of the kind code and then these values are all going to be coming from the environment variables so I'm going to replace all of these strings here with process. EMV do those environment variables that we copied already and since this is typescript I need to tell it that those definitely have a value those won't be optional they exist in the file and we will need to make sure that these environment variables exist wherever we end up deploying the app so in this case with fly iio I'll need to tell it that we have these details so if we scroll down we're going to use some of the node.js examples they have Express here as the standard template but we can copy these and only have to tweak them slightly to get them working with hono so in my app TS I have my expenses route I'm actually going to make another route here I'm going to make an or route and every single route that deals with logging in registering anything to do with the user stuff I'm going to put in here so first I need to import hono and actually this is going to be pretty much identical to what I've done over here so I will import hono and create a new route like this so I'll call this orth route and we're just going to append all of the get requests here and then instead of Rec res we end up with a context object and then we're going to return context redirect and then we need access to that kind client that we had over in the kind. TS so I can just import that object in and I don't think I actually exported that yeah so we should export that object okay so we're exporting that we're grabbing that in here and then it needs a session manager so did I skip a step or we about no we're about to get to that okay perfect so I'm going to copy this in to the kind. TS cuz this is setting up a session manager and I guess it wants us to bring in the session manager from kind and then I can export this from my kind file as well and then I'll import that in here too I'll explain what's going on in a moment I just want to get rid of all the red lines so when I make a request actually I guess I need to bring that into app as well so we'll import the or route from routes slor and then we'll pen that here so we'll always go through API SL API login will trigger the kind login and/ API register will trigger the kind register so these endpoints are pretty basic we're just going to use the kind client to either login or register and then in the kind TS we set up the kind client object and then here we have the session manager because kind doesn't really have an opinion right now on how we store the session details so when we log in or register with kind it's going to give us some tokens that identify and authorize the current user and how we store them is completely up to us and right now it's just going to store it in an object in memory but what I want to do for this app is store all those details in a cookie so that every request we make from the client will contain the user details so it will be really easy to authorize with the server and it's also common to have a react App Store them in local storage or session storage but I prefer using cookies for all of this so I'm going to update this code with some code I wrote earlier and to get rid of those red lines I'm going to need to import all of these things so we got a type from hono and then all the cookie stuff that is going to be handled by hono down here for the session manager any single time kind gives us a new token from the service we're going to store that in a cookie in the browser HTTP only so it can't be accessed by Javascript secure so we have to have an SSL connection and same site lacks to avoid cross- site forgery attacks so this is just making sure that we can set the the tokens as a cookie and if we need to remove the session or destroy the session we can just delete the item out of the cookie and then when we need to get any of those details we get them from the cookie so this is all just dealing with this as a way of storing the details in the cookie so now in my route file the session needs access to the current context so I've just set that up to be a function that now accepts the context object but everything else should pretty much be the same and we're going to hide all of the complexity about kind in the single kind file there's not too much more code we'll need to add here then the rest of our application will just be able to access the kind client anytime we need to do anything with regards to the user off so let's finish setting this up the next thing we want to do is implement the Callback function so this is what kind will call / API SL callback once it's done logging user in or registering that user again we're going to c. redirect with the context object and the URL is just going to come straight from the context object to so this is going to be c. rec. URL and the session manager we need to pass the context object into so we can log in register we are handling the call back from kind and this will get called every time we log in or register just in case you need to do any custom code after a user logs in or after they've signed up you can do that in here then the thing we're missing is log out so let's see that's next so I'll copy and paste that in here and then do the same things call the session manager with context okay so I think that's all the routes we need and then here's some code we can run to see if the user is authenticated or not so I am going to add another endpoint here I'm going to add get slme and if I call this endpoint I just want the server to tell me if I am logged in and then maybe it can also give me my current details like my name or email or something but right now I'll just have this return is authenticated yeah I'll just return that so at this point we should be able to log in we should be able to log out and we should be able to check with the server to see if we are logged in or not now we just have to go back and make sure the application is configured correctly so if we go into details and scroll to the bottom here we have allowed callback URLs and allow redirect URLs and we are doing everything from the V server remember so this has to be 5173 for both both of these and I've made it /i/ coolback so you got to make sure these match the URLs of your environment and then click save and now if I go back to my application just going to make sure the front end and back end are both running so if I go to/ API slme it should tell me that I'm not authenticated and nothing happened here and that's because I didn't actually add my orth route so that was bad and actually I don't want this to be author I just want this to be slash so if I go to slash API SL any of these it should just go straight there okay so that should now work SL API slme should show me that I am not authenticated perfect and now if I go to SL API SL register I should be able to sign up but it's saying I have an invalid URL and that's because in my environment variables file I was supposed to update these as well to be 5173 and/ API SL callback so after I update those I'm pretty sure I should restart the bun server and then let's try this again so I'm going to go back and I'm going to try SL API SL login and yeah it gives me this login page perfect so I can choose to sign in with an email address or my Google account I think I'm going to go with my email address oh I don't have an account I guess I went to log in instead of register that's fine all right so let's go first name last name email address and then the way this works by default is it does passwordless or so instead of having me choose a password it's going to email me a code every single time which is more secure than remembering a password each time but I could opt to do passwords instead of this but I do now need to check my email address on a different window and then I can paste in that code and continue so I should now be logged in and then my redirect URL was just go straight back to the homepage but now if I check / API slme it should tell me that I am authenticated and then if I go back and log out it should log me out and then I can check these API end points again/ API slme I am not authenticated and if I wanted to log in I would have to redirect to SL API login where I could log back in again with my email address get a new code paste that in and continue and this all seems to be working perfectly and if I go and check the application tab we should be able to see here are all the cookies so it gives me an Access token ID token refresh token and a user token so these are all stored in the cookie if I log out it's going to remove these from the cookie but this is what it's using to check if I'm logged in or not so one more thing I want to do right now is when I go to/ API slme instead of just telling me that I'm logged in I want the server to tell me who I'm logged in as so I'll go back to my orth route and right here I'm checking is authenticated to see if I am logged in if I'm not logged in I think I just want to say uh let's return C.J on let's make this an error we'll say that you are unauthorized and send back a status code of 41 that's a pretty standard thing to do if we're trying to access my current details and I'm not logged in but if I am logged in then we can grab the user details from the kind client by calling get user and passing in the session manager instead of initializing this twice I think I'm just going to create a variable and pass that into to both of these functions and then I could return cjson user so check if I'm authenticated if I am grab the user details and send that back so now if I refresh I can see those details that is coming back as a string not Json uh let's see I probably got the wrong function there's a bunch of functions here I think it's actually get user profile yeah that actually gets me the user type awesome all right so those are the details that's the unique user ID we're going to use this in the datab later that's the name I signed up with my email address I don't have a picture with kind but I have a first name last name so awesome and checking if the user is logged in is something that we're going to want to do in a lot of the routes like all of these expenses routes should only work for a user that is currently logged in and on top of that we're going to want the user ID so the user details on each of those routes so this is going to be something really common that we're going to want on every single authenticated route that we have in the back end so what we're going to do now is create a middleware function using hono custom middleware so I'm going to bring this in and we're going to do this in the kind file so that this can be shared with all the different routes so I'll import this at the top and then we're going to create a new middleware Factory function and then from that we can create some middleware so this is just the default stuff the thing that we're going to want here is a user object so if I have authenticated middleware I want the user to be passed in to every route that is using that middleware so from kind I'm going to import the user profile type and that is going to be the type that this middleware function uses I'll rename this to get user that's going to be the name of the middleware function and then essentially we want to do everything that we did in the get me route so I'm going to copy all of this and put it into the middle function and if the user is not authenticated and we're using this we will just send back a 401 but if the user is authenticated instead of returning that user to the client we are going to set the user variable that we have up here so that's what this middleware function is using we're going to set that variable to be the user profile and then we can call next and I think we actually want to await that and just in case anything goes wrong we should probably put all of this in a Tru by catch so that we can console error the error and if there's any issues trying to figure out who this user is we will just say they are unauthorized so now we have this get user and actually I did this wrong I didn't need the factory I should have just put the M there we're just creating a single middleware function in this case and now it's giving me a typescript error because I messed up the types oh cool okay nice error so it's not the user profile I'm bringing in it's the user type from kind that is the type that we get when we grab the user from get user profile perfect okay so now any API Handler should be able to use this middleware function it's just the same stuff we were doing before but now let's see so we can import this into the orth routes from kind and now it works just as you expect middleware to work so when I want to get the user profile I'll just make sure I call that middle function first now I don't need to do any of this because the context object will now have access to any variables that I've set in middleware functions which right now includes that user object so if we check this we can see the user has that user type this logic will only get executed if the user is logged in and we could get their details and now I can send that back to the client so we're keeping the routes pretty clean it's pretty minimal amount of code here we've put all that logic that we need just in this kind file here and it's still only 72 lines long so implementing all this orth isn't that much typescript code so let's make sure that still works we can still get all the details so now what I want to do is actually display this in the front end of the application so that we can see that all working we are going to make these protected routes in the front end and in the back end but we're going to start slow so the first thing I want to do is set up a profile page in the front end just so we can see see how this all work together so I'm going to copy the about page cuz that's a basic page I'm going to rename this to profile rename these things to profile and then we want to make a get request to/ API slme so I'm just going to copy the code from the index. TSX so I don't have to rewrite all of that so we're importing the API we're going to make a function to get current user rename this get current user and the get request is going to be to API me.get and I'm going to need use Query from react query put that in the profile component delete that and now the way I'm expecting this to work is if it's pending we're just waiting to see if the network request is going to complete if there's an error then really the user is not logged in or most likely they're not logged in but if we get down here then we have the user data and we could present something to the user so I could say hello uh data do user dot let's see we have a family name that looks good so I should be able to present details about the currently logged in user and if we get an error on this request then we'll assume they're not logged in because we get that 401 if they're not logged in so I'll also add a profile link into the Navar profile file so now yeah I should be able to navigate to the profile page it says hello from profile and then says hello and it gives me my last name so that is how we're going to check if the user is currently logged in from the client and this is going to work just fine for a single page but if we want to use this on multiple Pages there are two big issues the first is that I don't want to have to duplicate this code for every single page where a user has to be logged in and every single time we navigate between pages I don't want to keep getting the current user really what should happen is I load up the web page it makes that request to check if I'm logged in and then it doesn't need to make that request again unless I log out or I completely close the window and then come back so the first thing I'm going to do is in my lib API file I am going to take these query options out of here you I'll copy this whole thing and we're going to Define them in this file so const user query options equals and then this is going to be query options from tan stack react query and then I'm also going to bring in this function so that these two things can at least be reused this is the function grabbing the user details from the server this is the react query configuration when we get those user details and I'm going to set the scale time to be Infinity so the moment one component uses these query options the result will get cached until until we manually invalidate that cache or the user refreshes the page which will happen when they log in or log out so this will be completely sufficient for the current way that we're authorizing the user now any file that wants to get the user details can just import this from the API file and then use use Query like this so now the request will only ever be made once and this is still a pretty trivial way of just checking if the user is logged in but we can still do better than this and it's actually giving me an error here I must have forgotten to export it and then I'll go back to profile and let's just double check that's working yes so I can refresh and I can navigate about and that Network request shouldn't be made again so if I navigate to Home navigate to expenses it is going to make those requests again because I haven't set up any caching for those yet but if I grab the profile details we should be able to see that we never grab the profile again it just happens the first time and then when I refresh the page so like I said we could just put this at the top of every file that we want to be an authorized route and then just tell the they're not logged in or maybe redirect them to the login page but we can put this stuff in a single location and not even allow the user to get to certain pages if they're not logged in so to do this I'm going to follow the guide for authenticated routes in 10stack router because there's a couple of examples here so I'm going to copy the first code block and I'm going to make a new route in the routes folder called underscore authenticated and any route that starts with an underscore is not actually a page it's going to be a wrapper around any page that starts with underscore authenticated so if I want to make authenticated routes I would just have to prefix any of these other routes with underscore authenticated because here I'm going to put in all the logic that I need just import file route I'll put in all the logic I need to verify that the user is authenticated before they can continue on I'm going to bring in a little bit more code here from the example so we're going to see a lot of red for a while but let's kind of pseudo code this out for a moment so this is going to be a wrapper around any authenticated routes before we load this route or any of the child routes what we're going to do is use those user query options to check if the user is logged in then when we're done with that we can pass in the result here so we can pass in let's say the user object and this is where we render the component so if we have no user if the user is not logged in will return some sort of login component will show them a login screen where they can go to the kind login page but if they are logged in then we'll just continue as normal and render whatever the page is supposed to be so I'm just going to fake this just so we can see what's going on here so let's pretend uh the user is logged in we'll return a valid user object just give it a name for now then in the component I would actually grab the user like this and this will work work better if I break this component out into a function and then reference that here okay so this is the component that will be rendered I'm also just going to make a fake login component uh for now let's just say you have to log in and then Outlet comes from tan stack router okay so before we load the page uh there's two options we can either be logged in or we could return null for the user and not be logged in so let's check this state first so before we load grab the user return the user out of this before load function then the component that this renders has access to that object if we found a user if we request that from the server and we have the user then we're going to render the pages as normal if we didn't find a user then we'll just render this fake login page for now so this should already work we're not making a network request we're just faking it here but right now any authenticated routes should just show this login page because I've set user to n manually so the way we can make authenticated routes is prefix routes with this underscore authenticated or a nicer way would be to make a folder called underscore authenticated it just has to match the name of this file here and now any routes that I want to be authenticated I just put into this folder so I want someone to be logged in before they can create an expense see expenses see the home page and see the profile page but I will allow the about page to be viewed by anyone so I'm just going to move those into that folder and now if any of these pages are requested this file first gets executed we check if the user is logged in and then we navigate that person either to the page or to the login screen so all of these Pages now should be showing me a login screen if I try and visit them so the homepage says I have to log in about says I have oh no about it's okay expenses says I have to log in create profile these all say I have to log in to see these pages but about is the only not authenticated route so I can still see that page as normal and that checks out cuz it's the only one out here so that's how I make auth indicated routes I think these are complaining because the routes changed and sometimes the route tree. gen just opens itself up and you have to close it don't save it save a file and everything will go back to normal and to double check this is all working let's pretend we are logged in and I will pass in a valid user here if there is a user we just present the pages as normal so now I get normal homepage expenses create profile perfect okay so now I just need to make sure that this is actually performing the HTTP request to the server to get the actual user and we're going to do that with the query object from tanack query so I need access to this query client here and the way we do this is we can pass in context to the router here so we've passed in the route tree that was just the boilerplate code we set up but if we want every single page to be able to access some sort of object some sort of data in this case the query client we can pass down context to every single one of those those pages there's just one more thing we need to do to make sure we get type safety with that create the root route with context so I'm going to copy these two things we're going to go back to the rout and I need to create this as a root route with context I'm going to copy in that interface my router context and then we invoke this as a function and then pass in the rest of the stuff and I need to grab query client type from tanack react query so this is just specifying what the type of data is going to be in the context object but in the main. TSX we can pass in the data that goes into the router context but this does now mean that every single route including my authenticated route here is going to have access to that context and in the before load function that routes get we just have access to that directly in context that gets passed in so now the query client that we're using should be available on context. query client and the only reason I have to use the query client instead of usequery is because this isn't a component so I can't use a hook in here so now from the query client we can fetch query we're going to make sure that we're always using the user query options so that if we fetch this somewhere else we're not actually making the API request again we can grab the user from that and this user is actually going to have the user object on it so this is more of data so if this is successful we'll have an object with the user details on it so right here I can just return the data and that is going to give me the user object there I can remove all of this and then I just need to handle the error case so if this fails for any reason the most likely one being that the user is not logged in I'm just going to return user null so this is what is determining if the user is logged in or not logged in and it also gives us access to the user details so now that should all work if I'm logged in I should be able to see everything which I can and then if I'm logged out I won't be able to see anything so let's quickly add in on the login page and href to/ API login and we want to make this a normal link because it isn't a page within our application it is going to go to kind servers to do this so that's just a login button and then I'm going to do the exact same thing on the profile page I'm going to allow a user to log out so now I should be logged in but I have a log out button there I can navigate around all the pages if I click the log out link it should just log me out and all of these now become protected routes I can't access them but I can click this login button and then go through that login process again and then as soon as I'm logged in I have access to my details so I can view all these pages and I can view my details on the profile page so this is how we're managing the currently logged in user State and the only thing really left to do with regards to orth would be to to go to the backend routes for expenses and I want to import that get user middleware from kind and I want to attach this to every single route that requires me to be logged in which is really every single one of these so I'll add those all in and now I won't be able to make any of these requests unless I'm actually logged in and I can also grab the user details from C.V var. user and then I'll use the user ID to grab expenses only for that single user and the same thing for the rest of these routes but for all of that to be more meaningful we should set up a real database which we'll do in a little bit there's just a few things I want to do first let's clean up the UI just a little bit in the front end and I'm just going to make some Max widths with some margin Autos so we'll start with the Navar actually I'll just do this around the outlet too and then that should bring everything in a little bit I don't want the flex okay yeah that that all seems fine profile page could be nicer and the login page could be nicer but I think that's good enough and then the next thing I want to do is actually deploy this app to fly iio so we can see that it actually works deployed and back in kind I've set up the URLs to only be the Local Host URLs so I have this as my production environment and I could create a Dev environment and a production environment but to keep things simple I'm just going to have one environment for everything for now and then I can always add more environments later on anyway so I'm going to grab my deployment my production URL I'm going to copy that and paste it into my details here so the entire URL will be my allowed logout URL and my callback is going to be the same thing SL API SL callback and then I'm going to save these two like that in kind and then I need to send those environment variables to my fly iio server so I'm going to go to fly iio and see how we can do this cuz we can actually settle these environment variables as secrets with one simple command here perfect so if I run this I'm going need to change the file here but I'm going to run this from my backend directory basically we're going to tell it to import the EMV file that we have we have the environment variable set up it's going to store all of the environment variables as secrets so they're going to be encrypted on fly iio but they'll still be accessible through process. EMV so this is all we have to run to get all the local environment variables on the server or if we wanted to set different environment variables I could say uh fly CTL Secrets set and then I could just specify a secret equals whatever and then that would set it for the specific application but we're just going to use the same ones we have have in. EMV so now I should be able to say fly deploy and this will deploy the app with those environment variables and we should be able to use the app as we did locally log in log out and view the authenticated Pages only if we're logged in so now that's deployed I'm going to refresh okay so it says I have to log in I did not fix that login page all of this seems to be working so let's click login and I got an internal server error because I didn't set my URL in my environment variables to be different so this for production needs to be my production URL which is going to be this so my redirect URL is going to be that and that so now I need to set these as the secrets so I'm going to run fly Secrets set and then paste that in and do the same for this okay so those are both being set and I really should have checked that before deploying but that's fine it's one benefit of deploying incrementally you can catch the bug that you were just working with in the moment that you're working with it so that's nice okay so that should all be updated let's click login and it logged me straight in I guess it must have cached those details from logging in with Local Host so I'm going to log out make sure I actually get to the login screen perfect okay so I can log in here go through the login process and now I'm able to access the rest of the site and if I go back to kind as well and go to the dashboard here I can see the single user that's logged in I can view their activity delete or suspend the user and I can see all the users that ever sign up to the app so that's all working now that's great we have or completely set up so now it's time to add a database and before we do that let's talk a little bit about how we actually deploy apps so we have all the server side code that we've written and we need to deploy this code to a server in the cloud and this could be a serverless function or a container or a virtual machine it doesn't really matter because the plan is always generally going to be the same we're going to get users to our application and they are going to visit the app by making a request to our server and then if we get lots more users making a lot of more requests to our server the plan is always going to be to copy our code to another machine or Lambda function or container it doesn't really matter the way we handle more load is to have more instances of our code running to be able to handle all the requests and this works the other way too if we get no users using our application we'll scale this down and we can even scale all the way down to zero and this is why most Services don't cost any money when you don't have any users cuz it's pay for what you use so this is a really decent plan for scaling up you just run more instances of your code to handle more users but this does mean that you can't persist any data on any of your servers or containers or functions because at some point that data will get deleted so we have this space where all of our compute exists and we need to have a plan for our server side code to be able to access file storage from some other location if we're dealing with images or static assets like that and we also need to have a plan for where the database is going to exist cuz all of our serers side instances are going to need to access the same database but the database is going to have the same problems where it needs to scale up and down depending on how many users we have but it also has to persist that data so really what we want to use is a database service that manages all of that scaling all of that replication anything to do with databases we want to be managed for us so we're going to use a database service and I asked online and everyone voted for postgress so what we're going to be using here is a postgress database service and you could have a postgress instance installed locally on your machine for local development but when we end up deploying the application we're going to need a cloud postgress database service and the one that I'm going to use in this tutorial is going to be neon purely because they have been one of the easiest services to get set up with but if you have your own opinions on which database you would like to use or which service you would like to use you can use whatever but I'm going to be using neon and postgress for the rest of this and this will will mean that when we go to deploy the app everything will just work and then we also need to think about what the database is going to look like so right now we only have a single table it is for expenses so we have an ID this is going to be serial which is going to be an auto incrementing integer in postgress we're going to have a user ID and if you remember from the response that we get back from the server the user ID is going to be this string here so we're just going to say that the user ID is is a string we currently have a title which is a string and an amount and the data type for the amount is going to be special because this is for monetary values so if we check the postgress docs there are a few different types they have for storing numeric values there's integers decimals reals doubles so which type should we use for money we don't want to use integers because those are whole values and it would probably be okay to use an integer but then we would always be storing the amount in cents instead of in the dollar amount so it' be nicer to use something that has decimal values but we can't use floating Point values because those are inaccurate we can easily see in a JavaScript console if I add 0.1 to 0.2 we don't actually get 0.3 so this is unacceptable for monetary values if I check monetary amounts here postgress suggests using numeric with a precision and scale so this is the amount of values we can have and this is the amount of values that go after the decimal point so if we go in here and we choose numeric 12 2 this will give us enough values 1 2 3 4 5 6 7 8 9 10 11 12 two for the decimal this will give us enough to store a billion dollar so I think that might be enough for this expense tracker so I'm going to go with that amount in the database and this is just an ERD I'm not actually coding anything out here I just want to visualize it before we actually start coding so we're going to use postgress we're going to have a single table for now and this is what it's going to look like and then to connect to the database we are going to use drizzle omm which is a really great typescript om that is going to make it much easier to write SQL code it's going to let us generate and apply migrations it has this drizzle studio so we can view the database in a nice web interface and there's a lot of features to this Library it's really worth learning it's one of my favorites this one deserves a whole tutorial on its own because we're only going to touch the basics here but this is what we're going to be using to get the database set up in our application so I'm going to start with Drizzle and I'm going to go to the postgress section and we're just going to go to postgress DJs because the neon instructions tend to be for serverless environments and we do not have a serverless environment right now so we're going to bun add drizzle and drizzle kit to the backend application and then we're going to follow through some of these instructions to get things set up so I'll copy this code we're not going to need most of that but in the backend application I'm going to go into server I'm going to make a new folder called DB and then in here I'm going to set up an index.ts file this will be the entry point for the database I don't need any of this migration code we will come back to this in a little bit but all I really need is this so we're going to set up a database object with Drizzle and this is going to be set up using a database URL and we're going to use an environment variable for this so this is going to come from process. EMV and I'll probably just call this database URL so now I need a database URL we're going to set that up with neon in a moment but first let's finish looking at the rest of this so I'm going to scroll down to the overview in the managing schema section I think I'm just going to pull out the first example here and we'll modify we need to so we have the index.ts this is going to manage the connection to the database I'm going to make another folder here called schema and here's where we can put all of the tables so right now we just have an expenses table and I'm going to dump in the code they've given me this has two tables so I'm just going to delete the bottom one I don't need an enum it's going to be a really really basic table so I'm going to call this expenses and everything in strings here is going to be what appears in the actual database so in postgress this is going to be expenses ID is going to be serial it's an auto incrementing integer and it's going to be the primary key we're going to have a user ID which is going to be text and in postgress that should be user uncore ID and we need to import all the types they're coming from drizzle rmpg core so if you're using MySQL or SQL light you would just be importing it from that specific part of drizzle I'm going to bring in text there we're also going to want a title which is going to be text title both of these actually should be not null finally we're going to have an amount which is going to be numeric and I need to import numeric to use that type name in postgress is going to be amount with Precision 12 and scale two this is going to be not null I'm going to delete this name here I don't need it but I wanted to keep this block of code at the bottom here because this is how we can create indexes I want user ID to be an index on this table because I'm most likely going to be searching for expenses using the user ID for an equality check like grab me all expenses where user ID equals this user ID for the specific user we're going to create a user ID index it isn't going to be a unique index it's just going to be a normal index so I'll bring that in and this isn't called countries it's called expenses so we're using a user ID index on the expenses. user ID and this is just a performance thing if you know how you're going to be accessing data you can just add indexes early on so this is the entire table now we can modify this later the great thing about drizzle is that we are defining the current structure of the table so I don't have to say create table or alter table later on I'll always just modify this object to be the current state of the database and then I can apply a migration and it will always be whatever I've specified right here and I get a lot of typescript safety by doing things this way and if you wanted to check the docs there are all the column types here you can read through that you can see what all the options are but I have enough for the expenses table so I have the schema defined uh I have my database object being created I don't have a database URL I don't have a real database yet but I can still code through my app using this and Implement some of the things if I wanted to so I could go to the expenses route and I'll just show you how this works so I can import the database from database folder /index which is actually Dot slash and then I would also import my expenses table from DB / schema SL expenses and then if in my expenses route I wanted to actually select the expenses from the database drizzle would work like this so I would go db. select and it's just going to seem like normal SQL but with all the type safety so I'm going to select everything I'm select star from my expenses table and generally I like to store this in a variable where I would say const expenses equals so instead of just leaving that as expenses here I'm going to Alias it as expenses table so it's very obvious in this file that this is the table I'm talking about and every time I have expenses it's the result of calling a query so select from the expenses table and then you can add other things on so I want to only select the expenses for the current user so I can say where equals and equals is a function I need to bring in from drizzle omm so import equals from drizzle omm and you can see this all in the docs too if I went back to select statements here you can see all the different options that you have all the different functions that you can bring in and if you know SQL writing these queries is going to feel really familiar so I'm going to do an equality check for where the currently logged in user cuz we have that now where the user ID is equal to the expenses table. user ID and I got that the wrong way around I'm supposed to put the table one first and now that works and obviously this won't grab me anything so we haven't inserted any expenses into the database with that user ID but this just goes to show that we can start coding out the application with type safety this is going to tell me the exact type that I'm getting back and it tells me that I've messed up I'm supposed to await this so this will show me uh yeah if I try to get all expenses this is the type I have an array of these types right here the expense type Ty and we could go pretty far here without a database just coding everything but if I try to run this application it obviously won't work cuz I don't have a real database so I'll implement the post and the get just like that uh then we'll set up the real database make sure those two things are working and then we'll implement the rest of these so if I want to make a post request into the database it would be db. insert into the expenses table and the values are going to be the expense that's being validated with Zod so I'll destructure that because I also need the user ID so this is a validated route I can grab the user and then say that the user ID is going to be user. ID and this is complaining because I originally said that the amount was a number earlier on when amount is now a decimal type now this is a really interesting thing the expense has a numeric amount this is a decimal amount that exists in postgress but JavaScript doesn't have any sense of decimal values it has doubles and that's it and we can't use doubles here because they're not accurate enough so this is actually going to be a string type in JavaScript when we get it back and we'll see how to work with this table a little bit better in a moment but for now I'm just going to update the create post schema here to have title be a string just to get this working and then I'm going to set all the amount here in the fake database to also be strings so in the database it is still a numeric value we can still perform some functions on it or any math functions on them but as soon as it gets into JavaScript land they're going to be strings to make sure that everything's safe and I'm getting some errors from my fake database stuff I'm just going to fix this temporarily in the worst way possible it's always a good note to myself that this isn't a real fix this is just a temporary fix for something okay so now the insert should work I'm not pushing into to the fake database anymore what I'm actually doing is inserting into the expenses table this is just going to insert it's not going to return the result back to me but I can append a returning function onto the end here and this will give me back the result of inserting this expense into the database and then I could return that to the client if I wanted to uh maybe I will even though I'm not really using it I'll still just return that down so now post will grab the data from the post request grab the user details and try and insert that into the database the get request will grab all expenses and return that back to the user again none of this is set up to work with a real database just yet but we will get there one more thing I should change is that in my front end my create expense is probably complaining now because my value is a number instead of a string so I'm just going to make sure that that is also a string all the time too and that's a good thing because then the value that is input will be accurate all the way up to the database so I've got some queries written here I have my database code set up here and I have a table schema set up in the schema folder the only thing I really have left to do is to create an actual database get a URL and then hook this up to that real database so it can make queries to the database so I'm going to go over to Neon and I already have an account so I'm just going to log in but if you don't you can create an account log in I don't care about the new plans I'm going to make a new project my expense demo postgress 16 is good I'm going to put this closer to where my application will be that amount of ram I think is good I think that's what you get on the free tier anyway so that's good uh we'll just leave all the other settings as the basic settings and set up the database now I get this connection string and this is all I need this is why neon is pretty easy to get started with this isn't a production ready string I would want to create a rooll and limit access I get complete admin access to this database I'm going to copy this into my application I'm going to go to my EMV file and create a database URL EMV paste that in I'm going to put it in single quotes because this sometimes doesn't work unless I have it quoted and now I have a database URL so the database exists but the table does not yet exist in the database this expenses table so the next thing I need to do is create a migration for this expenses table and then apply that to my postgress database back in drizzle I'm going to scroll down to the migration section and here is the quick start getting started with migrations declare your schema we've already done that create a config file so we need to create a drizzle config TS file in the root of the project so I'm going to copy that I'm going to make sure that I'm in the root folder the very root folder here and we're going to make a drizzle. config TTS paste in that code this is going to be for postgress and the DB credentials can be a connection string which is going to be process. mv. database URL and the schema exists in server SL DB SL schema which is a folder so all the files in the schema folder and we're going to Output the migration files to a drizzle folder in the root directory I'm happy with that so that looks pretty good and then we should just be able to run drizzle kit generate and instead of my SQL I'm going to put in postgress or PG so if I do this from from the root directory it failed because I don't need EMV with bun I completely missed that line okay delete that line so this is what the config should look like I don't need this comment either so now that should create a migration Perfect all right so it said it created one table we should have a drizzle folder here and inside don't need the meta we have this SQL where it's going to create the table if we were to modify the schema in the typescript file over here for the expenses then if we ran a new migration we would get an alter table statement so that's cool now we just need to run this against the neon postgress database and to do that I can actually take the code that it gave us in the beginning so in index.ts I commented out some code here I'm going to take this out and I'm going to create a new file here called migrate dots and I'm going to paste that code in the migration client is going to use process. EMV database URL and then we just need to give it the migrations folder which is going to be/ drizzle and since I only ever run this as a configuration script I don't put it in my source directory I usually put it in the top level directory so I'm going to run migrate it's going to run the migrations from the drizzle folder against the postrest database on neon so now I can just run bun migrate dots and this actually needs to be in an object migrations folder give it that string and this is also async so I'm going to wait this and maybe I'll also put a little console log at the end migration complete and let's try running that again okay perfect migration complete uh the file didn't stop itself so I'll just stop that if I run this again perfect we get a little warning hey there's nothing to migrate so that's awesome that appears to have migrated successfully and we could go into neon and check this but we can also use drizzle Studio which is something that comes with Drizzle and it's a really nice interface of interacting with your data and your data datase and to use this all we need to do is run bunx drizzle kit studio so I'm going to run this oh yeah for some reason drizzle kit Studio needs the PG package I don't know why but let's install this uh B install PG and then we'll run drizzle kit studio so it's available at this URL and I can see I have an expenses table that's awesome so here is my entire database it says I have an expenses table I can see the schema here I could put in some fake data if I wanted to but this is just showing me that everything was set up correctly so I still have the server running I still have the front end running if all the code has been set up correctly I should be able to go to Local Host 5173 and I'm currently logged in so let's see let's create a new expense a new expense open up Dev tools just in case there's an error here and I'll just put in uh say 10.50 why not submit that I still have that fake loading thing in there so that's going to take a moment and then it seems to have worked it seems to be good expense went up it was saved correctly I have the user ID there when we requested it back down it seems to be good and if I check in drizzle Studio I just refresh it I can see that no expense was created it exists in that neon database in the cloud there's the ID the user ID the title and the amount so all of that is connected up together all of it's working perfectly we just need to make sure the other endpoints are working as well cuz currently getting the total spent is still pulling from that fake database so I don't need that file anymore I don't need most of these I'm going to close them all back in the server I have expenses and I am going to delete my fake expenses I don't need those anymore we're going to make this all work with the real database get is working we're selecting all of the expenses from the database we would probably want to add some pagination at some point and limit this to a couple of expenses maybe 100 at most but I'm going to leave this off for now that's something that you can Implement if you want to actually I would also always add an order by well you know what we should add one more thing to the schema what's really nice is always to have a created at column and I'm going to check the docs for this cuz I am certain that they have a created at Tim stamp just copy that and we'll call this created at and I have to bring in timestamp here so we'll always have a created app time stamp so we'll know when the expense was created and then we can order by in descending order so I'll have to bring in the descending function expenses. created at so we see the newest ones first and the oldest one last in the list but because I've updated that schema I now need to run the migrations so the first thing I do is drizzle kit generate PG to generate the new migration so I get that new migration file it's going to be the alter table to add the timestamp default now and then I can bun Mig great. TS and that migration is complete and if I check drizzle Studio we should see that new column in here with a default Tim stamp added perfect okay I'm going to select change that just a little bit to have a limit in an ordered by for post I don't think I need to change anything here we're just inserting the expense value and the user ID that we're grabbing from the cookie which is coming from the middleware total amount spent this is going to be different now so we're going to to cons total equals db. select from expenses table where the expenses table user ID is equal to the currently logged in users ID and I need V user equals c.v. user no not VAR const and we're not just selecting all the expenses what we're going to do is select the sum of all the amounts for for that user and this is actually going to be an object so I'm going to say that the total this is the thing that we're selecting so select the sum as total from the expenses table where the user ID is equal to the currently logged in user ID and the sum function needs to be imported from drizzle omm so I'll import that and this should be good I need to await it let's see the type we're getting back so this is an object actually it's an array of objects that contains a total that is going to be the string so that's kind of annoying even though we're only going to get back one value these select statements always return an array so in a case like this I'll just depend on a then and have that return the first item out of the array I know it's only going to be one item so we'll just force that there now this object has a key total and a value that is going to be the total amount so I'm actually going to call this result and then just return this result since it's going to be an object where the key is total and the value is the sum then for getting a a single item it's going to be really similar to getting all the items here so I'm going to copy this from up there so the ID is coming in from the URL then we're going to select from expenses where the user ID is equal to the currently logged in user and where the expense table. ID is equal to the ID that was passed in so we have two equality checks here so we can just wrap this in an and function which we're going to also need to import from drizzle so I'll import that and then it's complaining cuz I didn't make this async that's fine so we're going to grab the single expense from the database where those two things are equal I don't need a limit 100 it's definitely only going to return me one result if I'm checking for an ID equality check on the primary key so I am then going to make sure that this just gives me the first item out of the array the only item item out of the array and then I can delete this part here and then if there was nothing in the array we're going to return not found otherwise we'll return that expense this is going to be similar for the next one too where I can paste that in make sure this is an async function and instead of finding a single item here we're going to delete the single item from the expenses table so everything else I guess I don't need the order by I didn't need the order by up here too if we're just getting that single item so we're deleting where those two things are equal and then because this is a delete it's not going to return anything by default so we just have to append the returning thing in here so delete the item return what you deleted make sure you only get the first item out because it's always going to return an array then if there was nothing to delete then there will be no expense return to us so we return not found still but if we were able to delete that then we can just send back the deleted expense to the client in case that's useful I don't know but all of these now have the SQL statements that we would need to access the database correctly so this should all now work if I go back to the front end application I haven't implemented anything in the front end to be able to delete expenses but I can see the expense is there I can create another one here let's set this to just be some random numbers submit that I still have that uh weight in the front end that I should have deleted by now when we create an exp expense I have that fake wait 3 seconds before you do anything so we can see the loading state so I've deleted that now so created the expense we can see them in descending order if I go home yeah I can see the total amount so this all seems to be working pretty well it really didn't take much to get those pieces there so now I want to deploy this again to fio I want to make sure that everything's working in production and local so I'm going to take that URL again from the environment variables and the first thing I'm going to do is send it straight over to fly IO so fly Secrets set the database URL and then I'm just going to fly deploy to deploy this app and this should just work on my servers because the kind or service is already in the cloud because neon database is already a database service in the cloud when we go to host the application everything should just work as long as we have the environment variable set up correctly okay so that's deployed let's check out the URL and see if we have a full stack web app actually deployed so there we go I'm still logged in as myself I can see my expenses I can see the total amount I can create a new one let's just do $1 here I can see that in the list that's perfect so this is now a full stack deployed react app with a database with orth and this is a pretty decent Foundation to start building off there are a lot of things that you can add to this or you can use this example to then build pretty much any application that you one now we're going to add a few more features and clean things up a little bit the UI and the code and the first thing we're going to start with is actually adding form validation here using 10stack form so that if we were to try and create an expense without a title for example or with an incorrect amount uh it would actually give us a little bit of warning so if we go to the tan stack form docks there's a section on form validation and there's a few different ways of actually validating the form input but we are going to use Zod since we're using that in the front end and the back end and I will show you how we can share code between the back end and the front end so that we can share the Zod objects but we'll start pretty basic first so the first thing we're going to want is the 10stack Zod form adapter so we'll need to install that into the front end FN add that and then we're going to use the Zod validator when we set up the form so I'll just copy this line find the part where we're using use form right here and say that we're going to default to using the Zod validator we need to bring that into so import Zod validator tan stack Zod form validator and then this is an example of one of the validators so I'm going to copy that for the field and paste that in here so let's just start with the title uh I'm going to add the validator right here and we're going to need to bring in Zod for this so I'll also just import Zod and now basically this validator is going to work by checking for every time there's a change in this field we can specify some sort of requirement with Zod so I'm going to say that this has to be a string maybe with a minimum length of three we can just keep it simple like that for now and if we go back to the app we should be able to see this so right now there's nothing in there if I try and submit this I'll now get this error message coming from Zod and as I start typing in it will go away when I hit those requirements and then if I start deleting these characters it gives me that message again and we can customize this message a little bit so if we go back in here where we say it's Min three characters we can put in a message saying something like title must be at least three characters so we'll just leave it like that and now when I start modifying it yeah it says the title must be at least three characters and since this doesn't pass the validation I can't actually hit the submit button so this is pretty nice that's a simple version of this but since we're already using Zod in the back end and we already have some Zod validation so if we go to the backend routes to the expenses route when we make a post request to create a new expense right here we are using the backend hono Zod validator with this create post schema that I've set up right here which is validating against this stuff on the back end so since the thing being input into the form is being submitt to the back end and the back end is doing this validation before we'll even accept the input it would make sense to use this Zod object in the front end of the application as well so what I'm going to do is take this Zod code right here cuz this is going to be shared code I don't want to share all of this HTTP code with the front end I just want to share this sod code so I'm going to take this and put it into a separate file so I'll cut it out of here and just in the back end of my application I'm going to make a shared types file maybe and I can paste that in here and then I'm going to need to bring in Zod from expense stud don't need this in here anymore so I'll just cut that out put it in shared types and I might rename this file or refactor this out a little bit later but for now for this simple app with a single type this kind of makes sense just putting it in a shared types file but you could always name this file something different or structure this really however you want just as long as the shared code goes in a separate file now I'm going to export that schema and I could export this schema too I don't need this type expense and in my expenses route on the back end I can import this create post schema from shared types and then this should still be fine ex it's just using that and then we're going to do the same thing on the front end now so if I go back to the create expense form on the front end from my back end I'm going to import the same thing create poost schema from the server shared types file and as I'm writing this I don't love the name I've given this so I actually want to rename this to create expense schema I think that makes way more sense so I'll update that in shared types and in my backend route two I just think that name makes more sense especially as we're using this code more places so I'm going to take this now and I don't need to specify my own Zod schema on the front end CU I'm just going to be reusing the stuff from the back end in this validator field instead of creating a z string here and having to Define what I actually want I'll just say that we're going to use the create expense schema. shape. tile so this will be effectively the same thing so if we go back to the expense schema I can see that the title has a minimum of three and I've given it a Max of 100 we can put in some messages here too so title must be at least three characters and I'll do the same for Max 2 so I have this schema set up just in one single place it's shared in the backend route and in the front end route and I don't need to use Zod directly anymore and I'm going to do the same for the amount as well so if I come down here for the amount we are going to use the amount property from that schema which isn't really going to do much right now because we're just saying that it's a string but we could add some reject here and I'm just relying on co-pilot to do this for me but this is going to specify that the input must be a number it can't just be a random string uh which is going to be difficult anyway since this input form only allows uh numbers anyway but if someone opened up Dev tools or something uh at least the schema wouldn't allow this to be submitted directly but it is also going to avoid having negative numbers which is maybe something that I want and I do want in this case I don't want expenses that are negative in this application and right now the message just says invalid so let's give that a better message here we'll say uh amount must be positive and now in the front end let's see yeah okay so now I get that message too so that's kind of nice and again this is being shared front end and backend so we're validating that before they submit the form and after they submit the form and make the request to the server and I can actually go one step further with this since when I'm posting this in this is tightly coupled to the schema that I have the table that I have set up with Drizzle and if we check out the drizzle docks they should have a section on drizzle Zod yeah so we'll need to install drizzle Zod and then we can actually create Zod objects from the schema that we've defined in our schema typescript files so this can be handy because then if we add new properties to our table they're just going to automatically appear in that schema validation object so let's just take a look at how this will work first in the back end of the application we're going to burn add drizzle OD and then I'm going to take some of this code from the example here so we're going to take the create insert and create select schema paste that in here and and then I'm going to copy this code and then tweak it as I need so this is for a user table of course we're using expenses instead so I'll modify these things and this can be kind of handy because the insert expenses schema will have all the properties that we need to insert a new object so user ID title amount they all have to be values they're all going to be string values but the ID since that's going to be populated is actually an optional zard value here and create that because as a default value is also going to be an optional so that's kind of nice then when we select the schema those aren't going to be optional because they're definitely going to be coming from the database since they're not null so we can use these anywhere in the application especially in the back end to verify we have the correct Data before we even try inserting into the database so I'm going to export these so they can be used in my expenses route I could bring this straight in from the schema actually I'm importing the table here so I could bring in the insert expense schema the Zod object and right here in my post instead of immediately trying to insert that data into the database the expense object has already been validated there but the actual insert object is going to have the user ID place as well so I could create a new uh validated expense object and just make sure that that is valid so these things the expense that is coming from from the client application mixed with the user ID just make sure that that passes the validation test and then pass that into the database if it does and if it doesn't this will throw an error that I can handle in a custom way here or I can just let the hono error handling take place but really it's a nice place where I can control if this is valid for the database or not before I actually try submitting it to the database but then this create expense schema the data that is coming from the client is really just a Vari of this right here I don't want them to submit the user ID from the client the ID and the created app when I'm inserting also shouldn't be provided but everything else is really just a variation on what is being passed in here and no matter how it's being inserted I do want those extra validation checks to make sure that the title has a minimum length and the amount is actually a string so I'm going to take these I'm going to come in here and as we're creating the schema we can customize some of the options here so I'm going to override the title and the amount Fields just to have my custom Zod logic in there and I'm going to need to bring in Zod for that so now it's still the same it has those values ID user ID title whatever but now these ones have a little bit of custom logic to make sure a number is put in for the amount and the title has a Min and max length here and actually I don't really want the max length otherwise I would have used varar anyway so I'll take that out that is good so that'll always be used now but what I want to do is take this and put it into the shed types as well so I'm going to import that from the schema file and then my insert schema again is just going to be based on the stuff that I have to insert into the database so instead of creating a brand new object I want this to be the insert schema but I'm going to omit the user ID and the created app because I do not want when someone is posting a new expense it's actually going to be my create expense schema when someone is posting that from the client I don't want them to specify those two things so this is what going to go in the form this is what's going to go in the post request and it's based on what we're actually having in the database but with those few variations and then if I had another thing that I added into this like uh I want to add a date so that people can specify which date the expense was based on that will automatically propagate to all the other parts of my application to make sure that that is valid and verified before I actually try submitting to the database so that's pretty nice and I'm using that in the form 2 so everything should still be working the same way if I try in putting a title that is too short and if I try inserting a number that isn't correct this should work with decimals too oh it's actually doing yeah more because I'm only supposed to be able to go up to two decimals there but this should work otherwise okay so I should probably change the error message in my schema so uh I don't know amount must must be a valid monetary value let just go with that as the error message and one thing you might have noticed too is that the styling isn't great here because it's pushing into the next thing and if this has an error it pushes the button out of the way if I put a negative sign there so I'm going to update the form a little bit make the styling a little better so in the create expense form let's change this a little bit so I'm going to use flexbox flex call and then I'm going to wrap each of the elements so it's a label and an input I'm going to wrap these each in a div just so they don't interact with each other so the label from this one or the error message from this one doesn't interact with the label from this one so wrap both of these in divs and I might also add a little bit of a gap y maybe four here okay so everything's spaced out a little bit more they are completely separate from each other the submit button went the full length I don't hate that I'm going to leave that as is and then just to make sure everything's working let's try submitting an expense here and yeah everything still works the same way but now we do have that little bit of validation there that isn't just a validation in the front end it is on the back end it is before we get into the database so we are covering all of our bases using that Zod validation and while we're on this form I do want to add a way for the user to be able to specify the date that they spent the expense on so it shouldn't always just be this dat I spent this amount of money I should be able to say you know a week ago a month ago whatever just specify from a little date picker so if we go into the Shad Cen components there should be somewhere a couple of date or calendar things so this is a date picker where if I click it the calendar pops over I just want a calendar there all the time so I'm going to find the there it is calendar component so we'll have one of these so a user can actually specify which date a expense was actually made so for this we're going to add the calendar component to the front end just add that in there and then we'll copy the code to get the calendar into the application so we'll need to import the calendar and then just add it in like this so I'll in the create expenses page import the calendar in with the other components and then this is just going to be another form field we'll have a couple of things to do here but we'll just start by duplicating one of the form Fields over and instead of having a label and an input we'll just put the calendar component and we don't have state set up for that that's fine for now because we're going to need this to work from front end to back end so we're going to need to update the database table and everything so I'll just start coding things out right now so the name is probably going to be date on the schema will have a date property and then the selected and unselect we're basically going to copy these things so the value is going to be the selected there and on select this is going to be the same as the onchange for the other ones so on selecting a date we're just going to update the form value so a lot of red happening here but we are going to get this calendar component in I'm pretty sure oh no okay so the calendar component is showing up it's not going to work let's just style that a little differently I'm going to have this self Center itself in flexbox okay so there's the calendar for the date we're going to Select Title amount okay so that all looks nice but this isn't going to work obviously because I haven't set any of this other stuff up so I have the calendar I know what I want to happen in the front end but now we're going to work from the back end forward to get this working so the first thing we're going to need to do is actually add a date to this table because we don't have that in the schema so we're going to need to do that create a migration actually push that to the database so so I will add a date here which is going to be of type date I'm going to need to import that from drizzle and then I'm just going to call that date and make it not null so that should be fairly basic and like with most things this is going to end up being a string when we pass it to the server as a property of the Json object it's going to be a string when we put it into the database it's going to be a string but in JavaScript in the front end at times we are going to deal with it as a date so there is going to be conversion back and forth there but we'll figure that out as we go so because I've added it in there and I'm sharing those types that should have propagated everywhere so now it's expecting a date when I post it to the API my form is also going to expect a date before I can actually post to the server so now I'm going to add yes see it's complaining there because it doesn't have a date type so that's really nice everything is working correctly for the default values I'm going to add a date property right there and this isn't going to be a new date object it has to be a string so instead of just specifying an empty string here we are just going to convert this to ISO string and if we take a look at this in a JavaScript terminal we can see that just converts a date to a string that looks a little bit like this and the only bit we really care about is this date right here because this is what the database is expecting a date in this format so as long as we keep it as an ISO string this will be good so now if we go back into the code when we have the calendar set up we're setting its value to be a string value but this calendar component is expecting a date so we can just create a new date on the Fly for this based on that string and it will be able to use that correctly and then when we're setting the date we need to do the same thing as before convert from a date to a string so we'll just convert this to ISO string and I think I got this a little bit wrong this isn't going to give us an event object this calendar component is actually just going to give us the date object so it's not e. target. value it's just date that's kind of nice date might be a date object or undefined if we select nothing in the calendar so we'll do date if one is selected and if it's undefined or null then we'll just create a new date object which is the current date so that's a fine default so now on selected we take the date that was selected if one wasn't selected we'll just say it's today and we'll convert that to the string and now I think this mostly yeah this should be working there aren't any errors in there so we just added that new date field the Zod validation object is expecting that so this would be complaining if we got any of that wrong we have these default values so that should be it everything should be valid the only thing that won't work this will fail uh because I haven't migrated the database so neon doesn't know that we want to specify a date for these expenses so we are going to need to run a migration here so if I go back into the back end we're going to need to run drizzle kit generate the migrations where has given us a new migration file let's just double check that so we're just adding the date column there that's perfect and then we will try and Bun migrate dots which will fail because we have current values in the datab base that contain null values but I'm saying that the date is a non-null column so we could make this value nullable or figure out a way of inserting some default dates for the values that already exist but since I'm still in development I find the easiest thing to do is just to delete all of that test data that I have in there and start over since no one is actually using this app anyway so I'm going to open up drizzle kit Studio which also doesn't want to work because date does not exist it's actually kind of funny cuz it's using the existing schema so I should have done that before adding date but there is this new SQL Runner tab so let's try that let's say uh delete from expenses and see if it will just run that and delete everything not sure if that work let's try select star from expenses okay so it's saying there's no rows maybe that worked let's try again so I'm going to try the migrate file and it looks like the migrations are complete okay so I managed to drop all of the rows using the SQL runner in uh drizzle studio and now the migration should be complete so if I check out the expenses table so I'm going to re start drizzle Studio I just want to see that this is working uh so it looks like yeah there's a date a created at amount title user id id so this looks like it's working I should have no expenses in my database anymore so if I close that and go back to the application if I navigate around now I don't have anything that I've spent and my expenses is completely empty okay so that makes sense so now if I create a new one uh my first expense and I set this amount to something I should be able to specify a date now so let's just say it was the 1st of March submit that and now my expense uh appears here there's no date obviously because I didn't show that in the UI yet but if we check the Network tab so I'm going to refresh this page we're going to grab the expenses we should see in the response that just automatically it creates that date or it has that date property on it so we could now show this in the table when we're showing all expenses so let's actually do that let's go to the expenses route and we will in the table We'll add a new table head so we'll say this is the date We'll add uh another skeleton for when it's loading for that and then and when they are all loaded we will show the date and this is actually going to show the entire ISO date which we don't really want we don't care about any of these zeros cuz it is just a date it's not a full time stamp so what we can do here is we can just split on the T and only show everything before that that's the simplest way of course you could also uh bring in a date library and do other things if you wanted but this will be sufficient for what we need now cuz then it will just show the date that it was created perfect and just to double will check that this is working I'm going to go random date in 2023 I just want to see that yeah so that all seems to be working and I could even order by the date if I wanted to but I'll leave that as is that all seems to be working pretty well and this is looking pretty okay I'm happy with this happy with this very minimal the profile page doesn't look great the homepage kind of looks okay the navbar could do with a little touchup so I think I'm just going to spend a few minutes just updating the Styles a little bit before we move on to the next feature so in the root file I'm going to update the Navar a little bit cuz I am going to put a title in here too and throw the navigation links over to the right so I'm going to wrap all of that in a div make this flex and justify between also going to move the P2 to the outer one here and now this should allow me to put a header here that has expense tracker or something like that and then these links will all be shoved to the other side that's almost what I want but I do want the max width on the whole thing so we'll say the max width and the margin Auto also goes on the outer div here uh and now it looks like that so that I think is just a little bit better and maybe I'll Center the items as well actually no I'm going to Baseline the items let's see okay yeah so now they all have the Baseline there I think that's good and maybe I should instead of having the home link I'm going to make that be the title up here which I think is more of an expected Behavior anyway we don't have a home link you just click the title it takes you home right okay so we have all of these I can go to profile here and then if I click that takes me back to the homepage okay so that's a little bit better the about page I don't really know what's going to go on here so maybe uh I'll just change the about to have I don't know coming soon or something uh it's pretty lazy but whatever then in the profile page again I'm not really sure what I want to put in the profile page so let's check out the Shaden components cuz there's an avatar that we can use which might be a nice addition so let's bring that in into the front end and then we'll copy and paste the code for this and dump that into the profile page so this is really the code I want the Avatar code I don't think we need hello from profile so I'm going to put the Avatar in there and I think there's a chance that the user has a picture which is going to be a string we can pass in here so maybe I'll do a check for the picture if a picture exists then we'll show the Avatar image uh and I guess we could Al this to data. user. given name and then we'll fold back maybe just on the given name let's see how that goes cuz I don't have a photo so I think this will just fall back to my name I'm going to see if co-pilot gives me anything here and then maybe this can just be the given name and the family name let's see what this is starting to look like okay so there's an avatar with my name maybe I should actually Flex this and Center them yeah that seems okay just for this section yeah it appear like that I think that's okay and then the logout button I think should actually be a button so I'm going to bring in the button component at components UI button and I'm going to wrap this in a button and I'm going to check the docks again cuz I can't remember exactly how you make a button into a link but I think that's a supported thing yeah right here so uh we'll just do Button as child so it will actually just be an href tag but it will appear as a button I think that needs a little little bit of spacing so let's give this uh [Music] my4 okay that's still not amazing but it's fine I think it's a little better than it was so we have the profile page with the logout button create expenses about the homepage with the total spend and I think I'm just going to do the same thing that I did for the profile screen with the login screen just to make that a little bit more consistent so here's that login screen I actually wonder if co-pilot will do anything for me here so I think let's put that in a P tag login or register we want do the same thing as before so I'm going to put in a button that is from at components UI Button as child so that's a button for login one more button for register they want to register a new account and that looks terrible let's Flex Flex call Gap Y 2 items Center yeah okay that's that's better than it was okay so now we can log in or register and everything should still work the same just with a little bit of an updated UI okay so now we're going to do a little bit more advanced stuff we're going to work with the caching from react query and we're going to add in a feature that is kind of simple to do in a client side rendered single page application like this but I found incredibly difficult to do in in anything that uses serers side rendering like a nextjs application so right now if you notice as we navigate around so I'll go create an expenses and I'll just keep navigating back and forth here every single time by default it's going to re request the expenses even though they already appear here because it's not sure if there are updates to these or not so we can modify this a little bit so that it only updates every once in a while like every 5 minutes or so but we can also add in the newly created expense when we create it so that if we go to this page we load in all the expenses we create a new expense we can just push that new one into the list here without having to make a request to the database again this is making the exact same get request to get every single expense every single time so we can make that a little bit more efficient so to do that I'm going to start by going I'm going to close actually all of these and we're going to go to the expenses page where we're making the request to the API right here so this is the function that makes the request here's the use Query hook and the first thing I'm going to do is set a stale time here so this I think defaults to 5 minutes and it means that as we navigate around this won't retry and get the expenses for 5 minutes from the first request so if we look at this in the browser I'm going to refresh and clear the current cash so it made that request to get all the expenses and as I navigate around it's not going to remake that request now a downside of this is that if I go to create a no expense then it won't appear on the other page because the only way that page knew knew about the expenses is that it remade that request so we should have an 11th expense here but it didn't request it made that post request create but didn't then try to get the new expense so if I refresh the page we then get the 11th one so I'm not making unnecessary requests but I'm also not making the necessary one I need to get this new expense in here but when we create the expense after it's finished being created here this response object this contains the new expense that was created because we're having the server route return the new expense in the post request right here it gets the result from the database and actually returns that back down to the client so we're going to have the create expense page update the get all expenses query with the new one when it's created but before we do that we're going to refactor just a little bit because it's going to be easier if all this logic isn't just inside each page and each component so I'm going to take this function out the get all expenses function and I'm going to put that in my API file uh just at the bottom here so we're going to export async function get all expenses and then this get all expenses now exists in the API file so I could just import this function into my expenses page but instead I'm going to go one step further I'm going to take all of this data out of here and I'm going to come back in and I'm going to do the same thing that we did with the user query options so I'm going to export const get all expenses query options create a new query options object that contains all of that data from before so this is the unique tag this is the function it's just going to grab all the EXP expenses and here's that stale time from before so this now just exists in the API file and all I need to do is import this back into my expenses page here and then I can pass that straight into the used query so everything is still going to be the same right now we haven't made any updates to how it's actually making the request or getting the new data but this is in a new file now which will make the next step a little bit easier so if we go to the create expense page this is going to modify the expense it's going to create a new one in the database and then we just want to update the data on this page and the way that react query works is that it is basically an async State manager so it can handle the request States for us it handles the error State and the loading State and all of that but really is just a state manager it's holding the expenses in memory somewhere for us it's managing that state so all we're going to do is update the state for this query option here and then this page will update with any new state that we have so in my create expenses page I'm going to need to access that query options object so I'm going to import that from the API import it up here the get all expenses query options I'm also going to need use Query client from 10stack RE query and then at the top of the page component here we're going to create a reference to the query client using use Query client and then using this object we can update any of the state that react query is keeping track of so we make the post request to create a new expense if there's an error we throw the error otherwise we should be able to get the newly created expense from the response object so we should be able to await res. Json and this should be the new expense object new expense is the object that has all of these properties on it it's actually giving us an array so if we go back to the expenses right here we make the post request we can see this is where we're submitting it to the database we're telling it to return whatever it puts into the database but it's always going to return an array to us in in case we input many values or when we're selecting we might select many values so since we know that this is only set up to post a single expense we can have the database just return a single item by attaching a then and getting the first item out of the array so now in the create expenses page what we're getting back from the API when we create a new expense is the newly created expense object and now all we need to do is put that into the array of expenses that the other page has so we can use the query client here and we're going to say query client set query data for the get all expenses query options using the query key and we get access to the old data and then we can basically return the new expense in the old data but this will only really work if someone has already gone to the all expenses page then gone to create if someone were to just navigate straight to this page expenses wouldn't already exist so we're going to do one extra step here that is to get the exist existing expenses from the query client using query client. insure query data from the get all expenses query options object so this line right here if they have already been loaded if someone's already gone to the all expenses page we just immediately get what's already in the cash but if they hadn't already gone to that page if they'd started on the create page this will go to the server it will get all of the expenses and then we can modify that by putting the new expense into that locally so now this is going to look like this where we're going to return an object here and the thing that the server actually returns to us is this object that contains a key expenses that then has the array of objects in there so it's not just adding it to an array we are manipulating a whole object here so this is going to need to be uh an object that contains basically anything that exists in the existing expenses but we're going to manipulate the expenses array by putting in all the existing expenses with the new expense at the end of it so grab all of the existing expenses hopefully locally if that's already been done or go to the server and get them if it hasn't then update that local cache to include the new expense that was just created from making the post request and then once all of that is done we're going to navigate over to the expenses page so let's take a look at how this works if we go to the expenses I'm going to clear all of this and I'm going to refresh the page so we go to this page and immediately it makes the request to get all the expenses from the server so that's one request to the server to get all the current expenses then we go to the create page I'm going to create a new expense and then submit this it's going to make the post request and that should be the only request made so it made that post request if we look at the head as it's a post request the response is the one that was just created and this has id2 and then we just input that into the current expenses that exist and I put it at the end which was a mistake because the way this is ordered this should have actually been put at the beginning so I just need to go back here and switch that around but we're just updating this internal context really that react query is managing for us so it's really just an async context manager for us that works really well with making API requests but we can use it to manage State for the entire application and then just to verify this works I could go to the create page and again I'll refresh everything so this is the first time coming here if I make uh an expense and then I submit this one it should make the post request but then it should also make a get request since we haven't already got the expenses and we actually get the expense in here twice this is interesting because we waited let's look at the code here we create a new expense here then we get the existing expenses and if this one's already been created then this will actually get the already created expense that's kind of interesting a simple way of solving this that keeps everything working correctly is that we'll make sure that we have all the existing expenses before we post the new expense uh and then we'll put the new expense into that list so now if I go to the create page refresh again so we have nothing we have no data yet uh I'll just make another expense submit that and this time the order works correctly so we actually get the list of expenses and this means that there's just less Network requests and it also allows us to do more customizations locally which is something I love about client side rendered applications so what we're going to do now is kind of an optimistic update where instead of staying on this page until the expense has been completely created what I want to do this this could take a while you could have like images for receipts or something or just a really slow Network request so instead of just hanging about here I want to immediately navigate over to the expenses page and see the new expense appear in this list but in a way that shows that it's still loading it's still being created and if there's an error we can show some sign of an error or if it's successful it will just appear in a successful state so this one's a little bit more complex but not too difficult and I'll try and do this step by step so the first thing is is I'm going to move the navigate right here right before we make the post request so we'll end up on the expenses page and at the same time I'm going to move this code out and put it in the API file to kind of keep that consistent and easier to manipulate so in here I'll make a new export async function create expense and I missed the no expense code so I'm going to put that in here too so if that's all good we can just return the new expense so this function is just responsible for making that post request we're going to need a value in here and this value is going to be whatever we sent to the server so if we go back over to the shared types file this is what the server is expecting we can just export a typescript type here which is going to be z. infer type of that and then I'm going to need to import Zod into this so this is the typescript type that represents what we need to submit to the server I'm realizing now we don't actually ever want the user to submit an ID so we'll omit the ID as well that should have been done before not a huge deal I just add that in now but this means that we can now bring this type in here so I'm going to import type create expense from server SL shared types so this function is going to be expecting the new value which is a create expense type so all of that still works and then then I'll import that into my create expenses file so from API we'll import this so I'm not handling the API directly I'm just calling functions that exist in that API file uh and that's going to look like that so I can just call that function to create the new expense and get the new expense object now I'm going to add a fake timeout here so we'll add uh 3 seconds we'll pretend that this a really slow Network request and this is going to take 3 seconds to even go to the server and now we'll kind of see what's going to happen here so if we create an expense we'll navigate immediately to the expenses page and then in the background we're going to try and create that new expense which might take a little bit of time then once that has been created we update that internal State So currently this will look a tiny bit janky but you'll get a sense of what I want to go for here so if we go to the create page uh just create a new expense hit submit we'll immediately be navigated over to the expenses page where we get to see all the expenses then when the new one's created in 3 seconds it just appears there so that's the kind of behavior the kind of custom behavior that I want that you can Implement in a client side rendered single page application but what I would really love is that we can navigate over to the new page immediately but see some sort of loading State here to indicate that it's currently loading and then when it's successful we see it here and if there's an error we'll show an error state so there's a lot of customizations like this that we can do to achieve this though we're going to need to store the state of the new expense with react query somehow so right after we navigate we're going to indicate some sort of load State and then when the new expense has been created we'll have that success State and we could even throw this into a TR catch and handle some error State as well here but this is all going to be handled on the other page so the way we're going to do this is with react query just as a state manager so right here I'm going to say query client set query data will give it a key so I'm just going to call this uh loading create expense and the data I want to send it is going to be the data that is being submitted into the form so that's going to include the title the amount and the date so I'll just call this expense singular so I'm going to send this data over to the other page or to any page that wants it so that it can present that in some sort of loading State and then since this is my loading State here once we're done I'm actually just going to put this in a finally block so whether this was successful or unsuccessful I am going to just set this to be an empty object so again this just represents the current loading thing so if we are currently submitting it's going to have that state and if we are finished submitting whether it was successful or not we're just going to remove that because the loading State no longer exists there uh this whole bit of code here is actually part of the success state so only on success do we update the array that people see on the page and then we'll handle the error somehow in a little bit so now using this key I can go to another page so I'm going to go to the expenses page and I am going to use Query again so I'm going to say const data equals use Query and this time it's going to be for this key which I'm have to put in an object query key and this data is going to be the loading create expense object so this will be whatever data was passed in from the other page and before I do anything crazy I think I'm just going to dump this out to the screen so we can say uh Json stringify this object just so we can see it on the page for now and then we'll make it look a little bit nicer so uh just to go through that again when we create we will set this value just on some Global context you'll get the value that was in the form while it's loading and as soon as it's done loading we'll remove that value from the context then on this page we're just going to grab that value and show it if it exists so we'll have some sort of loading indicator here so if we go and create a new thing I'm going to input some data give it a value submit that and then on this page we have that data that is currently being submitted then when it's done we just see that no expense appear here so really what I want to do is get that data into the form here or rather the table right now this doesn't have a type it has an unknown type because usequery has no idea what's coming from here so to fix that I'm going to take this out again this key and I'm going to create an object in my API file just to keep all of this code in the same place so export const loading create expense query option so we're going to create a query option object here where the query key is going to be that string that I was using before the query function can return an object a stale time can be infinity and then we can just add a type to this so this is going to have the type it's going to be an object that contains an expense property that is either going to be undefined or that create expense type create expense type that we are getting from the shared types because that's what is being input into the form it's that data of a date title and amount and I don't want to make this undefined I want to make it optional which is undefined uh so now with that there it knows what type is going to come from this query option so in my create expense I'll use that instead this is for the query key of this option I should import that from from my API file and I should probably refact that into a different file at some point and then right here I'm going to make sure I do the same thing keeping it all consistent that's this one up here so I put that in there uh so again we're just setting that value then resetting the value then over here in expenses I'm going to use that loading create expenses query option we can bring that in from the API file and now this should know that the loading create expense is actually an expense object or it's undefined so this is good I'll remove this now and what I want to do is have my own table row that appears in the table if we're loading this data so I think I'll put this at the very top and I'll just say that if the expense is being created if there is an expense loading We'll add a table cell there that doesn't need a key cuz it's just a single expense it doesn't have an ID anyway so there won't won't be an ID here we will have a title an amount and a date and then maybe for the ID because it doesn't have an ID and I want to demonstrate that this is actually loading I could just put a skeleton in the ID field so now if we're loading it we'll just present a different cell that demonstrates that it's currently loading and there'll just be a skeleton since it doesn't currently have an ID yet so I'll go to create create a new expense here if I hit submit we see the details that we are able to see and the details that are hidden because they're part of the database won't come until it's done loading so I'll go to create again you see that's the loading State maybe I'd want to do something else to reflect that it's loading more maybe I I can actually skeleton the whole thing and have it uh pulse in and out but that is definitely a cool feature that you can Implement in something like this and I really like that because when you create the new expense you see the loading State on the page where the thing is going to appear this is where it's going to exist eventually and then the moment it's actually ready to be presented fully it exists in its natural place and this won't work for every single feature but it is nice to know that you can achieve something like this but we're not yet handling the error state so we're going to add another component in here we're going to scroll down to sonor and bring this in basically this component shows these little things in the corner here so that if there is a success state or an error State uh we can use these things and we'll use them when we start deleting expenses too so I'm going to grab the code for this actually I guess I need to install it first so we will add that into the front end code then we should be able to copy the example actually we need to put this in the root of the application first so we're going to bring in the toaster component which we're going to put into the root file because this is the layout of our entire application so we're going to dump that into the very bottom not the import the actual toaster component and then we'll import the toaster component there so this just allows any page to see the notification no matter where it's triggered from so that's all we need in there and then in other parts of our application we can trigger that just by calling this toast function so I'm going to copy this code and the import from the top and over in my create expense this is where I'm going to trigger it for now I am going to import the toast at the very bottom here we are going to handle the error and the success state so on error we are going to trigger a toast and I don't think I need any actions here I'm going to delete that so we're going to have expense created this is going to be the successful toast and we'll just have like an error toast if something went wrong there so the error can say something like failed to create new expense and I guess we could have a a link that sends them back to the create page if they want and when it's created we can say success sucessfully created the new expense and even put in the ID there since we have the expense that has come back from the post request so now just with this little bit of code we get a little more UI there so I'm going to go create an expense a new one there submit we have the loading State and as soon as it actually comes back from the server we see that there but we also see this little toast pop up here and if we were to trigger an error so if I go into API instead of the create being successful I will throw new error server error and now let's try creating that and we pretend that the server malfunctioned and we should get a little error popup here and no new expense up there so that's all working perfectly so I'll delete the error I'll leave the timeout for now just so we can still simulate slow Network requests and actually I'm going to make this a little bit slower to demonstrate the power of this cuz this is especially handy if you're uploading like large files to an S3 bucket or something which can always take a while no matter how fast your internet is uh is that with this type of application you can input something here hit submit it shows the loading state but you can navigate around and then the moment that's actually successful after 5 Seconds we get the little toast there and we can see this is updated here without having to make unnecessary Network requests uh we can also go to this page navigate back and forth we can create a new expense but when we get back here it's still in the loading State and then updates automatically so all of that just works seamlessly and the user can still navigate the app even if their Network request is slow or you are trying to do something like upload a large file or a lot of data or something so the next thing I want to do is actually be able to delete these expenses have a little delete button next to each of them and we'll use a couple of the techniques we just used there as well so on the expenses page we're going to have another column here for delete and each expense is going to have a table cell with a button for deleting and we're not going to put the word delete in there also make sure for some reason my vs code keeps bringing that in from react day picker make sure it's coming from at components UI button uh but if we go back over to the docs for button I believe there's some documentation about using it with icons uh let's look yeah all right there's the icon one so if we look at the code here it's bringing in a Chevron right icon actually I'm just going to copy this whole thing uh from Lucid react so what we're going to do is instead of bringing the Chevron icon in we can look for a trash icon that's probably going to be good and then using the provided code this will hopefully look pretty nice so again not Chevron right we can use the trash icon instead and we should get yeah okay these look pretty good all right so each of these have a trash icon next to them uh and I'm just going to make sure that the skeleton has an extra cell and my loading state is also going to have a skeleton and so there CU you don't want to be able to delete an expense if it hasn't even been created in the database yet so now I have that delete button on click of this button we need to trigger a delete for the current expense and the delete is going to need all of those things that happen in a network request we're going to need to know about any error States when deleting loading States and if it was actually successful to keep this nice and clean we're going to use 10 stack query again we're going to use a mutation to actually do this but because the mutations apply to a single expense and not to all of them we should be able to delete a single one at a time it's going to be a little bit simpler if we put some of this in its own component and at a minimum we can just take the button and put that in its own component and I'm going to keep this in the same file for now but you can refactor this into a different file if you want but I'm going to call this uh expense delete button and then we are going to return that button out of here so this is just going to render expense delete button that's perfect like that so it still just renders like that but we're also going to need to pass in the ID of the expense so that it knows which expense to delete so we'll pass in ID equals expense. ID and then we need to use mutation here so you can go to the tan stack query docs and check out everything you need to know about use mutation but I'm going to scroll down to the side effect section and I'm going to copy all of this code to put into to this component here we're going to need to import use mutation from 10 stack react query and then the mutation function itself just needs to be a network request to delete an expense in this case so I'm going to create that function in my API file again export async function delete expense we're just going to need the ID of the expense here then we will await API do expenses doid and that's just how it appears on the backend API to make sure it's a number dot delete and then the Pam that we give it is going to be the ID and it's going to complain because it's expecting ID to be a string over HTTP it has to be a string but we're trying to pass it a number so we can just to string here and then we'll just do the standard if res is not okay throw an error so we now have that delete expense function which we're passing in here we just need to make sure we import that in the expenses page so this use mutation is going to use the delete expense function I don't need un mutate I just want to be able to handle an error case and a success case so I'll delete that one as well and I don't really need any of this data here either because if there's an error deleting then I'm just going to do what we did before and show a toast that that says error description fail to delete expense and then on success let's do the same thing toast expense deleted successfully deleted expense okay perfect so these are the things that I want to notify the user on error and on success I'm going to make sure that toast was imported correctly yes it was okay so now I just need to trigger this mutation so we'll say uh const mutation equals and then when the button is clicked so on click we will give it a function that triggers mutation. mutate so this is going to invoke the delete expense function and then we pass in any properties it needs so this needs the ID of the current expense and that's pretty much it there's just one more thing when the mutation is currently loading I want to make sure that this button is disabled and it's actually is pending so if it's currently in the state of deleting the expense we make sure we disable the button I think we'll just display something different so if it's pending we can just show Three Dots and if it's not pending then we'll show the trash icon so this should be good onclick Delete the expense on error show a toast on success show a toast don't allow the button to be clicked if it's currently pending and just show the trash icon if it isn't and one more thing just so we can see these loading States I'm going to add a 3C delay on the network request so now if we go back we should see all of that that looks good uh if I refresh the page you can see the skeleton appears for just a second there but that was all the columns and now if I go to delete let's delete 21 I click this in that loading State I can't click it again and this should be successful expense deleted that was successfully deleted 21 but this does still appear there and I'm allowed to click this button again which should actually cause an error because when it goes to the server it should yeah failed to delete expense 21 because it shouldn't even exist so if I refresh the page it shouldn't appear there but the reason it still appears is because it's not uh changing this state the state of all the expenses after delete so that's just one more thing that I need to do down here in my expenses page so on success I also just need to remove that expense from the array of expenses so this is going to be really similar to in the create expense where we're setting the query data to be the existing expenses plus the new one we're going to do a variation of this where we actually filter out the old one so first I'm going to need the query client so const query client equals use Query client and I need to import that from react query so right here and then at the very bottom in my delete button I'm going to set that to be I guess I need the existing expenses so this one I'm actually going to use the function for because this will give me the existing expenses if I pass it a function instead then I'll have this function return the the object just need an extra bracket there so we'll return the existing expenses but we're going to need to filter this so existing expenses do expenses. filter the expenses that looks good I'm going to change this question mark to an exclamation mark because there's no way that this will be presented unless we already have the expenses so I'm just going to put that in there like that uh so we're going to filter uh if the expense has the ID of the current one that we just deleted it was just successfully deleted then return false don't include that in the array otherwise just return true for the other ones and we'll still see them all so now with the delay we should see a little loading State and then it should just get removed from the list so I'll try this with 18 I hit delete we get the little three dashes there and then it should just get removed with that little successful toast at the bottom and this should work no matter how many we do it with so I'm just going to delete the top four and these can all be deleted and as they start getting deleted we get these little notifications at the bottom so that's just a nice indicator it was deleted it gets deleted there um and we could even so delete these and then just navigate around to different pages as well and we'll see that they were successfully deleted there and we go back it doesn't make a new request to get all expenses that state was just managed internally by the application so this does lead to a very customizable user experience that you can make as nice or as not nice as you want but you do have the control to do whatever you want here and there's a lot more you can do with this both feat feature wise and Tech wise both react query and tan stack router both support using suspense so instead of using query like this you could change it to use suspense query and then use suspense and error boundaries in your front end if you want you can separate out the pages to be lazy loaded pages so that you only download the JavaScript for each page as you need it you can separate out individual components so if you have a large component like the date picker for the calendar you could have that be separated out and download that separately and while that's downloading you could use suspense to show a loading state so there's a lot of things you can do here a lot more to be discovered but this is definitely a good foundation to get started with and if you made it all the way to the end of the video thank you for watching the entire thing if you have any questions feel free to leave a comment and check the description because if there's any updates or anything else I need to communicate after this video goes out I'll just leave it in the description area of this video
Info
Channel: Sam Meech-Ward
Views: 44,161
Rating: undefined out of 5
Keywords: web development, software engineering, javascript, full stack, react tutorial, react tutorial for beginners, bun tutorial, hono tutorial, full stack web development course, full stack project, become a software developer, backend web development, react, next.js tutorial
Id: jXyTIQOfTTk
Channel Id: undefined
Length: 218min 12sec (13092 seconds)
Published: Tue Apr 30 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.