So now that we have our first table inside of our schema, we can actually go back inside of our Hono backend API and we can create an endpoint for that. So just as we had an example with books and authors, we're gonna have a separate file holding all the routes for every individual entity in our schema. So right now we have accounts. So let's go ahead and do the following I'm gonna go inside of our API route.ts right here and I'm gonna go ahead and inside of here I'm gonna create accounts.ts just like that and then in here let's go ahead and import Hono from Hono like that and let's write const app new Hono like this and then we can write app.get slash like this let's get the context here and for now let's just return Jason accounts and an empty array here. And make sure to return this, Just like that.
And let's also just export the default app. Like that. Now let's go back inside of the route here individually and let's add app.route. Slash accounts. And in here, let's import accounts from dot slash accounts.
So it's a default export. So we have to call it like this and we just add accounts here like this. And I believe that we can try it out now. So let me just see if I have my localhost running or not. So bun run dev, like this.
And I'm gonna go ahead and I'm gonna go inside of localhost 3000 slash API slash accounts and there we go so localhost 3000 slash API slash accounts is working perfectly fine we get an empty array which is exactly what we have defined right here. So what I want to do now is I want to enable RPC on my project here. So inside of the main route.ts here, what we need is we need to export a type called app type. And it needs to be a type of app, right? But right now, this is not exactly going to work.
So as you can see in here, my app type is some weird blank schema and all information it has is the slash API. So what are the changes we need to do? So first of all, we have to change this to be a constant called routes. And then we're going to chain every individual route in here. So accounts, accounts, and then routes are going to be right here, type of routes.
So we are still gonna use the app for the handlers, but for generating our RPC type, we need this. All right, so this is an improvement because now it's not blank, right? But it's still an empty object. So something is still wrong here. So what we have to do is we have to go inside of accounts now and we have to modify this as well.
So we are no longer gonna use app separately here instead we're going to immediately chain it like this so immediately after new Hono we chain dot get like that so when you hover over this there we go you see the types we have a slash endpoint with get input doesn't exist and output is just a never an empty array because that's what we defined here And now when I hover over app type that's exactly what we get. We know exactly the type of slash API slash accounts. So just in case you are confused about how I know about this chaining practices and how I knew to modify that in order to achieve these proper types here is because of the HONO documentation right here. So if you go inside of guides, RPC and scroll all the way to the bottom, you have using RPC with larger applications and they explain that you need to do that. So you need to chain directly to new HONO in this separate route managers.
And you have to do the same thing in your main index or in our case route.ts. So this is the routes constant which simply chains all the routes and then this is what we need. Perfect! So now if let's see if this is still working so if I refresh this still works perfectly So that's exactly what we need. So now let's go ahead and improve the types.
So what I want to do is I actually want to use Drizzle and fetch the data here. So how about we do this? Let's go ahead and let's import the database from at slash database Drizzle and inside of here I'm going to get the data by using await database dot select. And let's choose ID, which comes from accounts, which we get from database schema. So accounts.id, and we need name, accounts.name.
So those are the only two fields I want, right? So inside of my schema, I have id, plate id, name and user id, but I only wanna return these two to the front end. And since I'm using a wait, I also need to turn this controller into an asynchronous method. So let's do that from accounts. And from now, let's just do it like that.
So I want to return everything and instead of accounts let's return back data like that. So first of all let's see what's changed. So if I refresh localhost nothing has changed except that now it says data instead of accounts but it's still an empty array. But If I hover over app right here, you can see that now we have the output to be data, id, string and name string. So exactly what I selected here using Drizzle.
And if I go over here and hover over the app type, I also have those instructions here. So that's what we achieved with this app type, end-to-end type safety. And that is going to be extremely useful for us when we combine this with React query. So we are going to know exactly what our mutation and our queries are going to return to us. Perfect.
So let's go ahead and just wrap this up. So I want to properly do this. So let's go ahead and try and create a front-end hook which will handle this slash accounts get route and store this data inside. So for that we're going to need to set up React query. So just confirm that this endpoint works for you and confirm that you have this exact types as I do.
So your app type should hold slash API accounts, the get option should have an empty input and the output should be an array of id and name and that's going to be in the data object. Great! So let's go ahead and let's see how to install ReactQuery with Next.js. So right here first let's go ahead in the documentation and choose our way of installing. So I'm using bun, so I'm choosing the last option here.
So let me go ahead inside of the terminal here and let's add nstack react query. Great. Now that we have that, we have to create a provider which is going to work with Next.js. So for that, you have to find a sequence here called Advanced Server Rendering. Right here.
So you have two options. You can simply Google Advanced Server Rendering, search it inside of the 10-stack query, or use the link in the description, or just visit my source code and you can see exactly what we have to copy from here. So we need this initial setup. We need to create a query client provider. So it's this code right here.
So I'm going to go ahead and copy this entire code and I'm going to go ahead and I'm going to call this query provider. So what I'm going to do actually is I'm gonna create a separate folder for this in the root of my project called providers. And inside of here, I'm gonna create query-provider.dsx. And I'm gonna paste the entire thing inside. So we need use client, right?
That is true. We don't need to use state here. You can leave the comments if you want to, if they help you understand what's going on here. So I'm not going to modify anything here. And I also wanna write types for the children here.
So I'm gonna write type props here, children react.reactNode. And I'm just gonna assign the props here. And I don't want this to be a default export, so I'm just gonna remove the default. So it just says export function, and instead of providers, I'm gonna call it query provider, like that. And I will add some semicolons here.
There we go. We now have our query provider method here. If you're unsure about the changes you can always visit my source code. And now what we have to do is we have to go inside of our app folder and inside of our layout right here. And I want to go ahead and wrap all of my children here inside of query provider.
Make sure you import this query provider from our add slash providers query provider. So the thing that we just created here and removed the default export and we renamed this and we added the props here. So let's immediately discuss about how come this is marked as use client. Why am I wrapping my entire project in that? Won't that make all of my children use client as well?
Because we had a lesson about that. Well no, because if you pass in components through children in that case not all children are going to become client components. So if you pass them as children, you can safely use server components inside. So just to clear that up, no. If you wrap your children inside of a client provider, that will not automatically transform all children to stop being server components.
Your children can safely be server components inside. You can read more about that in Next.js documentation. Perfect, so now that we have that, I wanna go ahead and I wanna create my co-located folder where I'm going to keep all common account components, account queries, account hooks. So I want to keep all of that together and I'm going to call that folder Features. And inside I'm going to create the accounts folder and let's create API folder inside so features accounts API and let's create a very simple one use get accounts.ts so this is going to be the first hook that we are going to create and this hook is going to communicate with this accounts.ts get endpoint right here.
So let's go ahead and let's create that. So first of all let's import use query from 10 stack react query. And then what we have to do, which is one thing that I forgot to do actually, is we need to create an RPC client, which will use this app type. So it's very simple to do that. So let's just go ahead and do this.
Let's just save this file as it is. And let's go inside of our lib and let's create Hono.ts like that. And then let's import hc from hono slash client and let's import app type from at slash API slash app API our cache all route route so this file right here this is what we are looking for. And then what we have to do is we have to export const client to be HC and use the app type. And inside of here, we need a process.environment.next public app URL and add the exclamation point at the end.
And now what we have to do is we have to add this inside of our environment file right here. So copy it from here, go inside of dot environment and I'm gonna add it here. So let me just copy it. Next public app URL. And this is very simply going to be HTTP localhost 3000 or whatever port you are running your application on.
And later in production, we are going to change this. So it's important that that's how it works here. You can write it here, of course, but you're gonna have to change it for production. Great, so now that we have the client, we can head back safely inside of our features, accounts, API, use get accounts right here. And let's import the client from here.
There we go. And now what we can do is client.API.accounts.get. And you can see how we have amazing type safety here. So we know exactly what we're gonna do. So let's export const use get accounts.
Let's define our query right here. So our query key is gonna be accounts multiple. And our query function is going to be an asynchronous method here, which gets the response. And it's going to be await client.API.accounts.get and execute this method. And now you can see that our response is type safe and we know exactly what it's going to return.
So first of all, we don't have to worry if our route is correct. So usually we would do something like fetch slash API slash accounts, right? First of all, we don't have to worry about any typos in this because we're using type safe RPC. Second, what we would have to do is it would probably have to do something like account type and then that will be a type of fetch, right? Slash API accounts.
So something like this. So you would have to define that as well. But now we don't have to do that at all because it automatically infers that because of the way we defined our app type right here which is extremely type safe and everything is chained and works just fine and HONA knows exactly what data it expects back. Perfect! So now that we get the response we still have to take care of response okay.
So if not response okay we are going to break this query by throwing a new error here. My apologies, so just error. And we're going to say failed to fetch accounts like this. And then let's go ahead and destructure the data from await response.json right here. And let's go ahead and return data like that.
And in the end just return the query. There we go! So now we have our reusable hook here. So what we can do here now is the following. Well, first of all, you can hover over use get accounts and in here you can see exactly what you're going to get in the use query result.
You're going to get an array of objects with id and name. So how did I know that I can destructure data here? Well, first of all, because of type safety. If I try to destructure something else it's not going to work because we know that we return the data object back. So if I change this to be x data in that case in here we can destructure back x right so that's the type safety I keep talking about right that's why I'm so excited about this stack because I feel very confident in our code.
Here's one caveat you might have thought of. So you might be thinking, okay, so what I can do is I can just wrap this into a try and catch and then I don't even have to take care of this. Remember, this is not Axios. So Axios works in a way that if it encounters an error it automatically goes into the catch option but this is not Axios. So in here we have to separately take care of the error by checking if the response is not okay.
Great! So now we have this very cool hook and we can already try it out for example in the dashboard in page I'm gonna go ahead and remove this and I'm gonna mark this as use client here and I'm gonna go ahead and get const let's call this accounts query, use get accounts, like that. And then in here you can already see how you can work with this. So for example, you could do accounts query dot data dot map. You would get the individual account And if you were to return a div here, you can safely use type safety.
So account, and you can see how it autocompletes.id and inside account.autocompletes.name. So our query is working just fine. Or if you want to, you can use data and then in here you can write accounts, for example, and then you would skip over this and write accounts. And furthermore, you also have is loading. So you can also use that if is loading and then in here you can return a loading element, right?
So that's how we are gonna use these hooks. And what's cool about it is that we're going to co-locate a bunch of useful API, both queries and mutations inside of here. So each of our entities, which exist in here in schema, so alongside accounts, we're gonna have transactions, for example, and then after we create all the routes for that, using HONO, We're gonna go inside of features and we can just copy and paste existing accounts and just slightly modify it. So we would change this later to be transactions, right? And the query key will be transactions.
So a lot of reusable code and consistent code in our code base. That's why I'm so excited about this. But let's just wrap it up by doing one more thing which is error handling. So you can if you want to leave this or you can just you know I'm actually going to reset the home to be as it was. So I'm gonna return the homepage.
I just wanted to demonstrate how we're gonna use this hook now. So all that's important is that you have this hook completed, right? And that it has all the type safety that I keep showing you. But here's a thing. So for example, if I go inside of app API and if I go inside of accounts, first of all, this is not complete.
So what I wanna do is I wanna ensure that only authenticated users can see this. So I'm adding the clerk middleware from clerk next to JS server. So we learned that in the section about Hono that we can chain as many middlewares as we want inside. But I believe I shouldn't be using Clerk middleware like this. My apologies.
So Clerk middleware, not from here, but instead from at Hono slash Clerk out from here. Right. So just ensure you have this package installed. I believe we did that when we explored Hono in one of the previous chapters, right? So in the dependencies I have Hono-clerk-out.
Great, so clerk middleware, make sure you execute it right here and now let's go ahead and let's get the authenticated user. So const out will be get out and pass in the context. And the first thing we also need to get out, my apologies. So get out, there we go. So what I want to check is if there is no auth?userid, I want to throw back an error.
So we can do that in many ways. First, let's try this. Let's return c.json, error, unauthorized. And let's write back 401. So yeah, the second argument in here can be the status.
And let me just indent this like that. So this seems fine. And if we try it out, so if I go here, all right, let me just refresh my page to see that it's working. So if I go to slash API slash accounts, now I should just get an empty array because I'm logged in. But if I go ahead and if I log out, And if I attempt to go to slash API slash accounts, I get back an error, unauthorized, right?
That seems fine. It seems like this is working as intended and it's technically is. But let's take a look at what's happening now. If I revisit my route.ts And if I hover over the app type, all of a sudden you can see that my output has changed. So now my output can either be the error or it can be the data here.
And that will cause problems inside of our hook here called use get accounts right here. So because of this right now let's just see. So as you can see here I can still extract the data right so perhaps it actually is working fine but here's another way you can handle errors if you want to. So if you want to, you can do this. Instead of returning errors like this, what you can do is you can throw an HTTP exception.
So I'm gonna go ahead and import this. HTTP exception from at hono slash HTTP exception like that. And then in here, you can throw that instead. So HTTP, you would throw new HTTP exception 401, like this, response c.json error, unauthorized. And 401.
Let me just see if I did this correctly. My apologies. Like this. There we go. So what's changed now?
Well, what's changed is that now you no longer have that written in here. So now the only typed response can be data because otherwise we throw the exception, right? We throw the error, we break this method. And in order for this to work as it should. So now if I refresh, it still works exactly as written, but we do need to do one more thing.
And that is to go back inside of our route.ts right here. And just below this, let's write app on error here, error context. And inside of here, let's check if error is instance of HTTP exception which you can import from Hono HTTP exception here. And let me just see if I wrote this correctly. So if error instance of should be lowercase like that instance of in that case, let's return error.getResponse.
So we can do that because inside of the accounts here, we assign the response. So we are simply going to return this, but in case this is an unhandled error, What we can do is we can standardize our JSON output. So it never shows anything else in the API. So I'm gonna write error internal server error, or maybe just internal error, server is redundant. There we go.
So now nothing should change, but our type safety should be intact. So our app type here, C output is now intact. We know exactly what data will be returned here, right? So if you want to, you can explore, you know, with the alternative where you return c.json here error unauthorized and a 401 right so I will comment this out and this still works fine but now you can see that you have two different types of output here, one being an error and one being the success message. And that can be fine, but it causes problems for RPCs because let's go ahead and do this again.
So if I were to import use get accounts, and if I were to try and get my accounts query here, use get accounts like this, now what's possible is that I get an error here as well. So I believe that if I try and go over accountsquery.data.map, well, it looks like it's working now, but I remember that I had a couple of issues here before because of that error. So you can decide for yourself, you can try it out. If you want to, you can use this method. My apologies, where is my accounts file?
Right here. So if you want to, you can either use this. You can return C.json error unauthorized or you can use the throw new exception, right? So I'm gonna go ahead and go with throw new exception simply because I use this in my original source code, right? But if you want, you can play around, you know, I invite you to explore HONO yourself, but I found to have less type safety errors when using this right here.
Basically we're gonna see whether it causes problems or not later when we actually have a chance to use this query. So for now let's just leave it as it is. Let's turn the home page back to home page. I'm going to remove use client here, we don't need it. And what's important is let's recap everything we've changed here.
So we added the query provider, We added the HonoLib which uses the app type and the new Next Public App URL. We added the UseGetAccounts which handles the error for us here. And we've also added the accounts and we've also added the onErrorHandler here and we added this app type here as well as the separate accounts route here which very simply throws all the accounts here. One thing that I want to extend this with is the where so in here I want to add equals from drizzle ORM. So make sure you add this, so equals.
And we are very simply going to check whether accounts.userid equals the auth.userid, which is currently logged in. So I only wanna return accounts from this user. So this is very important. And if I refresh, I should still get unauthorized here. So I'm gonna go ahead and log in.
There we go, so I'm back inside. So if I go to slash API slash accounts and there we go. I now get empty data because I don't have any accounts to my name. Perfect. So what we're gonna do in the next part of the tutorial is we're going to add a post route inside of this accounts right here and I want to keep playing around with throwing errors because it looks like there are alternative ways of doing it, right?
And I just want to give you the proper information about them because when I was first developing this project, the only way I could ensure type safety was by using the HTTP exception. But it looks like HONO has a new version where it handles errors in a bit of a different way. But I still think this is gonna work just fine. But as I said, we're gonna see that fully in action once we actually get to use these hooks right here. Great!
Amazing job and see you in the next chapter. So just a quick explanation about our errors which I feel like might be a confusing segment before I finish this chapter. So I just went into my original project and I took a look at why exactly I used this, the HTTP exception. And turns out it was because I had an older version of HONO. So HONO has since been updated and it seems to handle these errors perfectly fine.
So here's what I propose we do for this tutorial. Let's try and use this method instead because I honestly prefer it this way. So go inside of your API route accounts and remove the throw new HTTP exception entirely and just leave the simple return of a JSON object but it's crucial that you add a 401 at the end here. So that's all I want to do and I want to remove this as well And let's also go inside of the route.cs right here and let's go ahead and remove this on error also. I believe we are not going to need that.
We can always bring it back if it turns out that I'm wrong But I think with the new version of Hono, and let me just ensure and show you which version I'm talking about here. So I'm talking about Hono 4.3.2 in my case right here. So let's go ahead and do the following. I want to show you how this works. So inside of here, inside of features API use get account, in my old project if I used this type of JSON error, I would get an error here.
It would tell me that data doesn't exist on this response because there's no data in this response. And I believe if I remove the status 401 and save this I get that exact error here. Data does not exist on type error. So that's the error I was getting. But in the older version just adding the status did not fix that.
But now it seems to fix the types entirely and that's great. So Hono was improved by the time I finished my course, well not course but my initial code to the time where I started recording this. So this is great. We can actually just use this. You can just set the status 401 for the error and that will automatically in this response ensure that there is a separate client response for the 401 status and there is an entirely different client response for all the other statuses here which has the proper data.
And also what brings me confidence here is that if we don't handle the error we also get this TypeScript error. So this handling of error gives me confidence with this type safety to tell me yes you handle this error properly. All you have to do is take a look at the if response is not okay and just throw the error and react query is not going to attempt to destructure this if there's no data in here at all. So I believe that we can just do this safely. So let's go ahead and do it like that.
If we encounter any problems we can easily bring back our HTTP exception. Great, great job.