How To Build A Production Ready API - Auth, JWT, API Keys
Chapters10
An overview of building a production-ready, type-safe Hana API with robust authentication (email/password and JWT-based sessions) and secure API keys, plus testing using the Requestly tool.
A practical, production-ready guide to building a type-safe API with Hono, JWTs, API keys, and a PostgreSQL database using Drizzle ORM.
Summary
Web Dev Simplified’s Kyle walks you through turning a simple Hono app into a production-ready API with end-to-end authentication and authorization. He starts by scaffolding a Hono app, then demonstrates how to structure routes, nest them, and enable TypeScript safety. The tutorial covers JWT-based login, API key generation, and hashing, plus how to test everything with the Requestly API client. He introduces Drizzle ORM (v1.0 RC at the time) and Postgres, wiring up migrations, schemas, and relationships (authors, users, API keys) in a clean, scalable way. You’ll see real-world patterns like environment-based URLs, pre/post request scripts for automating test data, and protected routes via middleware. The video also emphasizes security best practices: hashing API keys and passwords with salts, using timing-safe comparisons, and revocable API keys. Kyle wraps up with a hands-on demonstration of books, authors, and user-driven permissions, showing how a admin vs. user role affects CRUD operations. If you want a blueprint to build a robust, type-safe API stack from scratch, this is a thorough, practical reference.
Key Takeaways
- Create a Hono app with TypeScript and organize routes in a routes/authors.ts structure for maintainable growth.
- Implement JWT-based login with a secret stored in environment variables and sign tokens that expire after a set time (e.g., 5 minutes).
- Hash passwords and API keys with salts; store only salted hashes and use timing-safe comparisons for verification.
- Use Drizzle ORM (Postgres) v1.0 RC+ kits to define tables (authors, users, API keys) and migrations, with strict, verbose migration feedback.
- Implement environment-based base URLs (dev vs. prod) and Requestly scripts to automate test data and keep tests consistent across environments.
- Protect sensitive routes with API keys or JWT middleware, and demonstrate admin vs. user permissions on CRUD operations.
- Automate API testing with Requestly: post/put/delete flows, pre/post scripts, and shared variables (e.g., last ID, JWT) to avoid manual copying of IDs.
Who Is This For?
Essential viewing for full-stack developers who want a solid blueprint for building a production-ready, type-safe API with Hono, JWTs, API keys, and Drizzle ORM on PostgreSQL. Great for teams needing a tested pattern for authentication, authorization, and database-driven routes.
Notable Quotes
"“The very first thing we need to do to get started is just to create a Hono application.”"
—Kyle begins by scaffolding the Hono app and shows how to structure and import routes.
"“We’re hashing and securing API keys inside the database… and these API keys are all being hashed.”"
—Introduction to API key security and why hashing is important.
"“JWTs are great for short-lived authorization, but for long-term API access you want revocable API keys.”"
—Explains the complementary roles of JWTs and API keys.
"“We’re using Drizzle ORM v1.0 RC to define tables and migrations for Postgres.”"
—Drizzle ORM introduction and migration workflow.
"“Middleware makes the protected routes easy to share across an entire section instead of duplicating logic.”"
—Demonstrates applying API key middleware to routes cleanly.
Questions This Video Answers
- How do I implement JWT authentication with Hono and TypeScript in a production API?
- What are best practices for hashing and storing API keys and passwords?
- How can I set up Drizzle ORM with PostgreSQL and manage migrations in a TypeScript project?
- How do I test APIs locally using Requestly vs. Postman for a production-ready setup?
- What’s the difference between using JWTs and API keys for API authentication and authorization?
Web Dev SimplifiedHonoTypeScriptJWTAPI KeysDrizzle ORMPostgreSQLDatabase migrationsMiddlewareAuthorization
Full Transcript
In this video, I'm going to show you how to set up a fully Types safe HANA API that's production ready. And we're going to be covering things like every type of authentication you would need. For example, we have email password-based authentication. Also, when you log in, it's going to be based on JSON web tokens, which can then be used to create various different API keys. And these API keys are all being hashed and secured properly inside of the database we're going to be using inside this project. And then we have basic create, read, update that have various different permissions for what you can do based on if you're logged in or not.
and based on your different admin or non-admin levels. This is going to give you that baseline of everything you need to know to work with HANA inside of an API. And it's all going to be type- safe and production ready. And if you're curious what this amazing tool is that lets us do all this API testing fully automated and incredibly easily, this is the Requestly API client. It's an entirely free API testing tool that makes testing local or production or even staging APIs incredibly easy. They were kind enough to sponsor this video. And if you're familiar with a tool like Postman, requestly is essentially a very similar tool, but it cuts out a lot of the extra bloat and things that annoy me personally about Postman and it's just a better alternative.
And again, it's entirely free. They'll be linked down in the description if you want to check them out. Welcome back to WebDev Simplified. My name is Kyle and my job is to simplify the web for you. The very first thing we need to do to get started is just to create a Hono application. So we can type in npm create hono at latest. And if we just put a period afterwards, that'll create it in our current folder. And this will allow us to create a hono app. Now, for the templates, it really doesn't matter too much which one you choose.
This kind of depends on where you want to deploy. In our case, we're just going to be choosing Node.js cuz that's like the most basic one that there is available. And we'll say that we do want to install our dependencies using npm. And that's going to clone down just a very basic starting code. As you can see, there's really not much. If we look inside of our source, you can see we just have a very simple file right here that just starts up our server and serves that content. And the only thing that's being served right now is this route that is the homepage.
And it just gives us the text of hello hono. And we have a basic TypeScript config because again all of this is going to be purely type- safe and our package JSON. You can see there's pretty much nothing in here other than a dev script to start up our application. So let's run that by saying npm rundev. And that's going to start up our application. You can see here servers running on this localhost 3000. And if we just open that up, you can see it just says the text hello hono. It's quite small, difficult to read.
There we go. You can see that text hello hono. Now what we need to do is obviously turn this into an API. We don't want to be returning text inside of this. We want to be returning JSON and so on. So the next step is to turn this into an API. And the first step I really think I want to work on is just to be able to work on the most basic fetching of different data. And then we can move on to adding in our database and authorization. Now inside of Hono to create different routes, you can say app.get or we can do app.post delete put and so on.
But we generally want to structure these routes inside of different folders to make them easier to work with. Because if we put all the routes for our entire application in this one file, it's going to become quite large and unwieldy. So instead, I'm going to create a brand new folder. This folder is going to be for all my routes. We'll just call this routes. And inside of here, we'll create a folder for all of our author related routes. We're just going to call this author.ts. Just like that. And now all of our author routes, for example, this route right here, are going to go directly inside this folder.
And to create nested routes, all we need to do is just generate a new HANO app. So we can say const app equals new HANA. Just like that. And if we properly import Hano, now we have the entire HANA app working. And this is just a nested route. So now what we can do inside of here is instead of saying app.get, we can say app.route, we can pass it along what the path is going to be. So for example, / author. In our case, let's make it slash authors just like that. And then we can import the different routes we want to use.
So these are going to be our author routes. So we want to make sure we export those here. So we're just going to say we're going to export our app. And we'll set that as a default export. Just like that. So now I can import that author routes up here by just saying import author routes from and I want to get inside that routes folder / author just like that. That's going to import those routes. And if I just make sure to put thatts extension at the end of that, you'll see it gives us a really small error.
This is something we can fix in our TypeScript config quite easily. We can just come into here and use this rewrite relative import extensions. Set that equal to true. Make sure I just wrap this in quotes. And all that's going to do is it's going to take any place that we have this.ts extension and convert it to.js for us. So now everything is working like we expect it to. And now we have this / authors route which is just rendering out this text. We'll just say authors. So now if we go to hello world and we go to the / authors route specifically, you can see it prints that out.
And the nice thing is is we can nest this. So this is / authors. And if we want to make this route slightly differently, for example, we want to say authors 2 just like that. Now we need to go to slauthors /2 since they're going to stack on top of each other. And you can see it prints out that exact text just like before. Now, before we start hooking this up to a database, I just want to have some placeholder data that we can use. So, we can say const authors is equal to an array.
And inside of here, we're just going to put in some random IDs. And we'll just give them a name. So, for example, Kyle. And we'll copy this. So, we have two authors in this section. Just like that. We'll have Sally, and this one will be an ID of one and an ID of two. And now, I want to create all of my different create, read, update, and delete routes. So the very first route that you're going to have instead of a normal rest style API is going to be your list route. This is going to list all the different things that the user has access to.
So we want to return all of these authors and we're creating a JSON API. So we want to return this data as JSON and not as text. So here with that C value which is all of our context for hono. We can just call JSON and pass it along any JSON object we want and it's going to return that data as JSON to our user. So now we've created a list route that's going to return all of that data as JSON. Now again to just really quickly test that, we can open up that server. We can go to slash authors and make sure I spell that properly.
There we go. And you can see we've got printed out an array of all of that different data as JSON just like we expect it to be. Now the next thing you're going to create in a CRUD application is the ability to get an individual author from our list. So instead of just a list, we want to get an individual user. To do that, we can use that same app.get because we want to use a get method here, but instead of getting all of them, we want to get specifically an ID and to get a dynamic parameter.
So for example, I could pass along slash 1 slash 2 slash3 and so on. We can put a colon in front of whatever our variable name is and then write out our variable name. And this will automatically add type safety for that variable name inside of our parameters. For example, I can say C.est.param. And that's going to give me all of my different parameters. And I specifically want to get just the ID. And you can see here I already get full type safety. So I'll type in ID just like that. And I can come in here.
And now I get type safety that this ID is going to be a string value because hono is smart enough to understand all of this automatically which is why I prefer hano over something like express. Next what we just need to do is filter our list to get the author with that particular ID. So what we can do inside of here is we can just say authors. Let's actually say we want to get an author which comes from authors dot or we want to filter actually we want to find specifically just an individual author where the ID is equal to our ID just like that and then we can return that author down to our user and if we didn't have an author we could for some example throw down an error so we could say if our author is equal to null that means we don't have an author instead we can just return down some type of error.
So let's come in here with an error that says author not found. And we can also throw along a status code of 404. So the second parameter of this JSON function allows you to change the status code. And the HTTP status code 404 just means not found. So it's just a little bit more information we're sending down to the user. So we're either sending them the user or the author in our case or we're sending them down an error. So again, let's just test that out by opening this up and manually doing some testing. So we can say / author slash one and we should hopefully get the first author.
And it looks like it's not returning that to us. Let me just make sure that all of our code is correct. Authors.find. It should be finding it. It is at that slash ID. So that should be correct. Let's just refresh this one more time. And actually the reason this isn't working is it should say authors one. There we go. So you can see it's returning the first author. Authors 2 returns the second. And if we pass in an ID that doesn't exist, you can see we get that error. Author not found. and it's returning the correct error codes and everything else.
Now, that's pretty much as far as we can get testing using just our browser. And to do further testing, we're going to need to use the Requestly client. So, I'm going to just go ahead and start getting set up on that so we can do the testing for our post request, put request, and delete request and also make this type of testing much less manual. So, inside of requestly, when you first get set up, you're going to have a completely blank environment like I do. So, the very first thing we want to do is to create a new collection.
And this is where we're going to store everything for this application because if you're working on multiple applications, they're all going to have their own collections. We'll call this library because that's a relatively self-explanatory name for what we're doing. And that essentially gives us this nice little library folder right here. Now, inside this library folder, I also like to add additional collections. So, we're going to add a collection into here. And this is going to be for all of our author related stuff. So, we'll add the authors into there. And then we can add individual requests for what we want to do.
For example, this is going to be for listing out all of our individual authors. So, we'll call this list. Now you'll see automatically by default it shows this git request because we're doing a git. We'll leave it as a git because that's what we want. And we can paste in our URL here which is just going to be localhost 3000. Make sure I have my slashes up here. And we want to go to slash authors. And that's going to get all of our authors when we send along this request. And if we just save that and click send, you can see we get a list of all of our different authors being sent back to us.
We can also create a request that's specifically going to be forgetting an individual author. So we'll come in here. We'll create a brand new one. This is going to be called get just like that. This is going to be for getting an individual element. And we kind of want to copy this from our list. So we'll copy that over. Go into our get section just like that. And this is going to be for a particular ID. Let's in our case say the ID is going to be one. And then we'll give that a quick save.
And if we send that along, you can see we get the user with the ID of one. And here you can see we get the user with the ID of two. Now it's generally not ideal to hardcode our values up here. So using that same colon trick, we can say colon ID. And that gives us these path variables down here at the bottom where I can specify for example one, two, and so on. So if I put in one, I can send this and get back the data for user number one. This again just helps me a little bit with my testing.
I'm going to show you later how we can automate almost all of this to make it incredibly easy. But the first thing we need to do is actually create the post method to create authors and so on. So let's just give that a quick save and let's work on our post method to create an author. Next, I'm just going to copy all of this code so we kind of have a good starting point. I want to do a post. And in order to post, generally what you do is you post directly to that main/author's route or slashbooks or whatever it is.
So we're just going to post to that main route just like that. So we don't have an ID or anything like that. We just want to pass along all the data for the author that we're going to be creating. And that comes from the body. Now we want this to be type safe. So we're going to be using zod for all of our validation. So let's make sure we install zod as a library dependency. We can say npm zod. And we're also going to be installing a library from Hono called hono standard validator. And this is going to allow us to use zod to validate these inputs automatically for us.
So we don't have to manually write that code ourself which is really handy. Now what we can do is we can use this S validate function. There we go. S validator. This S validator is going to take in a string of what we want to validate. In our case, we want to validate what our JSON input is. So we'll paste in JSON. That's coming from our body, the JSON body. And then we need to pass along whatever our schema is going to be. So we can just call this create author schema or something along those lines.
And let's create that create author schema. So at the very top here we'll say const create author schema equals z that comes from zod. And we want to create a brand new object for all the keys and values we want to test. Now our schema is going to be very basic. We're going to come in here with a name. This name is going to be a string if I can spell properly. There we go. With a minimum value of one. And we're also going to specify a birthday just like that. And what we're going to do is we're going to use Z.co.
That's essentially going to take the type of string, which is what it normally is, and try to convert that into whatever type we specify next, which in our case is going to be a date. And we're going to say that the birthday is entirely optional. There we go. Now, if I give that a quick save, that's our full schema. And that'll give us all of our information for our author down here that we can use. And if we wanted, we could even put a birthday on some of these. So, we can come in here. Let's say this one is going to have a birthday and we'll set that equal to just whatever the current date is.
Just like that. Now to get all of that validated JSON data, what we can do is we can say const data is equal to C.est.valid. That's going to ask us what valid data we want. In our case, we want to get the validated JSON data. If we hover over the type for this, you can see that matches the type that we created in our ZOD schema. Again, everything is fully type safe, which I really love. Now, the next step is to just save that author inside of our list of authors. So what we can do is we can just say authors.push and we want to push in a brand new author.
So we'll say ID is just going to be crypto random UU ID and we can pass along all of our different data just like that. So it's essentially creating a brand new author and we can return what that brand new author is. So let's actually put it in a new variable just like that. We can push that author into our author's list and then we can return that author down to the user and we're going to pass along the status code of 2011 which just stands for created saying we created this brand new user. So now we just gave that a quick save.
Let's make sure we test this out. So let me bring over that requestly API client. I want to create a brand new request right here. And this request is going to be specifically for creating. So we'll call it create. Just like that. It's going to be a post request. And we're going to be going to that URL which is just our local host URL. And we specifically want to go to the author section just like that. And now we want to be able to pass along JSON data. So we can do that inside of our body.
If we specify our body type as JSON, we can now pass along any key value pairs that we want inside of this section. So let's go ahead and we specify a name for our user. Let's just say that the name is going to be Kyle number two. And we could pass along a birthday if we want. For now, I'm just going to keep it as a name. Now if we just give a quick send along on that you can see that we now have a brand new user being created and this user for the author sorry has an ID being specified and it has a name and if we copy this ID we can actually use this ID inside of our git function.
So down here passed in that ID and you can see when we click send it's now returning us the information for that user which has an ID and a name and we never gave them a birthday which is why no birthday shows up at all. Now let's just make sure that we save both of these. And now the next thing we can work on is going to be update and delete. So let's come in here with a simple update function. We're going to be using put for all of our update methods. So we'll just say putut like that.
And in order to update, we need to update a specific ID. So we're going to request a specific ID. Again, we want to validate the information that's being passed in. And this is going to be a brand new schema called update author schema. It's going to look very similar to our create schema. So we can come in here. We'll copy that down. Really, the only difference is we want to make sure that all of our options are optional because we can sometimes update one thing and not other things. Also, I want to be able to have the option to remove a birthday.
So, I'm going to specify this as nullable. This just allows me to pass along null as an option. So, I can now remove the birthday if I wanted as well. So, that's my update schema completely done. I also want to make sure I can get my ID inside of here. So, we're going to use that exact same ID code. So, now I have my ID. I have my data that I want to update. All I need to do is get that author and update all the information for that user or that author. So, to get our author, that's relatively simple.
We can just go through our author's list. We want to find the author where the ID is equal to our ID. And again, if we don't have an author, we'll throw the same error we had before. So this error just says, hey, we don't have an author. Throw throw a 404 error cuz we can't actually do this. Next, what I want to do is I want to update this author's information so that it's stored properly. So we can just say that the author.name is going to be set to data.name and the author. equal to data.
birthday. And since these values could technically be undefined, I'm just going to do a quick check that if our data.name is equal to undefined, I'm sorry, if it's not equal to undefined, then we're going to set it. And I'll do the exact same thing. If our data birthday is not equal to undefined, then we'll set that value properly. And I'm getting a little bit of a type error just because up here, I just need to specify the type for my author. So, we'll say that this author type is an ID with a string, a name with a string, and it's going to have a birthday that is a date, and it's going to be an optional date.
And this is just an array of that data. That should clean up my type errors down here. And actually, birthday I need to make sure can be a nullable value. So, I'll just come in here with null, just like that. There we go. Now, that does set up everything properly for my author. I'm pushing that back into the list, which I don't even need to do cuz it's already there. And that should update my user and return that user's data back to us. Now, a lot of this is kind of hacky because we're just doing this in memory.
We're going to be converting over to a database very soon after we get the rest of these methods in place. But for now, let's go ahead and actually set up our requestly for this put method. So, let's bring that over. And inside here, I want to create a brand new HTTP request. This one is going to be for our update. So, we'll call this update. It is a put method. And it's going to go to essentially the exact same URL. We want to make sure it has the ID parameter at the end of it. For now, let's just hardcode the ID of one into there for what we want to update.
It's going to take in a JSON body, and we can pass along any key value pairs that we want. For example, we could say the name here is going to be new Kyle because we're updating that name to be whatever our new name is supposed to be. And now, if I click send to send along this data, you can see that it's updated that name for this first identity here to be new Kyle instead of what it was in the past. So, with that done, let's just give that a quick save. And now, let's move on to the delete method next.
So I'm going to copy all of this code because it's going to be very similar. Delete is going to take in an ID, but we don't have any JSON data we're validating. So we can just completely remove that parameter. And these parameters that we are passing along is like this second option here. This is just middleware. So this is just code that runs before your actual request, which is this final function and can do different things by adding or changing your parameters. In our case, it's just validating this JSON data for us. So now down here, we have delete where we're getting in our ID.
And all I want to do is I just want to remove that ID from our author's list. Now to do that we need to find what the index is that we're removing. So we can say index is authors.find index where that ID is equal to the ID. And if we don't have an index in our case if the index is equal to negative one that means it couldn't find anything. So we'll return a not found message here. Otherwise we're going to go ahead and we're going to remove that from our list. So we can say authors.splice and we can say that we want to start at whatever our index is and we want to remove one element.
That's going to remove one thing from our author's list. And then for returning data, we don't really need to return anything because we're just deleting data. We're not adding or creating or updating. So here I'm going to pass along a body that is null. That's a completely empty body. And to say that you have successfully executed and you have no data to return, use a 204 status. This is very commonly used with any type of delete that you're doing. Also, since our status up here is not for creation anymore, this is an update status. I'm just going to leave it as a normal 200 status to specify we or we updated this properly.
and 204 is being used for our delete status right here. So now let's go ahead and set this up in requestly. Let's come in here. We want to create a brand new request. This is going to be a delete request with a very similar URL except for we want to have the ID we're going to be up or deleting. Let's just say we're going to delete ID number one. And now it should be if we send this request along that it's passing us along no data at all. But if we test our get function right here, you can see that ID1 has been completely removed from the list.
You'll also notice none of our other elements are in the list. That's because every time we save our server, it restarts and refreshes all of our database data. That's because we don't have anything stored in a database yet. But that's the next thing we're going to be working on. Once we clean up our requestly setup a little bit to make it even more automated than it currently is. So, let's just go ahead and save this. And now that we have that saved, let's actually make sure we call it delete. Just like that. We'll give it another save.
And now I want to work on highly optimizing what this is going to do. So, the first thing that you'll notice is inside of all of our URLs, we have this HTTP localhost 3000 being copied between every single one of them. This is obviously not ideal, and if we wanted to test our production or staging environment, we would have to manually overwrite this. This is why there's something called environment set up that we can use. So, let's create a brand new environment. We're going to call this our dev environment. And we can create any variables we want inside of here.
For example, we can create a variable called base URL. And we can give it a value. Let's give it an initial value of localhost 3000. This initial value is synced between all of the different people in your workspace. So for example, if you have other people working on this project, this will be synced with them. While the current value is just something local to just you. For our purposes, it doesn't really matter. We'll set the same value for both of them. Then if we give that a quick save, we now have this base URL variable we can use in development.
I also want to create a production environment as well. And let's come in here with a base URL. Just like that. And for this, we would have whatever our URL is. So let's just imagine this is exampample.com. You would come in here and all of your example.com stuff would be there. So now we have a development environment and a production environment that both have the same variable. And we can make sure we select our development environment right here. And now we can use that in all these methods. So let's replace this entire URL with that variable.
To do that, you just open up double curly braces. You can see we have tons of options. Specifically, we want that variable we just created. And if we hover over, you can see what the exact value of that variable is. And when I send along this request, I still get all of my data being returned to me. So, just make sure we save that. And I want to essentially copy this base URL to all of our different requests. So, let's paste this down there. Give that a save. Paste it down here. Give that a quick save.
Same thing for our list. Save that. And then finally, we want our update as well. And we'll give that one a save as well. Now, this next feature I want to show you is one of my favorite features in all of Reestly because it makes automating your test so much easier. For example, one thing that's really annoying is let's say I want to create a brand new author. Let's say I want to give it the name of Kyle 2. I click send. I now have that brand new author. I now need to take this ID.
I need to copy that ID. I need to move it into delete. Paste it down here. Or if I want to get it, I need to paste that value into here. Same thing if I want to do an update. I have to paste that value here. I just have to move all over the place using the same value everywhere that I go. This is kind of cumbersome and annoying to work with. This is why it's really nice to be able to create variables. In our case, we're going to go into this author section. We're going to create a variable called last ID.
Just like that. We'll give it a save. And this is actually set on our author's collection, which means everything in this folder has access to this last ID variable. And we can automatically set this last ID variable to whatever value we want. For example, when we do a create, I can just get this ID value right here and automatically set it by using these scripts. We have pre and post request scripts that essentially before your request goes off or after you get back your response, you can run various different code. In our case, I specifically want to run this line of code.
So, we're just wrapping our line of code in try catch and we can use RQ, which stands for requestly. We can access various different things. For our case, we can access the collection variables. These are the variables set on our folder. In our case, our last ID is on our author's folder. Then what I can do is I can set that value by saying set pass it in the name of my ID or my variable which I called last ID. And if I want to get the value, I can use RQ.response. that gives me what is being returned from my API.
So that's all of this information over here. I want to convert this to JSON because we're getting JSON data and I want to get the ID property right here. So now whenever I make a request, it's going to run this code after the response gets back. It's going to take the ID and it's going to save that ID directly into this variable right here. And then I can use that variable everywhere else that I want. So let's give that a quick save and we'll try sending this to make sure that it works. And I gave it a quick send.
You saw that we got no errors at all. If I go into this author section right now, there's no value being set inside of here because it's not quite showing up. But the nice thing is if I try to use this value, it should work. For example, I can come in here and I can say that I want to use that last ID variable. You can see the current value is actually set to the value from my latest create request right here. So it did work properly. And now if I were to do a delete request, it should delete that user.
When I click send, you can see no date is returned and it did delete that user. I can do the exact same thing down here. So I can say last ID just like that. Do the exact same thing for my update as well. Change this to last ID. There we go. So now any single time that I try to make a request to create something. For example, I send this along. It's going to take this ID. It's going to save that ID. And now I can, for example, update that user by sending along a request and it's going to make sure to use the correct ID.
Or if I want to get just that user, I can send this along and get it. Or if I want to delete that user, I can delete it and I don't have to worry about copying around any IDs. Everything's automatically handled for me. We can also make it so that our data is much more automated. For example, right now I'm hard- coding this name. I don't want to hardcode the name. Instead, I want to use some random data. The nice thing is they have a bunch of random data you use. So, I can, for example, get a random name.
Let's just see here if we have a random username, random full name. Let's go ahead and go with that. And now, if I just give that a save and send, you can see every time I send, I'm getting a completely different random name. And since I hooked up all my ids, for example, if I try to do a git request, it's going to give me whatever the most recent user I created is, or I can delete that user if I want, and so on. In my update function, I can essentially do the exact same thing.
I can come in here. I'm just going to put new in front. And then I'm going to get my random full name. There we go. Just like that. And now if I give that a save and I click send, this author is not found because we just deleted them. So let's create a brand new one. And now let's try to update. And you can see it just puts new as well as a completely random name afterwards. Now, hopefully that little bit of a taste kind of shows you why I really, really love this requestly tool and why I was so happy when they reached out to sponsor me.
Again, if you want to check out this tool, it'll be linked down in the description below. It's entirely free to use all of this, which is absolutely amazing. Now, the next thing that we need to work on in our application is hooking it up to a database. Cuz right now, everything's hardcoded. It's not great at all. So, let's work on using Drizzle for our database. We're going to be using the brand new version 1.0 0 release of Drizzle, which is honestly super incredible to work with. Now, in order to install Drizzle, we just need to run npmi Drizzle OM.
And I am currently recording this before the version 1.0 is released. It's currently in a release candidate. So, if you're watching this video right now before version 1.0 releases, just type in RC at the end. That's going to get you the release candidate version. But hopefully by the time you're watching this tutorial, or if you're watching a little bit in the future, 1.0 0 will currently be the standard release and you won't need to add that at RC at the end of it. I'm just doing this to keep this tutorial up to date for when Drizzle releases version 1.0 quite soon.
We also need to install the development dependency. So just do a dash capital D and this is going to be Drizzle Kit. This is just making sure we can do migrations and things like that. Again, if you're watching this video right now, you need to use the release candidate version, but in the future it should hopefully be the 1.0 version automatically. Now if we look inside of our package JSON, we should see that we have inside of here Drizzle kit with that release candidate version and the Drizzle OM. Now we're going to be using Postgress for our database.
So we need to install all of our Postgress stuff by just saying npmg. And we can also install the development dependency of at types /pg to get all the types for the Postgress library. And finally, I want to also install.env. So we're going to install so that way we can get all of our environment variables working properly in our application as well. Now to set up our database, we first of all need to create an environment variable. So we'll say env. This is going to be our environment file. And let's make sure it's included in our gotget ignore, which it is, which is great.
So now we want to create a DB section. And inside of this section, we're going to have a few different variables. We're going to have our DB password. So let's make sure we come in here with our DB password. Set that to whatever you want. We'll just say password for now. We're also going to have our DB user. We're going to set that to Postgress. We're going to have our DB name. We can call this whatever we want. Let's just say library YouTube video. We're also going to come in here with our DB host. We're going to set that to local host because we're going to be running this locally.
And then finally, our port. The default port for the application is 5432. So, we'll just set it to the default port. Just like that. That hooks up all of our environment variables that we're going to need. Now, we need to run Postgress on our computer. And we're specifically going to be using Docker for that. So, let's create a brand new file. We're going to call this docker compose.yiml. I'm going to paste in what this docker file specifically looks like. But the key thing is we're going to have a services section up here with a database inside of it called db.
And then we need to hook up the image we want to use. In our case, we're using Postgress version 18. Our host name, ports, environment variables, and volumes are all kind of set up based on our environment variables. You can see we're just porting over all of our environment variables. And this volume just tells Postgress where to set your actual data. And this volumes down here just exposes that data to our computer. Now, don't worry. You don't need to understand how this Docker file works or anything inside of it. You can go to the GitHub repository linked in the description and copy this file exactly as is.
And as long as you have Docker up and running on your computer with something like Docker Desktop, it'll run perfectly fine once you paste in this configuration file. And then just run docker compose up. That should start up your application. As you can see, everything was created and is working just fine inside of our application. Now, the next step to work on is to be able to get our Drizzle configuration set up. So, inside of our application, let's create a brand new file called drizzle config.ts. And inside this Drizzle config, we're just going to export a default define config.
And this define config function should be getting imported from Drizzle. So, let's just make sure we do a quick import define config from. And that comes from Drizzle Kit. Just like that. Now, inside here, we can define a bunch of different things. First of all, the out location is where all of our migration files are going to go. We're going to put them inside of our source folder, and we specifically want them to go in a DB folder called migrations. Just like that. We then have our schema. This is where our schema is going to be located.
So, we can say this is going to be in our source /db/s schema. There we go. TS. Next, we need to specify our dialect. In our case, we're using Postgress. So, we can use Postgress as our dialect. And we also want to set strict to true and verbose to true. And that's just going to add extra safety nets inside of this. So when we do migrations and so on, it'll tell us what code is being run and make sure if we try to run any unsafe code, it'll give us a warning before doing so. Lastly, we just need to hook up all of our database credentials.
So this is going to be things like our password, which we know comes from our environment variables. So we could say process.env. DB password. But doing it like this is not type safe at all. So instead, I'm going to create an environment variable file for all of our Typesafe environment variables. So we're going to create a brand new file. We're going to put this inside of data/env.ts. Inside this file, I'm going to import/config. That's going to load up all of these environment variables for us. And then inside of here, what I want to do is I want to create a brand new environment schema using ZOD.
So we're going to import ZOD. And all I'm doing is I'm taking each one of our environment variables. So, DB password, DB user, DB name, DBO host, DB port. And all I'm doing is I'm making sure that they work within all of my schemas. So, I'm just making sure that these are values that exist. And also putting a port in here. So, by default, it'll be port 3000. And that's nice because inside of this file, I can, for example, replace this port with ENV, which if I make sure I export this, there we go. Export.
Actually, that's our schema. We need to get our data from that. So, let's get our parsed data, which is env schema.safe. safe parse pass it along our process.env. That's just going to parse all of our data. You can see here it's going to give us all that information and we can just say hey if our parsed data is not successful then we have an error. So we'll just say throw new error invalid env. And we can just pass along our parsed error message. That way we know if there's something wrong with our environment variables. Otherwise, we're going to export our envir variable, which is just our parsed data.
There we go. All this code is doing right here is just adding type safety to ourv file. So now, wherever I have that index file, I can say env, I can import that from that file I just created. And now I have type safety for all my different environment variables, which is exactly what I want. And in my Drizzle config, I can now get those environment variables as well. So I can say env to get my password. I can come in here with my database and my database here is going to be env. DB name.
I can also make sure I get my user which is my env. DB user. Let's see. We have our host ENV. DBH host. We have our port which is our DB port. Just like that. And then we're going to set SSL to false just because we're not working on a secure connection because everything is local. So that's why I have that set up as is. Now in order to use our database, let's go into our source folder. Create a new folder called DB. And inside that folder, we're going to create a file called db.ts. We're going to export a constant called db, which is just going to be equal to calling the drizzle function.
Just like that. And this drizzle function is going to take in an object, and we're going to be passing it in a connection parameter right here, which comes from our environment variables. So, if we take a look at this, you can see all I'm doing is mapping my password, username, database, host, and port exactly like I did before. And now to get drizzle, let's import that as well. So drizzle that comes from our Drizzle OM. There we go. Drizzle OM and we're using the Postgress version on node. So we're going to say node Postgress just like that.
So now we've hooked up our database with all these different connections and these connections right here. So now we can properly use our database inside of our application if we want. But we need to add what our schemas for our tables and so on look like. So let's create a folder called schemas.ts. And this is where or not ts schemas. There we go. And inside of here, let's create a folder called authors.ts to define what our author table is going to look like. So to create this table, we can just say we want to export a const.
We'll call this our author table, which is using the PG table function, just like that. And we can specify the name for this, which is authors. Then we can specify each individual key that we have. So for example, we can have an ID and our ID is going to be a UYU ID coming from Drizzle. We want this to be a primary key and we want it to be a default random value. So this is going to be our default ID that we use for everything. We also say that we have a name which is just text.
Again make sure this comes from the Postgress version. We want this to be not null. And then finally we're going to have a birthday. This one is going to be a time stamp. Again make sure we get the Postgress version. We want to set that to say with time zone true. That's just going to make all of our time zone conversions much easier. And then finally, we're going to have one more field. This one is going to be called created at. And this created at field is going to be not null. It's going to be required.
And it's going to be a default value of now. So that way, it's always defaulting to whatever the current value is whenever we create a brand new author. Now, we're going to have multiple different tables. I've just broken them out into each of their individual files. But to use them all in one file, we need to create a schemas.ts file. And in this file, we're just going to do a simple export star from. And we want to go into that schema folder. Just like that. So for every table we create, we're going to create an export line right here.
So we have access to that directly inside of our database. Now what we can do inside of our DB folder is we can pass along our schema just like that by importing our schema. So we're going to say import star as schema from that / schema ts file just like that. And that's going to import all of our schema related data directly into here. Now, technically, this is the old way of doing this inside of it. And we need to use relations instead. So, we're going to create a brand new file called relations.ts. And this relations file is going to import all of our schema data just like this.
And it's going to export a constant called relations, which we're going to set to some particular value. And then what we can do in our database is replace this with relations. And up here we can import relations from that dot / relations file just like that. Now to use these relations all we need to do is we need to use our define relations function and pass it along our schema just like that. There we go. And as we start to add more tables we'll define the relationships between those tables. But since we only have one table we don't need to do anything.
So this is the only thing this file has for now. And that has hooked up all of our relations with our database. So now we can start using our database properly inside of our application. So now let's go into our route for our author and let's just completely remove all this hard-coded stuff and change all of our routes to work properly. So all of these routes are going to be changed to asynchronous routes cuz we're going to be doing asynchronous data gathering. And the first thing I want to do is I want to get all my authors which is just call await and we're going to call DB dot.
We want to query make sure I import that query my author table and I want to find many which is just going to find everything in that list. And now we can return that data as is. So already that's completely solved this section. Let's work on our get section next. So here we just need to change this code to say await db.query. We want to query our author table. We want to find the first one and we can use a wear clause right here to say where the ID is equal to this ID right here.
There we go. make sure that is asynchronous and that solves this entire section. Again, we're just replacing that hard-coded data with our actual database. I can do the same thing down here with our create function. So, instead of doing an authors push, we're going to say db and make sure we await this db.insert, we want to insert data into our author table and we want to set our specific values just like this. So, to set our values, we can come in here with an array of our values or in our case just pass along all of our data as is.
We also want to get all of our data. So we can just say returning just like that and it's going to return all of our data. And if we make sure that this is an asynchronous function, we can then say const author is equal to getting that data from here. And this is actually going to return to us an array of all the data we inserted. We're only inserting one value. So by dstructuring it like this, that gives us the first author we just created. So that's updated our entire create function. Our update function is going to be luckily a much easier function for us to work on.
I'm actually going to copy the code right here because it's going to be very similar. We can just come in here, paste that down. I can do a simple DB update. There we go. And we want to set our data to be this new data. And we need to make sure we restrict where we update. So we can just push a wear clause directly onto here. And we can use this equal function to say where our author table do ID is equal to our ID. Now, what that code has done, if we make this a sync, is it's properly done an update function for our author.
It's returning all of our new author data based on that update. And this code down here, we could just completely remove because that was only there for that placeholder section. So, again, just replacing this code with our database code. And finally, for delete, again, this is going to be quite simple because we can just do db.delete on our author table and we want to delete where that particular clause is set. So we'll just copy over the exact same wear clause because we only want to delete in the instance where it matches the ID. There we go.
Make sure that's asynchronous. And now we finished setting that up as well. So to make sure that all of this is working properly, let's first go ahead and migrate over our database. So what we can do is we can run npm run actually npx drizzle kit and we want to do a migration. So we can just say migrate and actually we should generate our migration first. So we'll say generate just like that. This should successfully work and it's right now saying no schema files found for this particular path. So let's make sure that our path is correct.
I called this schemas. This should probably be called schema to make sure it matches our name. Now let's just try that command again. And hopefully it should work this time. You can see it properly created our migration which you can see right here is migrating all of our data. And now if we just do drizzle kit migrate that will actually push all of that data to our database. Now it looks like we are getting a bit of an error. I think that's because I stopped my docker. So we can say docker compose up. That should hopefully set all this back up.
There we go. I just stopped it for some reason earlier. And now if we try to run this migrate, it should hopefully work properly. There we go. It looks like that worked. The reason I was getting an error the second time was because I had a different port being run for a different application. So it was causing an error. But now I just closed my old Docker that was running and now I have this all working properly. You can see it properly migrated everything. And now if we go into our application and do the testing, everything should still work exactly the same as before, but it's persisted in a database instead.
So, let's just go to our client to test to make sure everything's working like we expect it to. Let's make sure we save anything that wasn't saved before. There we go. And now, let's go ahead and we'll just do a simple create. Minimize that down. Click send. And you can see it sent on that information. And now we're getting that created that field from our database. Our ID is automatically randomly generated. And that birthday is set to null because we didn't give it a birthday by default. Let's try a get to see if that works.
You can see it's giving us that one single user. If I do a list, it should also return only one user because there's only one that's been created. Let's create another one. So now we have two users in our database. And if we do a list, it's returning both of those users. If I do an update, it should hopefully update our user. Let's give that a quick send. And you can see that worked properly as well. So now if I go back into my list, you can see that that user has been updated. And finally, if I do a delete, we can also remove that user.
And if we go to our list, they've been removed from that list. So everything is now being properly synced with a database, which is exactly what we want. Which means now we can move on to some of the more fun stuff, which is dealing with authentication and also dealing with relationships between different tables. So, let's go ahead and create a user table where we're going to store all of the information for a user. We'll just copy this author table because it's going to be very similar. So, we'll say users. This one is going to be our user table.
We'll call it users. And now, we need to specify all the fields associated with a user. And a user is just someone that can log in and view data in our database. So, they're going to have an email. So, we're going to say that that is going to be a text, which is not null. And also, it must be unique. So, we'll come in here with a uniqueness on that. We also want to pass along a password for the user. So we'll say password, but we don't want to store their password as a plain string.
That's very insecure and definitely something you don't want to do. Instead, we're going to be hashing that password. So we'll call this a password hash. This does not need to be unique. Multiple users can have the same password. So we'll just say it's text that is not null. We're also going to give the users a role. So to create a enum, which is what we're going to use, we're going to come up here with an export conster enum. And we need to set that to pg enum. And we can give it a name. We'll just call this our user roll.
And then we can specify an array of values. So in our case, it's going to be either user or admin. That way we can do different things with different permission levels for our users. So for our role here, we can set it to our user ro enum we just created. We can say it's not null and by default they're going to be a user just like that. So now we have a email, a password hash, a ro, we don't need a birthday, and we have a default created at. That's everything that constitutes what a user looks like in our database.
And there is no relation between an author and a user. So we don't need to modify what our relations looks like. We just need to make sure here we export what our schema looks like. So inside of here, let's export our users. Just like that. So now we have both of those available in our database. So now whenever I type in db.query, I can see I have the author table and the user table that I can query from right now. And that works properly. Now in order to set up our users, I'm going to take our author routes and completely copy them.
So we have our user routes and inside of here we want to get our user routes. Just like that from our users and we'll just say user routes comes from that user file we just created. So now essentially slash users is going to give me my users routes. Now I think I'm going to rename this to off actually. So we'll just say off routes and we'll call this off as well. And finally here we'll call that off. Just like that. So that just kind of cleans up all that. Let's make sure this import works properly.
There we go. And now inside this section, I want to convert everything from this author related stuff to be working with users and authentication. Now in my application, there is no way to get a list of users or anything like that. So we're going to completely remove all of our git methods and instead the only methods we care about is going to be registering a new user or logging in as an existing user. So we're going to have a post for register and we're going to have a post for login. All this get and delete and put and update, we don't need those.
We just need to be able to create and log in as a user and those are both going to be post request. So for this register, let's create a schema for that. We'll call this register schema. There we go. And to register a user, all I need is an email and a password. Let's make sure that we set this to an email. Just like that. And this one down here is just going to be a string. There we go. And we don't want this to be optional. Of course, they obviously need a password. And we can specify, for example, it must be at least eight characters long to give us a minimum value.
We can do the same thing with a login schema as well. The login schema is going to be essentially identical, but we're going to make sure that the password does not have a minimum requirement because if our password requirements change over time, having this be the least restrictive as possible is ideal for what we want. So now we have a login and register schema. We can use that register schema here. And now what we have access to is all of our data for the email and the password. We just need to make sure we properly hash this password, store it in our database, and then later look that data up properly.
Now I'm going to create a brand new folder where we're going to store all of our hashing and o related stuff. So I'm going to say lib just like this. And inside this lib I'm going to create a folder called crypto.ts for all of our cryptological stuff. So we're going to have a async function. This one is going to be called hash password. This is going to take in our password as a string and we're going to convert this to a hashed version. And essentially hashing a password all it does is it takes your password that looks like this as plain text and it converts it to a bunch of random nonsense but it's deterministic.
That means the same password gets converted to the same random nonsense every time and it's a one-way conversion. The reason you do this is if for some reason someone gains access to your database instead of seeing everyone's password as strings like this they instead see this absolute nonsense and there's no way for them to go from this nonsense value to what your actual password is. It's a one-way encryption, which is why it's called a hash because encryption is two-way. You can go encrypt and you can decrypt. But with hashing, you can only go one direction.
So, we're essentially creating an irreversible version of our password. So, people can't figure out what it is if they get access to our database. So, that's what this entire function is doing. And the nice thing is is there's a lot of built-in functionality to help us do this. Specifically, there's a function called script. So, we're going to import script just like this. This is going to come from the node crypto library. And this gives us access to everything we need to do for hashing our passwords. So first we want to create what's called a salt.
So we're going to say salt. This is going to be a completely random bit of data. So we'll call random bytes. Pass it along 16 here to give us some random data. And we'll convert this to a string. And this is going to be a hex based string. Now the reason we use salt with our password is because like I said whenever you convert a convert a password it's a one-way conversion. So password always converts to the same thing. And if I pass in the same exact string, it's always going to convert to the same string on the other end.
By adding in a salt, it's just random data that gets added in at the end of our password. That way, the output data is also somewhat randomized. This is useful because if, for example, I know that converting password always gives me the value 1 2 3 4 5. If I am a hacker and I get access to your encrypted or your hashed passwords and I see the value 1 2 3 4 5, I know that that actually means the user's password is password. I can essentially do a reverse lookup because I know what the one-way conversion is.
By adding in this random bit of data at the end, it now takes this thing that the hacker knows the value of and it changes it to something else that's entirely different. They don't know what the value of this is. So, this is just to help you against what's called a lookup table attack or a dictionary attack or rainbow attack. All these different terms are essentially the same thing. We're just helping make this even more secure than it was before. So, now with this salt and this password, we can create the hashed version. We're going to call await and we're going to call script async just like that.
Pass it in our password and pass it in our salt. And we're just going to create a nice little async version because this script version uses callbacks which are not really ideal. We want to use promises. So we're going to create an async function called script async which takes in our password as a string and a salt that's a string. And essentially, we're just going to return a new promise that is going to wrap what we had before. So, this is going to be our request and our response. And actually, it's supposed to be our resolve and our reject.
Just like that. There we go. And what we can do is we can call that script function. This script function takes in a password. It takes an assault. We're going to pass along the number 64 here. That's how long our key is going to be. Essentially, that just is how long we want our password and encryptions to go for. And then finally, we get a call back. This callback is going to give us an error and it's also going to give us our derived key just like that. Now, if we have an error, we can just reject that error.
So, we'll say reject and pass along the error just like that. Otherwise, if we don't have an error, we can resolve the proper thing. So, we'll just do a quick return right there. Otherwise, we'll call resolve with our derived key. Now, our derived key, if we look at the type here, is this non-shared buffer. We can actually generic this type slightly by saying that it is going to be a buffer. And this buffer is an array buffer just like that. That's the generic type that we want to use specifically for this section. Now I shorthanded this to just res.
So we'll shorthand this right there. And now we have this script function completely written. We have our hashed value right here. And essentially that's all we need to do. So now what I can do is I can return that hash. But I want to make sure the salt is also part of that hash. So I'm going to return my data as salt and I'm going to put in my hash as well. So here's my salt at the very start. And then I'm going to put a colon. And afterwards, that is going to be my hashed password.
And I need to make sure I convert that to a hexadimal string. Just like that. Close this off. And now if I give that a save, essentially what I've done is my password is now storing the salt at the beginning of the password and it's storing the hashed version of that at the very end. So that way when I get my data from my database, I can just split on this colon to get my salt and to get my password data. So let's go ahead and go back into this off section here. The first thing we've done right here is we've tried to insert a brand new user into our database.
Clearly, that's not what we want to do. The first thing we want to do is we want to say is there an existing user in our database. So, we'll say existing is equal to await db dot we want to get a query for the user table. We want to find the very first one and we want to find them where our email is equal to the email that comes in from our data. So, we can come up here and say that the data we want to get from here is our email and our password. So, this is going to figure out do we have an existing user or not?
If existing is equal to null, well, that means that we don't have an existing user. But if it's not equal to null, that means we're trying to register a user that already exists. That's obviously not something we want to allow. So, we're going to throw an error. We'll just say email already in use. And there we go. We can send along a 409 status code for this one as well. A 409 status essentially means that the user's data being passed up is conflicting with certain things that we expect and hopefully they can fix that data themselves.
It's very similar to a standard 400 error you may have seen before. So now that we've made sure that the user doesn't exist, what we can do is we can get the hashed version of their password. So we'll get our password hash by calling that hash password function and passing it in our password just like that. This is an async function, so we'll make sure we await it. And then we can get our user by just calling that same exact db.insert into our user table. We want to insert specifically values. And the values we want to insert are our email and our password hash.
Just like that. That's all the information we want because again this password hash, if we remember, it includes our salt and our password, which is what we need to essentially reverse engineer what our password is. We also want to make sure we return all that user information. But we only care about specific fields. For example, I care about the ID field and the email field. So, we can say user table.mmail. I don't want to pass down the password or anything like that. That would be very insecure. Finally, I can pass my user down. Just like that.
Now, if I just make sure I await this because this is an asynchronous function, that should be everything I need to do to register a user inside of my database. So, let's go ahead and test this out before we do our login section next. So, to do this, we can create a brand new collection. We're going to call this collection off. And inside that collection, let's go ahead and we'll create a brand new request. And this request is going to be for register. We know that this is going to be a post request. And it's going to use that base URL.
And we want to make sure that we go to off/register. Actually, we don't even need slashregister. It should just be slash off, I believe. Actually, I believe it is register. I lied. So, let's just give that a quick save. Now, let's go ahead and set up our body to pass along our JSON to register a user. We know to register a user, we need to pass along an email. And of course, we can use that random email information. So, we'll say random email. And we also need a password. This doesn't really matter what it is.
So, we're just going to set it to password just to hardcode a value just like that. So, now if I give this a save and I click send, we should hopefully be creating a user. But looks like we're getting some type of error. So, let's go ahead and actually take a look what that error looks like. And it looks like here it's failing cuz our database table doesn't exist. That's cuz we need to rep push our database. So, let's create a new migration. And then we're going to migrate all that data into our database. And that should hopefully create that brand new table for us.
Once that's done, we should hopefully now be able to send this request along and actually get a user created. And if we look over here, that does look like it worked. We have a user with a brand new email and we have an ID for that user. So now we have a user created in our database. We can go ahead and we can log that user in, which is the very next step of our application. So what I'm going to do is I'm going to come in here. I'm going to copy this entire post cuz our login is going to be relatively similar.
We're going to call this login. We're going to use our login schema just like that. And of course, what we want to do is we want to get what the user we're trying to login is. So we'll say user just like that. We're going to try to find the first one where their email is equal. And if for some reason our user does not exist. So if our user is equal to null, we'll tell them that hey, for example, this is an invalid email or password. Invalid email or password. There we go. And we're going to send along a 401 status code, which essentially means that you failed to authenticate properly.
Now all the code down here we can essentially remove and instead of hashing our password I want to verify it. So I want to check is my password valid and we can use a function called verify password which is going to take in the password they type in as well as our hashed password from our user and we want to check to make sure that these are the same exact password. So let's go into our crypto function and we're going to create a brand new asynchronous function called verify password. We know this is going to take in a password which is a string and it's going to be taking in a stored hash password which is a string as well.
And all we want to do is we want to compare these two values. So first we need to get our salt and our hash based in hexadimal because that's how we saved it. You remember here our salt came before the colon. Our hexodimal hash came afterwards. So to do that we can take our stored hash and we can split it on the colon symbol. That's going to give us these two halves together. Next, what we can do is we can get our stored value by saying buffer.f from and we can take our hash that is in hex and we convert it away from hex.
So essentially what this little bit of code is doing is it's taking this hexodimal string and converting it to a buffer array which is what we need to be able to compare these two passwords properly. And that's because we're going to be using a function that is called timing safe equal. And this timing safe equal function is going to take in our stored value. This is the one that comes from our database. And then it's also going to take our derived value and compare them together in a way that you cannot use timing attacks against.
Essentially, timing attacks are a kind of fancy attack that depending on how long an inequality takes, you can determine how close your password is to the correct one by using this function that gets rid of that entire vulnerability. So now all we need to do is get our derived password. So we can say const derived is just equal to calling scrypt async and passing along our password as well as that salt. So, what I'm doing is I'm using the exact same salt from the stored database password plus the password my user typed into the form and I'm encrypting that using S-cript.
So, I'm hashing that and I'm comparing it to the already existing hash and I'm saying if those two values are the same, that means they typed in the same password both times. If they're different, that means they typed in different passwords. That's all this function does right here. So, now inside here, we can import that function. And if this returns true, the passwords were correct. But if it returns false, so if it is not valid, then we know that they have an invalid email or password. So we can send along that information. And the reason I'm specifically telling them invalid email or password for both is because if I said up here invalid email instead of invalid email or password, they would know they have an email of a user in our account system.
So that's generally why it's best to always say invalid email or password for these types of things. Now, in order to say what user we are for our different validations in our application, we're going to be using JSON web tokens. And luckily, Hono makes it really easy for us to do that. So, we can use a function called sign. This is coming from Hono. It's an asynchronous function. And this function just allows us to sign a JSON web token. Make sure we import that from hono/jwt. And with this sign function, we can pass a lot along a lot of information.
We're going to pass along an expiration data for how long we want this to work. First of all, we need to pass in what our current time is. So we can say math.4 of datenow / 1000. That's going to convert the current time to the correct format that is expected for this expires time. And then we can just set a default expiration. So we can come up here and say const JWT expiration in seconds. And we can specify how long we want this to be. So if we say like five times 60 that would be five minutes and that would be how long our JSON web token last.
So essentially by coming in here and saying now and we add along what that expiration time is we're saying that this expires 5 minutes in the future. So our JSON web token lasts for five particular minutes. Now the next thing we can do is pass along any data we want. For example we can pass along the subject that is our user ID and we can also pass along other information like the email. So we'll say user email. And now we have access to the user's ID and the email whenever we try to access this JSON web token.
And this is going to give us a token as a particular value. And we can send that token down to the user just like this. And we don't even need a status code associated with that. Now the final thing we need to pass along to this is going to be some type of secret value. So we're going to get this from our environment variable. And we're going to call this JWT secret. Just like that. So let's go into our environment variables. And we're going to create that JSON web token secret. This is going to be a string with a minimum value of one.
And now we need to make sure in our environment variable for our JWT section, we create that JWT secret. Now there's lots of ways you can create this secret. You could just hardcode some random value. But what I like to do to create this secret is just open up a random terminal for node. And I like to type in buffer.fr. That allows me to create a buffer. We're going to use crypto.random. I'm sorry, get random values and I'm passing in a new u int 8 array and then I can specify how long I want that array to be.
So we'll say 16 values. So I've essentially created an array that is 16 values long. I'm converting that to a buffer and then I'm taking that buffer and converting it to a hexadimal based string. If I make sure that I close off all my parenthesis properly and I make sure that I spell u int array properly because this i should be lowercase. There we go. You can see that's giving me back a value. And the reason this value doesn't look perfect is because I messed up my parenthesy replacement. There we go. So now if I put that value there, you can see it just gives me a random string of data.
And this random string of data is nice and secure. And I can use this string of data. And every time I generate this, you can see it's just another random piece of data. You want this to be as random as possible. That's why this is what I like to do to create this random data. Now I have that JSON web token secret. That's what signs my JSON web token. So essentially the way that this function works inside of our off is we create a token, we sign it with this secret value that only we have and then when the user wants to do something, they send that token back to us and we're able to use this secret value to decrypt this code and get access to what the user's ID and email information is.
So let's test to see if this works. Let's come over here with our requestly. Let's just duplicate this one for register. We're going to call this login. Just like that. Our body is going to use a email that we're going to paste in whatever our actual value is. We're going to have a password here. And when we send this along, we should hopefully see right now we're not getting any data. There may have been something weird going on though. So, let me just make sure I resend that. Still not quite working. Let's make sure that we actually give this a proper name of login.
Just like that. Give that a quick save. And also, let's make sure that we pass along the correct information. So, inside of our register, let's just send along a register. And it looks like that's not working as well. I wonder if our server maybe stopped working at some point. So, let's just go ahead and check our server. Looks like there was some errors. Let's just try rerunning that and see if that works properly. Looks like there's no more errors. That's probably because of our environment variable stuff. So, now let's give that a quick send. And you can see that did work properly.
We have an email for our user. We can copy that email, go into our login, and where we have our body for our email. We can paste that email in. Click send on this. And you can see we're generated a brand new token, and that token can then be used to send along with other requests and information associated with it. Now again, to make this process a little bit more automated, I'm going to go into my O variables. I'm going to create a variable called last email. Just like that. We'll give that a quick save.
Then what I want to do, just like I did for my post response request here, inside of my register in a script, I want to make sure that I set my last email equal to my response.json email. Just like that. And this should be in the post response. So let's make sure we're in the post response section. There we go. Just like that. And now if I give that a quick save and I just click send, that should save that email inside that last email variable. And now when I do my login, I can just use that last email variable just like this instead.
And now when I send, you can see I'm getting a token for whatever the last email I used for my user was. Now that we have all this set up, we have a token that we can use anywhere to authenticate as this user without passing along an email and a password. And JSON web tokens are great for short-lived authorization, but they're not great for long-term authorization like APIs because if you have an API key, you want to be able to revoke that API key. And JSON web tokens are not something you can do that with.
So what we want to do is we want to be able to convert…
Transcript truncated. Watch the full video for the complete content.
More from Web Dev Simplified
Get daily recaps from
Web Dev Simplified
AI-powered summaries delivered to your inbox. Save hours every week while staying fully informed.









