API Design in Node.js, v5

Type-Safe Environment Variables

Scott Moss
Netflix
API Design in Node.js, v5

Lesson Description

The "Type-Safe Environment Variables" Lesson is part of the full, API Design in Node.js, v5 course featured in this preview video. Here's what you'd learn in this lesson:

Scott demonstrates using Zod to create schemas that validate environment variables like Node ENV, port, database URL, and JWT secret, emphasizing proper validation to prevent runtime errors.

Preview
Close

Transcript from the "Type-Safe Environment Variables" Lesson

[00:00:00]
>> Scott Moss: So then we'll make a schema using Zod. It's kinda cool how I do this. I still to this day think this is kind of clever and basically what I wanna do is I wanna put the keys here of the environment variables. That I expect to have, so I expect to have no the and V.

[00:00:16]
That I expect to have, so I expect to have no the and V. And then I put the Zod schema here. So in this case I expect Node EV to be an enum. If you don't know what enum means, it just means one of these.

[00:00:30]
If you don't know what enum means, it just means one of these. That's all it means it's gonna be one of these. That's what I know means, it's. It's short for enumeration we're enumerating over a select amount of options.

[00:00:45]
It's short for enumeration we're enumerating over a select amount of options. I think they could have they could have called it something else. I don't know, it's called a enum it's predates all of us, but that's what it's called. I think Node EV, the three values that No EV typically are developments.

[00:01:03]
I think Node EV, the three values that No EV typically are developments. Test And, Production very similar to our app stage, which is why it's misleading that a lot of people use it, so we'll do that, but we'll also set a default to for now, we'll just default to. Oh my god, it's so hard to type those computer Develop. And then I wanna do my a stage, which is pretty much like the same thing, so I'm just gonna copy that, put it down here, change this to a stage.

[00:01:29]
And then I wanna do my a stage, which is pretty much like the same thing, so I'm just gonna copy that, put it down here, change this to a stage. And I think the only thing I do here is I change this to dev and not Deve, and I also just default to dev here. Got a bunch of other environment variables that I expect to have and I'll talk about them as I do them So port right now we hardcoded a port, but when you deploy this, you get assigned a port. You don't get to pick what port you have when you deploy this, so.

[00:01:47]
You don't get to pick what port you have when you deploy this, so. We need to, not hardcode our ports and instead dynamically use them from the environment variables. That's the number one environment rebel you'll use when you deploy a server is a port. You're never gonna be able to.

[00:02:03]
You're never gonna be able to. Deploy anywhere. So what I wanna do here though is I can use this cool thing called coerce because port should be a number, but technically every single environment variable ever is gonna be a string no matter what you put in it because it's just text. It's always gonna be a string, so I can use Zo to coerce it to the type that I want it to be.

[00:02:17]
It's always gonna be a string, so I can use Zo to coerce it to the type that I want it to be. So I'm going to coerce this port. To be a number cause Express expects a number or maybe expresses. OK, it's fine with the string, but you probably wanna pass in a number.

[00:02:32]
OK, it's fine with the string, but you probably wanna pass in a number. Gonna say positive number and I'm gonna default to what we hardcoded, which was 3000. There's some other ones on here like Hosts and things like that. You could do those, we're not really gonna use those, depending on what.

[00:02:49]
You could do those, we're not really gonna use those, depending on what. Provider you have, you might have to put a host or not, I'm just gonna put the ones that I know we're for sure gonna use, that's gonna be database URL. We are for sure going to use that and that's just going to be a string like this and then I'm gonna add some extra validation on it's gonna start with and it's gonna start with this post. Grass QL.

[00:03:03]
Grass QL. Protocol or would this be a protocol or a scheme, I guess. Technically it's a protocol, so if you're looking at this like. What is this?

[00:03:24]
What is this? HTTP and file aren't the only protocols for URLs. Anybody can make any protocol they want for any type of URL. If you've ever done mobile development, every single app on your phone has their own version of this This is how they open up URLs on your phone with deep links and universal links, databases, as we'll get to have their own protocols for their databases using like TDP or.

[00:03:44]
If you've ever done mobile development, every single app on your phone has their own version of this This is how they open up URLs on your phone with deep links and universal links, databases, as we'll get to have their own protocols for their databases using like TDP or. That's no use TCP. I'm sorry. I'm pretty sure databases use TCP unless it's like a service database for Postgres they have their own protocol URL and it's prefixed with posttress QL.

[00:03:58]
I'm pretty sure databases use TCP unless it's like a service database for Postgres they have their own protocol URL and it's prefixed with posttress QL. This is how you know it's a postgress database, So yeah, it's like HDP, but it's not that protocol, it's a different protocol, but they're all URLs. We're gonna use posttress, so whatever stream you pass in for the database he has to start with that otherwise it's gonna break. Is that just like a validation thing?

[00:04:13]
Is that just like a validation thing? Yeah, it's a validation thing. We're gonna write the code that validates this against the environment all the environment variables and then when we start the server up, we're gonna import this file and it's gonna run in validation and if any one of these things fail, we'll get an error and our server will stop. That way we literally cannot run the server without.

[00:04:28]
That way we literally cannot run the server without. The proper environment variables which as someone who's ran into that problem for years, it is a common beginner problem to not set the right environment variables just to find out like the worst part is you set an environment variable. But it was the wrong value and that caused some issues down the line that you don't figure out until much later so this doesn't help you with setting the wrong value but it does At least eliminate a lot of this stuff. Cool, JWBT Set, we're gonna need that, cause we're gonna do some JWBT stuff, This is just a string It has a minimum of like that.

[00:04:41]
Cool, JWBT Set, we're gonna need that, cause we're gonna do some JWBT stuff, This is just a string It has a minimum of like that. And then you can pass a custom error message. I'm gonna say it must be 32 characters long. So that way when error is out, you know, it'll tell you, so you do that.

[00:04:58]
So that way when error is out, you know, it'll tell you, so you do that. JWT. Expires in. Oh my God.

[00:05:16]
Oh my God. There we go. This is just a string It's just like, it's a special syntax that describes like how many hours, days. Whatever, You want this state of tea to expire I'm gonna say 7 days Don't worry if you know what JWTs are, we'll get there, it's just how we're gonna do off.

[00:05:33]
Whatever, You want this state of tea to expire I'm gonna say 7 days Don't worry if you know what JWTs are, we'll get there, it's just how we're gonna do off. The other thing we'll use is. Decrypt rounds. The crypt.

[00:05:52]
The crypt. Rounds, is that something to do with like hashing passwords? We'll talk about this as well. It's gonna be a number, so I want to coerce it.

[00:06:05]
It's gonna be a number, so I want to coerce it. It's a great number. And a minimum of 10 and a max of 20. How did I come up with those numbers?

[00:06:25]
How did I come up with those numbers? I didn't. I actually asked AI want some good. Mein and Mac for decrypt rounds cause I'm not a crypto expert.

[00:06:45]
Mein and Mac for decrypt rounds cause I'm not a crypto expert. Cool. There's some other ones in here, we don't really need them, but I put them in there as an example of different things that we could do. All right, so the last thing we wanna do is, Validate this, right?

[00:07:02]
All right, so the last thing we wanna do is, Validate this, right? So, first thing I wanna do is I wanna make a type, so I'm gonna export this type here. I'm gonna call it ENv and I'm just gonna say Z. Infer.

[00:07:16]
Infer. This is where Zo gets really cool because you can. Create a TypeScript type by. Inferring the type of a Zod schema.

[00:07:33]
Inferring the type of a Zod schema. Which I actually need to do that. So what does that actually mean? Let me, let me walk through this.

[00:07:52]
Let me, let me walk through this. So Z. Infer takes These things are this is this is called like a generic essentially this is the best way I can describe this is like passing in an argument to a type. It's the equivalent of passing an argument to a function but you're passing it into a type because this type takes in.

[00:08:05]
It's the equivalent of passing an argument to a function but you're passing it into a type because this type takes in. A type as an argument and it does something with it. We'll actually make some code. Well, we'll write some code to you that does a similar thing.

[00:08:27]
Well, we'll write some code to you that does a similar thing. This is the advanced stuff of TypeScript that I say I know how to do. The other stuff I don't really know how to do. So this is really cool so.

[00:08:44]
So this is really cool so. I've actually gotten to the point now where I actually almost never even write TypeScript types and TypeScript anymore. I almost always write them in Zod, and I just infer them this way that way I can still get the benefit of having the TypeScript type checking at dev time and build time, but also get the additional benefit of type checking with Zod at runtime with just one schema. So that's something I've been doing a lot lately, but maybe it's just overkill.

[00:08:55]
So that's something I've been doing a lot lately, but maybe it's just overkill. So then I'm gonna say ENV which is gonna be our final object. It's gonna be this type of ENV. And then we need to build up our objects.

[00:09:09]
And then we need to build up our objects. So first I'm gonna, I'm gonna try catch this because if we wanted to throw an error and we wanna like catch that error. Zod's gonna throw error if any of these validations fail So what we wanna do is we just wanna say ENV equals we can run this function called parse. I'm sorry, in these schema parse.

[00:09:22]
I'm sorry, in these schema parse. Like this and then the cool thing is we can just pass in this whole object right here process.ed. That's an object. Like I said, I'm not gonna log it.

[00:09:39]
Like I said, I'm not gonna log it. You can log it if you want if you wanna see what it looks like it's gonna log every environment variable on your computer right now. And it's just a it's just an object, it's a key value object. It looks very similar to this.

[00:09:57]
It looks very similar to this. It's got these keys and then it has not, you know, obviously not these definitions, but it has like values. So we're gonna pass that whole object into here and parse it and it's gonna validate whether or not this is true. If any of that fails, it's gonna land here in this catch and then we only wanna handle this if it failed because of a Zod issue and not some other issue that's going on with your computer So first we say if error is like an instance of a Zod error.

[00:10:16]
If any of that fails, it's gonna land here in this catch and then we only wanna handle this if it failed because of a Zod issue and not some other issue that's going on with your computer So first we say if error is like an instance of a Zod error. So error like that, then we know that like, this was. Validation issues, so then what we can do is we can just you can just log it. You can do whatever you want.

[00:10:28]
You can do whatever you want. I'm gonna log it. I'm gonna say invalid, Eviva, I'm gonna do that and then. What you can do is you can flatten it completely because what I've noticed with the Zod errors that they're heavily nested like array of errors so you can just flatten them, and log it so I'll say console.

[00:10:43]
What you can do is you can flatten it completely because what I've noticed with the Zod errors that they're heavily nested like array of errors so you can just flatten them, and log it so I'll say console. Error and I'll say JSON. This is a cool trick right here. So if you string ay.

[00:11:01]
So if you string ay. An object Or an array. You can, you know, like when you, when you log stuff and notde sometimes if it's more than 3 levels deep, it won't show you the third level. It'll just abbreviate like, oh, here's an array or here's an object.

[00:11:17]
It'll just abbreviate like, oh, here's an array or here's an object. If you stringify it it'll show you all of it. So string ofify it helps you get past heavily nested things, then I'm gonna flatten this array. Actually, that's got to be E.

[00:11:36]
Actually, that's got to be E. And then That's gonna have something called field errors on it. And then null 2 is something you can do in string of 5. This basically means, formatted with spaces of 2, intent of 2.

[00:11:55]
This basically means, formatted with spaces of 2, intent of 2. So it's otherwise it'll just be like printed on one straight line and look like somebody miniified it look bad. Reality you want it to look like. This indented, you know, each property is on a new line, everything's spaced out proportionally, that that's what No too is gonna do inside a string of 5.

[00:12:11]
This indented, you know, each property is on a new line, everything's spaced out proportionally, that that's what No too is gonna do inside a string of 5. OK, so we got that. And then, for each one of these we'll say for each, we'll grab the error. And we'll get the path OK.

[00:12:35]
And we'll get the path OK. Yeah and in case of path that we're not talking foul paths, we're talking. The path that's on this object because this object could have been nested so Zo just given us the path on this object and where like which field actually failed essentially that's what it's telling us so We'll get that path. He thoughts.

[00:12:56]
He thoughts. Have to join those back up if we need to and then we'll just log this console. Log. And then the actual message, so the way it'll read it will say like.

[00:13:09]
And then the actual message, so the way it'll read it will say like. What the error was and then. What message for the error, like for instance we have a custom message here it must be 332 characters long, so the path for this might have been. You know, invalid men length, and then the message will be this.

[00:13:22]
You know, invalid men length, and then the message will be this. That makes sense, so. Message. And then after that, Here's a cool little trick, we could do this thing called pro.exit.

[00:13:37]
And then after that, Here's a cool little trick, we could do this thing called pro.exit. And we can pass it at number one. What does this mean? This is, this is how you kill your server.

[00:13:54]
This is, this is how you kill your server. So remember I said the only way to stop a server is if it throws an error, you close your computer or you stop the terminal by hitting. On a Mac, it's Control C to stop it on Windows. I think it's.

[00:14:10]
I think it's. Might be Alt C. I don't know what it is on Windows, but on Mac it's Control-C to stop it. Well, if you also do process.exit in your code, you will stop it and depending on what number you pass in here, this will tell the terminal.

[00:14:26]
Well, if you also do process.exit in your code, you will stop it and depending on what number you pass in here, this will tell the terminal. Whether or not it closed with an error or not. 1 means it closed with an error, 0 means it closed with no error. So as you process that exit 1, that means this thing died, and then otherwise if this was not a Zod error.

[00:14:46]
So as you process that exit 1, that means this thing died, and then otherwise if this was not a Zod error. Just throw it like you were gonna throw it anyway. Of course. I'll just mention that if anybody's new to Zod or wants to learn more about scheme of Validation Steve Kinney just released a course a full stack type script with Zod course that covers kind of scheme of validation in that case between client and server applications but goes pretty deep into Zo Odd.

[00:15:09]
I'll just mention that if anybody's new to Zod or wants to learn more about scheme of Validation Steve Kinney just released a course a full stack type script with Zod course that covers kind of scheme of validation in that case between client and server applications but goes pretty deep into Zo Odd. I should watch that course. I can learn a lot. The last thing is I like to include these other helpers here which are functions that I might use throughout the app.

[00:15:29]
The last thing is I like to include these other helpers here which are functions that I might use throughout the app. So like it's very similar to what we had at the top, but these are gonna be based off of the ENV once it gets solidified, so now I can say app stage equals production and I'm gonna do the same thing for. It's deaf. Upstage is deaf.

[00:15:44]
Upstage is deaf. Because in our app we'll be doing different things like in testing or there's so many different things that we might do depending on what environment we're in, so I don't wanna have to. Oh wait, did I already have something here? Oh yeah, let me change that to this testing.

[00:15:59]
Oh yeah, let me change that to this testing. There we go. I don't wanna have to write this every single time this is gross because like what if this changes? What if it's something else we want a single source of truth.

[00:16:14]
What if it's something else we want a single source of truth. And then to my standard exports, so ENV export, default, ENV. Cool, so we got that. Let's head over back to our index.

[00:16:34]
Let's head over back to our index. Instead of putting port here, we're gonna import our ENV. And I'm gonna say ENv. Aha, look at that port.

[00:16:53]
Aha, look at that port. It's there. Type check, ready to go. All right, so let me clear out my Let me clear up my .env file and let me try to start this server and yeah, if you, if you change your .env file, you will have to restart your server that won't trigger a rebuild for Dvmode.

[00:17:07]
All right, so let me clear out my Let me clear up my .env file and let me try to start this server and yeah, if you, if you change your .env file, you will have to restart your server that won't trigger a rebuild for Dvmode. So you can see I got rid of everything in my .env file, so I got those two errors. The Database URL is required, so it broke. JDFT secret is required, so it broke, and you can, you can see right there is saying database you're required, JDFT secret required.

[00:17:23]
JDFT secret is required, so it broke, and you can, you can see right there is saying database you're required, JDFT secret required. You can just write stuff for those for now. It doesn't matter. I have a database URL for JTT Secret.

[00:17:43]
I have a database URL for JTT Secret. You can just literally put any 32 length string that you want or you can just. Comment that out here wherever you wanna do it, you can do whatever you want we haven't gotten to it yet. I just wanted to show you that it works.

[00:17:56]
I just wanted to show you that it works. OK, so I got that. Go back to our index. We got our port here.

[00:18:12]
We got our port here. I'm gonna change this to also say. PMV.port change that to Interpolate. There we go , and Oh yeah, I gotta save that file.

[00:18:33]
There we go , and Oh yeah, I gotta save that file. Here we go. Cool, and there we go , I'm back. I got a log here that says no ENV fault present for the current environment, so it is default to the .env and that's just what, This thing is doing right here.

[00:18:53]
I got a log here that says no ENV fault present for the current environment, so it is default to the .env and that's just what, This thing is doing right here. I didn't pass in a name, so it always just default to .env. Did you run NPM install and then Zod. That's it.

[00:19:12]
That's it. And then that will show up in your pack if you ever see. Error saying cannot find a Package and that's a package that you are expected to have been downloaded then just NPM install that package. You you're gonna run into that for the REST of your life.

[00:19:29]
You you're gonna run into that for the REST of your life. I don't think a day goes by where I don't see that issue. Does everybody understand environment variable? When and how did they get set?

[00:19:46]
When and how did they get set? When and how they get set? That's a great question. So when and how do environment variables get set?

[00:20:02]
So when and how do environment variables get set? Locally they can get set in many ways, but the one way that you will most likely set them locally It's through an .env file so we write them into an .env file like this. I think the thing that's magical or like OK well how does something like this in the .env file which is let's just, let's just be honest, it's just a text file, how does this get. Enabled To be used In our code like this, where is that happening?

[00:20:19]
Enabled To be used In our code like this, where is that happening? Yeah, so there's a little magic. And that's what this is doing this load ENV, what it does is. It will synchronously.

[00:20:37]
It will synchronously. Go and look at, let's say in this example on Develop, it will go look at this file, it will read the file, it'll actually read the file, take the contents of this file. Iterate over each one of these variables, and essentially we'll do It'll set them for us, right? So like if it's, it'll take each one of those variables, let's say we have a database, right, so database.

[00:20:55]
So like if it's, it'll take each one of those variables, let's say we have a database, right, so database. I'll take David URL, it'll say. process.env. Davis URLs equals to whatever that was in here and it'll set it for us because that's how we would set it that you can set environment variables yourself and your code by doing that followed by equal sign.

[00:21:12]
Davis URLs equals to whatever that was in here and it'll set it for us because that's how we would set it that you can set environment variables yourself and your code by doing that followed by equal sign. So this package is doing that for us. It's just looping over each one of those and setting it for us synchronously. So that means, and that's important because that means by the time we get to the next line all the environment variables are already set.

[00:21:29]
So that means, and that's important because that means by the time we get to the next line all the environment variables are already set. If it did asynchronously. You might run some code before the environment variables are set and you're screwed, right, so. That's how you do it locally in production.

[00:21:45]
That's how you do it locally in production. You will, you'll see when we deploy it, tomorrow you're gonna wherever we deploy there's gonna be a settings panel and they're gonna have a place where you can add these environment variables. You can put the name and then the value and then you can say, you know, for production or for staging or for whatever app stage you have deployed, you can set those there and. Because when you do that, those will be assigned to your process.

[00:22:04]
Because when you do that, those will be assigned to your process. Ev automatically so therefore we don't need to do this. Because the hosting platform already put them on the process. Ev for us, so when we get down to here.

[00:22:15]
Ev for us, so when we get down to here. And we parse the process at ENV those environment variables that we added in the settings are already on this object. And then this is what loads them into that ENV object that we use in our code so. I want to address one issue that I still don't know how it happened, but for some reason, I had a cached older version of Zod that was working, and then when Someone indicated to me that Zod was not in Pakistan JSON that then went and downloaded the latest version of Zod which has some breaking issues.

[00:22:34]
I want to address one issue that I still don't know how it happened, but for some reason, I had a cached older version of Zod that was working, and then when Someone indicated to me that Zod was not in Pakistan JSON that then went and downloaded the latest version of Zod which has some breaking issues. So how did I have an older version of Zod in my Node modules folder where it didn't exist in myack JSON. Couldn't tell you, but, the fix is make sure you have the latest version of Zod. I have, I'll tell you how you can check what version you have by the way, so.

[00:22:49]
I have, I'll tell you how you can check what version you have by the way, so. Once you install Zod with NPM install Zod, you can do that. That will install this will always install the latest released version. You could always Fix it to a specific version if you know it with the at sign, but let's just say you want to install Zo, that'll install the latest released version.

[00:23:05]
You could always Fix it to a specific version if you know it with the at sign, but let's just say you want to install Zo, that'll install the latest released version. That will put it in your package.json you can see it says 4.0.17, but that might not be the version you actually have It might be now because you just installed it, but it could drift later. The way you can actually determine what version you have is two ways. One, if you go look in your pack.

[00:23:24]
One, if you go look in your pack. Lock and you search for Zod. And actually I'm gonna search for Node. Modules Slash Zod there we go and this is the actual version that it resolved to so it did reserve resolve to that latest version because I literally just installed it but as time goes on, you might come back to this project.

[00:23:35]
Modules Slash Zod there we go and this is the actual version that it resolved to so it did reserve resolve to that latest version because I literally just installed it but as time goes on, you might come back to this project. And you'll see that there is a newer version of Zod that's out, but the version you have is still always gonna be 4.017, no matter how many times you hit NPM install, it'll always be that version because the lock locks it in there. That's one way the other way you can do it is if you just go into your Node Module folder and you go into Zod and then you go to itsackage JSON and then you can see what version is there. So that's the other way you can Determine what version you're rocking so we're on 4.017 which I'm guessing was a broken change from the version that I had cached somewhere that does introduce some different changes.

[00:23:35]
So that's the other way you can Determine what version you're rocking so we're on 4.017 which I'm guessing was a broken change from the version that I had cached somewhere that does introduce some different changes. So here inside of our parsing logic and the ENV this is no errors, is no longer a thing. You can see we get this type script thing. I was informed by some really smart people that it is now issues, just a different name, same.

[00:23:35]
I was informed by some really smart people that it is now issues, just a different name, same. Same stuff, And then this flattened one is deprec which basically means. You could probably use it for now, but if you upgrade it not guaranteed to be there, so. This is their warning.

[00:23:35]
This is their warning. This is how they warn people of breaking changes by deprecating stuff and then type scripts, IDEs, language parsers. Do things like putting a strike through it to let you know that it's deprecated. But deprecated means it's still here but planned to be removed, so you're safe for now if you don't ever update, but if you ever update this will break too.

[00:23:35]
But deprecated means it's still here but planned to be removed, so you're safe for now if you don't ever update, but if you ever update this will break too. Or at least that's what I would assume. I would assume this is still in here, but.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now