So now that we've wrapped up authentication in our project, it's time for us to create our first API route and to also integrate HONO inside of our Next.js project. So first of all, how do you build API routes in Next.js without Hono? So we learned that page.tsx is a reserved keyword for files when representing client routes. Whatever folder page.tsx is in is going to become part of the URL. Well, same is true for writing API routes.
So here is the structure that I prefer. I always create an API folder and then inside I create an API route. For example, test. So this will be the equivalent of localhost 3000 slash API slash test. But instead of writing page.tsx, we write route.ts like this.
And inside of here, using route handlers, we define which method is this for. For example export constant get this will represent the get method So I can go ahead and write return next response which I can import from next slash server and my apologies dot JSON and I can write hello world just like this. So now if I go ahead and write slash API slash test there we go. We get our JSON object back. But if I wanted to have some parameters here What I would have to do is I would have to create an additional folder, for example, test ID and then I would need a route.ts inside and then I would have to access this again.
So let me copy this and paste it here. In order to access this test ID we would have to destructure first the request and then we would get the params. So let me just align this like that so it's easier to look at. First of all, the request would be a type of next request from the next server. And in here we would destructure the params and that would be a type of params test ID string.
So why test ID here? Well, because that's what we gave this folder the name. So then I can keep the hello world but I can extend this by also giving it a testID to be params testID. So if I go to slash API slash test and then slash one two three I should get that in my json object so api slash test 123 and there we go hello world test 123 and if you wanted this to be a post request you will simply change it to post or patch or put So if I change this and refresh there we go I get 405 meaning that we don't have a GET request on this route and if I bring it back it's here. So this is fine we now know how to write API routes using your regular Next.js But what I want to do is I want to use Hono.
So Hono is as it says here a web application framework. It is fast, lightweight and built on the web standards. And what's the coolest part about it is that it has support for any JavaScript runtime. So in here they give examples for Cloudflare, Fastly, Deno, Bun, AWS or Node.js. So let's go ahead and see what this is all about.
So as you can see in here, this has more of a structure similar to Express or FastAPI, something like that, if you worked in your separate Node.js environments. And I honestly prefer this for building API routes over folder and file based structure. So folder based structure for me is fine for front-end routes. So this is okay to me. But API routes, I'd rather have something like this.
So let's go ahead and see how we can add this. So in this quick start they are giving you examples if you're building from scratch right? But we are not building from scratch we already have our project. So instead what we're gonna do is we're simply going to install Hono inside of our project. So I'm gonna shut down the app and I'm gonna write ban add Hono as simple as this.
And now I'm gonna try and find exactly where in the documentation they explained to us how we can add this to a Next.js project. So let's see in here in getting started we have options for Vercel and there we go. So it's right here. If you use app router, edit app API and then create a catch all route and inside a route.ts. So basically what we have to do is we have to create an endpoint that from now on is going to go through HONO instead of going through regular route handlers.
So we're gonna reserve the API folder for that. I don't know if I made that clear, but when making routes, API routes, you don't need to use the API folder so this is just my preference and obviously a preference of many since they recommend having this folder as well. So we are going to reserve this folder for Hono. So I'm going to go ahead and remove the test folder, we no longer need that. And instead I'm going to go ahead and create a catch-all route like this.
So just spread the route inside and then I'm gonna create a route.ts file. So let's go ahead and do this exactly. I'm going to copy this snippet here. It's very short so even if you don't copy it you can pause the screen and write it yourself. So there we go.
Let's take a look at what was just written here. So first of all we add the imports for HONO. Then we add an import to handle our router from Hono slash Vercel. And what's cool about this is that in here, you can add any adapter you want for, for example, AWS Lambda, or for example, BUN, if you're just running on BUN, or Cloudflare Pages, Workers, so a bunch of different things here. But since we are working with Next.js, let's keep it to Vercel.
Then we can also define the runtime to be Edge. If you want to, you can turn it off or you can also write node.js. So for now, I'm going to keep it on the edge, but later we're actually going to have to turn off the edge because of the Plaid integration that we will do. So Plaid will be used to connect to our bank accounts and Plaid uses Axios on the backend which unfortunately doesn't work well with the Edge. So later we're gonna have to remove this but for now let's just keep it so we work with this example.
So in here we initialize a new HONO app and we immediately chain the base path to be slash API. So that works for us because we are in the API folder so that is correct. And then we initialize a slash hello route and we very simply return so what is a C they write C everywhere you might also see CTX somewhere like this that basically means context that's how you can read that So let's go ahead and try this out. And so in here it's pretty clear what we do. We define a get route to go to slash hello and what we do is we return a JSON object with a message hello next JS.
Below that we enable the HONO to work on GET routes and POST routes. So I don't know if you kind of noticed this already but this GET and POST are actually the route handlers that we just built a moment ago but instead of doing export const get and then inside of here we return next response dot json instead of that we tell them no from now on you're just going to handle the app and app is hono so that's how hono works We kind of overwrite existing Next.js route handlers so we don't have to do that file-based routing. So let's try it out. If I did everything correctly now, if I refresh this I should get a 404 but and I think I need to have my app running because I shut it down. So make sure you do this as well.
So now it should be 404 here, if I'm not mistaken. There we go. And then let's go ahead and let's attempt to go to slash hello my apologies api hello and there we go we have a message saying hello next js here's a quick tip for you so let's try something else I don't know if I explained this enough but I'm logged in currently. If I log out and if I attempt to go to slash API hello so this will still work. Great!
I just wanted to confirm that in case you were having any issues. Perfect! So we just confirmed that our integration of Hono is working. So now if you want to you can explore Hono yourself Or you can also take a look at the workshop on my website. So in here we have a workshop where we actually build a very small Twitter clone with Hono.
And we actually use NextOut, Drizzle and Hono all in one combination. So it's really, really cool. Great. So now let's actually go ahead and let's try and do something else. So for example, if I go to, let's go ahead and add an app.get here.
Let's go ahead and chain another route.get slash hello slash test for example. Like this. In order to get the test what we would have to do is get const test and then we would write c.request.param and you can see how we have an autocomplete for test so that's another thing that I absolutely love about Hono is that it has end-to-end type safety. So let's go ahead and return c.json. Let's go ahead and give it a message of hello world and let's pass in the test to be, well, our test from params.
So if I go ahead and go to slash API hello slash 123 there we go! We just had the exact same thing. But you can see that this is even safer because if I try to write test 123 here you can see that in my editor I already have an error here so I don't have to wait for this to be a bug for me and then I go and debug. No, I already see that this param does not exist here so that's why I prefer this way of building API routes. But that is not all.
HONO is extremely powerful and what we are going to do with it is we are going to build, let me just find it here, in the guides they have RPC. So RPC is a feature that allows sharing of the API specifications between the server and the client. And we're going to do that in combination with the Zod validator. So as you can see here, for every route that you initialize with Hono, You can pass a bunch of middlewares inside. So where do the middlewares go?
So everything between the route slash hello and the initialization of context. Everything inside is a middleware. So there can be as many middlewares as you want here, right? So all of these things, for example, would be a middleware. So these are just mocks, right?
But all of these things would be middlewares. So all of these are valid middlewares right here. And they can be validators, they can be authentication protectors, they can be permission protectors, so anything, right? So what we're gonna do here, let's see if we can already try this out. So I want to use the Zod validator here and I want to validate my param here for example.
So I'm gonna go ahead and see whether I need to install the Zod validator. We do. So let's go ahead and add Zod validator here. So go ahead and do this along with me. In your terminal, I will shut down the app and do bun add Hono Zod validator right here.
And let's do bun run dev. And we also need Zod. So bun run Zod, bun add Zod. All right. So now I'm gonna go ahead and I'm going to import Z from Zod and then I'm going to add the Zod validator here like that and then in here we can add Zod validation on the params.
So let me show you how you can do this. So in here you would add Zod validator. Let's see if we can do this. Did I add the Zod validator? Sorry, Z validator.
And inside of Z validator you can define what you are validating. So if this is a post request you can validate the form or the JSON. In our case we want to validate the param. So inside of here I'm going to write z.object and I'm going to write the test needs to be a type of z.string like this and then instead of using c.request.param I would use c.request.valid let's see like this and I would destructure test in here and this would be so valid params and there we go test is a type of string and if I change this to number for example, there we go. Test is a type of number now.
And using the same method you can also for example validate a form. So now we don't need this right. Let's say I chain a post method here. Let me just get the context and let's return a c.json here. So in here we can now add z validator, json and z.object inside.
And for example, we would need, I don't know, a name, which is a type of z-string, and we would need, for example, I don't know, a user ID, z-string. Like this. So usually you would access the body, you know, without any validation here, but now that we have the Zot validator, what we can do here is we can extract the body from c.request.valid. And we would specify valid JSON. And then in here, we would get the name and user id and we know exactly what type of let's say user id is a number for example right so we would know exactly those types right here and here's the cool thing So right now we are typing the Z object here, right?
We are defining the schema here. But later when we add our database we're gonna go ahead and we're going to share the schema from the Drizzle database and we're going to reuse it for validation of our API routes and for our form values on the front end. So it's gonna be a seamless experience using RPCs, using Hono, Drizzle and Zod. It's gonna be very very smooth and end-to-end type safety. So if you want to you can also chain validators.
So let's say you had this to be I don't know create slash I don't know something post ID. If you want you can chain the validator so you can add another one and this one would not be adjacent but this one would do the params right post ID and then below that you would have valid params and it will only extract the post ID, not params, but param, in case you were wondering, right? So as I explained in the beginning, everything between the route and the returning of the context is a middleware here. Perfect, So I hope that kind of summed up Hono. What I want to do now is I want to create a protected API route.
So, so far we know that we can use the middleware to protect the routes. So If we wanted to, technically, we could add API asterisk dot if this is the syntax, I'm sure, I think it is. So now I think I'm logged out, right? So if I go to localhost 3000, I will be prompted to log in. All right, so I did something wrong here.
So I'm gonna go and write slash API slash, let me just find the correct syntax. I think it's the opposite order. So I add a dot and then an asterisk. Let's see, there we go. So now if you attempt to go to slash API slash hello, you get redirected back to the sign in route.
So if that is okay for you, you can do that. You can protect all of your API routes. But I don't want to do that. I don't want my API routes to be redirected here. So I want to be able to access API hello.
But at the same time, I also want them to throw a JSON error if user is not authenticated so we can do that as well inside of the documentation of Hono here inside of middleware here you can find all kinds of authentication middleware that exists. And even this is not the whole list because then you have this third-party middleware here. And inside of here you see that they support Sentry, Firebase, Qwik, TRPC, whatever all of this stuff is. And we also have Clerk out. And if you're interested, also out the JS or previously called Next out.
So I'm going to go ahead and use clerk out here. So I need to install this. I need to install Hono clerk out and I also need the Clerk backend. So let's go ahead and do those one by one here. So bun add HonoClerkOut and I also need the Clerk backend.
Like that. Let's confirm that we have all the necessary environment keys here. So clerk secret key and clerk publishable key. Let's see if we have that. So inside of my dot environment, we have the clerk publishable key and we also have the clerk secret key but I think that we are gonna have to add the clerk publishable key one more time separately so I don't know how much you know about environment variables in Next but if you want to expose them to the client you add the Next prefix to it.
But this one requires a ClerkPublicPublishableKey but in a form of server only. So here's what I want you to do. I want you to copy and paste your existing Next Public Clerk Publishable Key and remove the Next Public in front of it. So it needs to match exactly what this documentation says. Clerk Publishable Key, just like that.
And later we're gonna try to remove it just to see if it it would have worked without it and let's see how we use it so first of all we have to define well in here they set all the routes to use clerk middleware I don't want to work like that I want to be specific around which routes are protected. So let's go ahead and import this too. So Clerk middleware and get out from Hono slash Clerk out. I'm gonna go inside of my routes here, inside of app API route. Let me just zoom in here.
And I'm gonna go ahead and add those two. So from Hono, ClerkOut which we've just installed alongside the ClerkBackend, we now have the ClerkMiddleware and GetOut. So I'm gonna go ahead and do the following. How about I clear up this entire thing where we learned some parameters and stuff about Hono and I'm gonna go ahead and I'm gonna add a middleware here. So remember everything in here in this space between this last method is a middleware.
So let's go ahead and collapse this and let's write clerk middleware right here. There we go! So now what we can do inside is we can go ahead and we can extract the user like this. Get out and pass in the context. And then in here, we can check if there is no out user ID, we can go ahead and return c.json, error unauthorized.
I think I misspelled it, but it doesn't matter for this case. I just wanted to demonstrate this. So make sure you are not logged in. So just make sure that on the localhost 3000 and also make sure you have the app running. Just a second.
Run dev. There we go. Let's just wait for this to refresh. All right. So it's redirecting me here.
If I go to slash API, hello, there we go. I get an error back that I am unauthorized. Perfect. So now this is working. I'm going to go ahead and try and log in now.
And I'm also going to extend this method. So alongside message, let's also write out .userid here, if we have one. Like this. So I'm gonna pause the screen and I'm gonna go ahead and sign in. All right, so I'm signed in.
Let's go ahead and go to slash API slash hello. And there we go. We have a matching user ID from our clerk user. Perfect! So now our clerk middleware is working and we are separately protecting our API routes as opposed to doing it in the middleware.
I think this is a better way of doing it because the middleware will redirect you to the root page. Perfect! So now we have that. And I want to resolve one more thing before we wrap up this chapter that I think you probably are thinking of. And that is, alright, But if we have a bunch of these routes, isn't this file going to become absolutely huge?
Yes, but luckily for us, there is a solution for that. No, we're not gonna write it all in one file here. So let's go ahead and see what they say about best practices here. So I think that you probably have something in mind of building controllers as they wrote here, right? So for example in here you wouldn't write a route but instead you would have, you know, hello controller right?
Something like that. Well as you can see in here they say don't make controllers when possible so they call that rubby on the rail rubbing rails like controllers so the issue is related to types for example let's see the path parameter cannot be inferred in the controller without writing complex generics. So that is the problem, right? Remember when I showed you how we can have type safety then infer params? You can't do that if you build controllers, right?
They do have a solution for that. If you really, really want to create a controller you can use something called create factory here. But we're not going to do that here. Instead, what you can do is you can build separate files like this one. So let's go ahead and just copy this example.
So I'm gonna go ahead and copy this right here. And I'm gonna go inside of my route and I'm gonna create outdoors.ts. And I will just paste this here. Right? So let me just return that.
There we go. And then I'm gonna copy this again, the one below it, and I'm gonna call this books.ds. So separate files, right? There we go. So we have a get, a post and a get of a specific ID.
And now back in my root file right here, what we can do is we can import those two. So let's do that. There we go, like this. And we don't need Clerk Middleware anymore. So just import your authors and your books and instead of using app.get you would do app.route.
Authors and you would just use authors and then below that you would do slash books slash books there we go so now this is much more manageable each entity has its own file and we still have access to the context and all the type safety inside. So if you want to go to slash API authors, we should get a return of text list authors. If I go to the book, my apologies books, list books. If I go to slash book slash test there we go it says get test or authors there we go api authors test get test perfect so this is working as well But this actually will require some changes later on when we build our PC. Right, so for our PC in here, you can already check this out.
They have best practices, not best practices, but is it in the guides? Our PC here. So in here, we're going to have to have one app type, which will hold all of our routes. So regardless if they are in different files or not, we're gonna have to somehow find a way to hold all of those types together. And in here they actually explain to you how you can do that.
So let me go ahead and scroll down right here they should have something called there we go using RPC with larger applications. So as you can see the difference is that we're not gonna have this separate let's go inside of author. So inside of here they keep calling the app again. So app.get, app.post and app.get again. When we switch to RPC we would have to chain those together.
So we wouldn't have app.get, app.post. Instead, it would be app.get.post.get, right? So we would chain the elements. And same is true for this example of authors and books. You would chain them all under one app and then your app type will be able to hold all of the types and context tests and params and all the Zod validations together.
But that's for later. I just want to give you a quick introduction to Hono. So go ahead and play with HONO a bit yourself. What we're going to do in the next part of the tutorial is we're actually going to go ahead and start going back into our UI so that we can build our navbar. Great, great job!