skip to content
Home

Local Development of Serverless Functions on Netlify

with Jason Lengstorf

Serverless functions are incredibly powerful, but how do you work on them locally? In this solo stream, Jason will show how Netlify Dev makes it painless!

Transcript

Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.

Jason: Hello, everyone, and welcome to another episode of Learn With Jason. Today on the show, you're dealing with me, just me, no one else. We are going to work on some stuff. So, first and foremost -- oh, nice. Thank you for -- wait. Oh, no, the 404. Where did it go? Hmm, that's not good. We should fix that. So the Party Corgi is 404'ing. We'll figure out what's going on with that. Why don't we just flip right over to some desktops here and figure out what's going on with the Party Corgi Discord. Let's see. There it is. And where's the website? There's the website. Let's find -- oh, no. Did we accidently lose this in the -- here's the code of conduct. Okay. I don't know what happened. We need to get that back on the website. But for anybody who's looking for it, the code of conduct is here. Oh, it got moved. The URL -- okay. So we need to redirect. So the code of conduct is up and live, but we need a redirect for it. I bet we could build one of those. Let's go look real quick. I believe -- here's our redirect. That should actually be working, and it appears that it is not. So we should go figure out what happened. Okay. Well, that's a mystery that we should go and investigate. Also not what we were planning on doing today. What's up, everybody? How are you? Luke, your sub ran out. Well, I mean, you know, there are hackie ways you can boop. I probably shouldn't show you all this, but I'm going to do it anyway. There's a chat command that apparently doesn't work anymore. Fun. We're having a great day here. Looks like my overlay is being weird. I've had internet issues all day, and I'm wondering if there's something upstream or if it is something with -- man, just so many problems today with my API not working the way that I want it to. It should be showing this. Right. So there's that. For some reason, it's not updating in the -- man, just got issues. Just got issues today, everybody. So I don't know why it's showing the wrong thing here. I'll tell you what. How about this. How about we do this today. Before I forget, let's do a quick shout out. We have Rachel with us today from White Coat Captioning. White Coat Captioning is always with us doing live captioning for the episode. That's on the home page at learnwithjason.dev. Six months of Luke. I'll take many more than six months of Luke. And then the live captioning is made possible by our sponsors, Netlify, Fauna, Hasura, Auth0. All kicking in to make this show more accessible to more people. Make sure you check them out. These are all clickable on the homepage. Go and check those pages out. We're also, unfortunately, you hanging out with me today. That's what we are up to. Hey, what's up, Michael? Yeah, so Pachi was scheduled for today. She needed to reschedule last minute. So we are still going to do that episode, but we're not going to be able to do it today. So today I called an audible. I'm doing some solo stuff. Thank you for your understanding there. Yeah, I'm trying to figure out what's going on with my stream. So, now that we've done the things here where we've talked about the captioning, we've talked about the sponsors, let's go figure out why my serverless functions aren't doing what they're supposed to do. Look at this API, right. This API is showing me the right episode information. Local development of serverless functions on Netlify. Great. That's what we want. For some reason, this overlay down at the bottom here is picking up cached data. So I need to figure out why it's picking up cached data so that I can get it to stop it. So let's poke at the Learn With Jason repo. I have my scenes up here. This is what we're looking at now. It's a website, and the way that it gets data is through this hook that says use current episode. That use current episode is hitting a sanity API. Why doesn't it hit my API? Oh, boy. You know what? I thought I was going to have to come up with a project for today. Looks like we had one handed to us by me writing bad code. What a great time, everybody! Great news! We've got broken production issues that we can fix as part of the stream today. Yes! Excellent. So, let's -- yes, the compooper is misbehaving. Really, it's me. I did something wrong.

What are you doing, computer? Why? Why are you like this?

Jason: (Laughter) Oh, what a great turn of events. Okay. So we're going to pull down everything here, make sure I've got all the latest bits. Then I'm going to open this up. What I want to do today is I actually wanted to talk about how we can build serverless functions locally because I feel like serverless functions are one of those things that they're really powerful, they can do a lot, there's a ton of potential with them, but getting started can be a little bit intimidating because it's an unfamiliar setup. If you've never used it before, it can be a little daunting to be like, well, where do I even start? So, what I want to do is jump into these functions. So this one is doing a use boop. That's not what I want. We don't want the boop. We want the current episode. So I want my current episode to load from the Learn With Jason site. I don't know why this isn't doing what I want it to do. This shouldn't be cached. This should be pulling the date from today but it's not. If I go and reload this, maybe it's doing something it shouldn't. Refresh. It's still pulling the wrong thing. So why? Why are you like this? So let's see if we can figure it out. I'm going to open up the Sanity API. Then I'm just going to copy in my query. And my date time. My date needs to be -- what's the format for a date? Is it going to show me? So if I do like 2021-09- -- what's the date today? The 24th. Then I need a capital T to show it's time. Oh, boy. Time in GMT -- no, UTC. 5:38. So we need it to be 5:30. So that would be 17:30 Z. Is that right? Let's find out. Nope, that's not it. Or maybe I just need to sort. Does it need to be earlier? So let's go with, like, 2:00. No? Hmmm. Did I do this wrong? Oh, yeah, it helps if you know how dates work. Building streaming data model with pie torch. So these are all out of order. That's an issue. So in addition to this, we should sort. Is there a sort option? Let's find out. Where, limit, offset. No. So I have to sort it myself, which I assume I'm doing down here. Sorting. Okay. But does it have even the right data in it? Personalization, apps, UI, Twilio request, Reactive state management. This is the wrong date. Like, it's edited in my Sanity instance. So, let's -- oh, wait a minute. No, it's not. No, it's not. It's not edited. It should be hidden, though. Let's see if I can get -- hidden on website. Is there a hidden in website? I have that setting somewhere. So I want it to be hidden. I guess we would want not true. Right? Then if I do that, we shouldn't see -- we don't. Ha, that's the problem. So this is not serverless at all, but it is going to solve this particular problem. You know what I'm going to do? You know what I might do instead, actually? Let's look at the -- if we look at the schedule, this is my serverless functions. Because it's hidden, it skips. So let's refactor this to actually use our serverless function. Instead of doing this sorting like this, we can load the episode by getting the HTTPS. Then we'll get learnwithjason.dev/api/schedule. Then we can get the -- we don't need any of this body. We can just do it as a git, in fact. We can get rid of all this stuff. So that's gone. Get it out of here. Then we can go even further here. Okay. Go to the res JSON. Then I think what we get back is just episodes, right? Then we don't need to get that. So we could just return, like, episode zero because they're already sorted. So this should get way easier. All right. Then we should get our episode.title, episode.slug.current. Is that right? Episode title, episode slug current. And guest name. Hey, hey. All right. So that should actually solve this problem. So let's just give it a shot. What do you think? I'm going to run it locally, so we'll run Netlify Dev to make sure it's doing what I want. See a little behind the curtain here of how I just push directly to production on my site. (Singing) dates in JavaScript. So this is running. If I pull this over to the right browser, we can then go to -- all right. So it's already doing what I want. We can see here that's working. If I go to the monologue scene -- okay. So this is pinned to a certain size. That is what it needs to be. So that's good. That's good. We like it. We're happy. Let me get out of full-screen mode. Let's close this browser. And let's ship this thing. Let's fix the stream on the stream. We're going to fix, use the API instead of direct GraphQL call. Pushing to production. We'll push it live. Then I'm going to just watch this build. Hopefully it opens up in this browser. It, of course, did not. It opened in the other browser. So let's bring this over. We can see this building. This should only take a second. Ship it. I like that alpaca. That's good. So that's live. Let's refresh the scene over here. And? As if nothing was ever wrong, we've refactored that code and we're now using an API. Yeah, yeah, yeah. So this is all dynamic now, right. One of the problems that I have is I spend a lot of time on the admin of this. So I have help. I have Aidan helping me out with a lot of the scheduling stuff and getting things on the site, but there's a lot of asset generation that has to happen. So if you look at the site itself, there are these episode assets with the play button. Then if you go into one of the episodes itself and you take it over to the Twitter thing and you go to automate some stuff, there's that. Then if you go to the schedule, there's this card as well that has to get generated. So there's a lot of pieces that have to get put in for each of these to be generated. I don't want to have to do that every time, and I don't necessarily want anybody to have to do that. It's a template, right? So this uses the API data that's at /api/schedule. There's also /episodes. This will get you the first 50-ish episodes or something. Then you can also, if you want, get an individual episode detail as well. So we can go to episode automating. That'll get you a singular episode. So there's definitely some options there. And this is all -- like, core is enabled. You can use it whenever. If you're trying to use an API for building, this can be a good tool. That's why I opened it up. So people who want to learn can use the Learn With Jason API as a free API with a lot of data. It's got images in it. Those are all CDN hosted. So you can do a lot with it. Go wild. Go do whatever you want. Make some cool projects. Hey, if you use the API, let me know. But I love this. The Twitter cards are using my Cloudinary package. Oh, another fun thing that I did recently to make my life easier is -- oh, not the API part. Let's get rid of this. Check this out. I just built a thing where if you want to make a card, you can just go to poster.jpg on any episode, and it'll make that poster. So we can go in here, and here's Moriel's episode. If I add poster.jpg to the end, it makes a poster. This makes me very happy. Then you can just save it as whatever you want. We'll put it in the downloads. Ba-da-bing, ba-da-boom. Oh, no. It doesn't want to download. Don't you do that to me. So, I got to figure out what happened there. That's too bad. Anyway, like I said, we're fixing it live. We're doing stuff in production. But my whole goal of making this stuff work is to just automate things. The way this works, if you want to see how it happens under the hood, this is the Learn With Jason repo. So you can poke around in here. Yeah, I'm not sure what happened there. I got to go figure out why it's changing the file name because that seems bad. But we will, you know, see what happens. So the way that works right now is I have a function. I have a lot of functions that are built to do a thing here. So we have the episode one, for example. It takes -- hold on. Why don't we show this locally? Then we can mess with some stuff. So I'm going to move into -- let's close down this one, open up this one. We'll open up learnwithjason.dev, pull everything down, make sure I'm using the right version of things. There we go. Okay. All good. Looks like my package JSON changed. Must have done something. I don't know what it was. Let's install. This is running Toast. I don't know if Chris Biscardi is on the chat, but shout out. Oh, the URL of the API. Yeah, if you want the API, it's learnwithjason.dev/api/episodes. You can do schedule as another. If you want to get a particular episode's details, you can add the episode slug. Episode/slug. So those will let you run. You can do quite a bit with that. So we've got this install running here. Let's open this thing up. And inside, I have this folder full of functions. So one of the things is all of the commands in the chat actually run off of serverless functions. If you send an ahem, it runs off of this serverless function. So you send the command, it listens for any command to be fired, and then if there is a command that matches, like this one called "ahem," it will run off this audio and this image for this many seconds. This is kind of a fun thing. If you want to make a sound effect for the show, you can do it as a pull request. So one example of this is Brittany made, I think, Jamstack.

Jamstack.

Jason: There it is. So, she made that and submitted it. Now there's a sound effect for that. So if anybody wants to pull request in sound effects, this is the format. You can do it. But that's not what we wanted to look at. I wanted to look at -- let's look at this /poster and figure out what's going on with it. I think -- how is it working? It should be here somewhere. I think it's episode.

Holy buckets, did that just work?

Jason: (Laughter) Yes. What listens for it? So what listens --

You hackers. You dirty hackers.

Jason: A project I made called Socket Studio. This is not a particularly -- like, this is very much like, hey, did you build a project for yourself and release it to the internet? But I have Socket Studio here. I haven't really put anything into it yet, but the way that it works is it's got this kind of core server. This is a web socket server. It runs on a pretty straightforward -- like, it's an express server. It uses Apollo. It does subscriptions. When it opens up, what it does is it listens for the Twitch API. So Twitch runs on IRC. And so, I use something called tmi.js, which is like a wrapper around Twitch's notification layer. So when I listen, it looks for any connection. So it gets a chat client. This chat client goes into the Twitch API and uses a bot user, which is the Socket Studio. That's what replies in the chat when you see, like -- when you use a command and see Socket Studio put a message in. That's the bot that we're using here. It subscribes to my channel. It's actually configured that you can set up any channel to work with it. Then when it does that, it starts -- it listens for events. So if the ready state is open, it'll say it's open, et cetera. When you create a chat bot, it will add a listener that says -- where is the actual listener? Client on subscription, client on resub, et cetera, et cetera. Each of those is an event that comes from the chat. Then we are able to use those to send off message. So for example, this one does a client on message, it figures out what the message is, and then if it's a command, it will send off the command. To send the message -- where is the actual bit? We parse the command, and when we parse the command, we are able to figure out what that command is. Where does it actually get the -- it pulls out what the emotes are. There's a request that it makes at some point to enhance that with the data from the serverless function that I'm not 100% sure where I put that. But it's in here somewhere. Are you in here? Commands, it's going to be in here. So get commands for channel. It does a request for all of the registered commands. I use Hasura to keep track of them. They just get thrown into a database. This channel has registered this command. That's to prevent somebody from, like -- if somebody was able to get a PR committed but I hadn't added it to Hasura, they wouldn't be able to call it yet. The reason for that is that if, say, you had a bigger channel and you had committers, somebody who had access to your repo and something went wrong, like the relationship sours and they went and did some stuff, you can make it so that only you take a new command live, even if somebody were to put one that's really problematic in and they can get past your code review. It's like a double-verification thing so you don't get yourself into accidental trouble. Once it loads, it sends off a fetch for the command handler. This is the actual function URL. Then what it gets back from that handler -- so it sends off the Twitch data. This is the message that came from chat. This is the command, any arguments that were sent, who sent it, which channel it came from, and then that comes back as data. So we get back the name, message, description, audio, image, and duration. That's what actually gets sent to my seams. My seams then play the effect. So it's a little bit of a round robin, but it makes it way easier for me to deal with new effects and stuff than having to hard code them in or anything like that. It's sort of a set thing. So I want to look at why my poster isn't doing what I want. I have a poster that can be set here, and when I get that poster, it should be creating a poster. So when you request an episode, what is happening here -- let me close this one and we'll use this one instead. So I'm going to run Netlify dev. What we get inside of our poster, or inside of this function, I should say, is -- okay. It's loaded all the episodes. Can you believe I've done 221 episodes of this show? I sure can't. I keep this in just to remind me. Okay. Now we have a local version of the site running here. If I go to /api/episodes, I will hit the local serverless function and get that data. What we're interested in is the episode flow itself. So let's grab here. Okay. And we're doing episode with Node CLIs. If I add a second piece to it, which is poster.jpg, it should send back the poster. So, it does. That's what we want. That's how it works. Then I'm using a rewrite. So this is maybe a moment of truth here. Does it work if I don't have the extra rewrite? No, okay. So something is wrong with the way that I'm delivering this image in general. So let's reverse engineer this a bit. This is what I think is exciting about working with serverless functions locally. We're able to just try this stuff instead of having to deploy it every time. So we've got the ability to fetch. We're using Netlify's on-demand builders, which if if you're not familiar with those, they are basically a way that once you've set up a function, you can cache the output so you don't have to run the output over and over and over again. It's powerful stuff. I use it a lot. It's like the API -- the reason I'm comfortable opening my API is if someone spams this, they'll get the same response back. It's cached so they're not running up my functions usage or anything like that. It's just getting a cache response. So that's what this builder thing does. The way that works is down here. We just wrap the handler with builder. That's all we have to do. Everything else is the same. Then we have the ability to make a request to Hasura. This function is just a helper function that sends off a request. It uses -- you know, Hasura needs an admin secret. We need the URL. We have to send the query and variables. This is just a little housekeeping so I can share it between files. So I request that, pull that in. Then what we're doing here is we're getting the event, and I want the path out of the event. So the path that we get when we are using serverless functions is we get -- let me just go back here. Let's request and look here. So the path that we get is just whatever the URL is. If I change this and say, like, you know, some other thing, then it'll still hit this function because we've got the redirect set up for API episode and whatever. It'll show us what the path is. So what I'm doing then is I am -- let's get into here. I'm breaking up the path on the slashes after removing the /api/episode. So when I hit the regular episode name -- so if I take these out, let's go here. Just go to poster. It hits. What we'll see in the CLI is it does it like this. So we remove the API episode so we end up with a slug and the poster name. So there's the slug. There's the poster name. The poster might be false. Then we split. So once we get down here, if I log what we're getting out, get the slug and the poster. And I can run it again. We end up with automating task with Node CLIs and poster JPEG. If I hit it from api episode, we get the same thing. If I leave out the poster part, it gives me the API response and poster is false. So that's the setup for this function. Are we getting episode data? Are we getting an episode poster? You can request the transcript if you want. That's a slower call, that's why it defaults to false. Then I check to see if it's the starting soon or the poster. If you go with this one, it's like that. Then the starting soon just has different text on it. This is what I actually show in the scenes. So if I switch over to my scenes here, once it loads -- I got to make this faster. Oh, no. I got to figure out what I did there. (Laughter) The inception. So this is generated on the fly. So I can do those scenes. I use Cloud Flare. Sorry, Cloudinary, to do that. So, got the slug and the poster. We can see here it says the poster is there. Then I make a query for the episode that has that slug, and I get the details that I need. Title, slug, description, who the guest is, who the host is, demo, repo, links, et cetera. Then once we get down here, I pull out the guest. Or I pull out the episode details, the guest, and then the thumbnail is -- you actually pull out the guest image asset. We're turning it into a base 64 encoded string because the way that works is we are actually building a Cloudinary URL. This is the part where we get into that. This works through the fetch API, Cloudinary's fetch API. So to use this, I have my -- like, my con stance -- sorry. I'm going to apologize. This is going to get messy. I didn't ever think I was going to show anybody this code. So, apologies. So I set up what the default width is and then I'm multiplying that by different scales. Then I have an aspect ratio with this width and height of 1280 and 720. So then we build out what the URL should look like. So with this one, we're saying the width should be 34-ish percent of the total width. And we want to make that -- the image should fit. So no bars or cropping. We want to set the text color to white. We want to set the gravity to northwest. Then the offset from the left to like 44-ish percent, offset from the top to 32% of the height. Then we use text. We set the text to be my font that I've uploaded to Cloudinary. We round it off to be whatever the percentage of the width divided by the total width is times 18. This is math, but I just tinkered until this looked right. Then I set the line spacing to zero and use the text starting soon. So that is how I get -- this is a lot, right? This is the string of the URL that creates the text "starting soon" when you are looking at this. That is how this gets positioned. It's how it gets sized and how we're determining like what the scale of it should be next to everything else. Hey, what's up, Alex? Good to see you. Thanks for the raid. And then we get to the file name. So if Ben did some guest hosting on the show, so we use a different episode template card. Otherwise, we use this episode card. I can show you what these look like. So, let me get just one of these. We'll get this one, and let's go out here and I'll go to here. I think it's /lwj/episode.jpg. So this is the template. Actually, let me make it a PNG so you can see what the template looks like. If I move this around -- it won't let me move it. If you can see -- if I could move this, you could see this is a hollow space. There's nothing behind this. This is just background color for the browser. Then if I do, like, episode-ben-hong, it's the same template, but we swapped out Ben. So what we're doing is inserting all of this extra stuff in here. This is the text for whether or not it's starting soon. This is the actual poster URL. So we set the width and height to be, like, 1280x720. We want it to fill that space. If it's a little off, it should prop, not leave bars. We want to set the quality to auto, format to auto, and then we fetch. So when we fetch, what we're doing is we are getting the thumbnail, which is the guest avatar, which is the one way base64'd. The reason is to we can put it in another URL without causing issues. Then we've got the width, which we want it to be like 31%. Then it's square, so the height is 31%. You fill that so it's a square crop. Put it up in the northwest. Then we offset it. So northwest is, you know, north and west. Then we offset from the top and left by doing it like this. So we want it 46% off and around 11.5% from the top. We put in our starting soon text. Then we just do all that stuff over and over again. Here's how we place the title, and here's how we place the guest name. All those good things, which then leads us to this kind of image. So if it's a poster, right, each of these automatically generated from these details using these URL structures. If it's the poster, we do the same thing. There's some logic in here to move stuff around a little bit. So if it's starting, then we use a different offset for the text. I think we do that in a couple places. With the starting soon text, if it's not starting, we just leave it empty so it doesn't get included at all. Otherwise, all these details are the same. So you'll notice the guest image, their name, and the size of this doesn't change. The only thing that changes is the top offset of that text between the two posters. That's all. So I'm able to do a little hackie stuff to figure this out. Now, is this easy to read? Absolutely not. Every time I have to change this, I effectively have to rebuild the whole thing. But then what I can do is I get a response by fetching this poster URL, and I turn it into an array buffer. Here's why. I want to send back the actual image -- maybe that's all I need to do, the image.jpg. That can't be right. There's no way that's right. If it's a poster, I want to send back image.jpg. Do I need to send a 200? Is that what's wrong? Let's try a couple things here. I'm going to see if anything changes when I do it this way. Send back a 200. All right. Now, can I save this? A-ha! That was the problem. I was doing a 301. I still don't know why it's sending back as a JPG like that, though. But yeah, that's a lot. So let's see. Relevant question, what's the max URL string length? That's a fair question. Oh, Sean, I can set the file name? Is that a header? Let's look this up. Set file name of download in header. Content disposition? Set the file name of a thing. Header content disposition attachment, file name. Yes. Okay. This is great. So, let's do that. We're going to go with content disposition. We'll set it to attachment. Then we want to make the file name -- oh, you know what I'm going to do? It's going to be great. File name, and the file name is going to be the slug, the episode slug. This is going to be great. I'm going to do episode.slug.current.jpg. It needs to be quoted. And that should do the thing, right? So, I should be able to name this whatever it needs to be named. So let me get out here. Let's try this again. I'm going to -- here's my local host. Oh, wait. No, I don't want to always make it -- okay. So that did what I want. That part is good, but I don't need to make it auto download. So, can I just do no attachment? I'll just do file name. Let's try that again. So, it didn't auto download. And if I go to save image as, it does it. Ha-ha-ha! Yes. Thank you very much. So Ben, this is what we're doing. We're playing with serverless functions. What we've done thus far is we've fixed two bugs on the Learn With Jason site. So this one is a Cloudinary auto-generating function to build posters for websites. As we go through and look at any of the episodes, if I go to episodes here, then I can go into one of the episodes. Let's look at -- I don't know. Let's look at this FlutterFlow one. I can go to /poster.jpg. It's going to build out this poster. Then if I go to save it now, it should save as let's learn FlutterFlow. It does. It still does the .jpeg. Does it need to be image/jpg and not jpeg? What happens if I do that? I'm going to try it. Let's go here. Going to reload. I'm going to save image as. Let's learn FlutterFlow. Save. Wait. If I just get rid of you, let's try that again. Still .jpeg. Why is it doing that? Hmmm. I guess I don't really care, but that does seem odd, doesn't it? Like, it's not going to impact anything. It's my machine? Oh, no. Well, that's always good news. Just broken. Just how things work here. Yeah, no, that's fine. So I don't know why it's doing that and doesn't want to save a thing. Which one is correct? Is one of those correct? Is it supposed to be /jpeg? Look at stack overflow helping me with stuff. You should use image/jpeg. Okay. So that's good to see. So this is correct. Without the "E" is technically incorrect. Although, it will probably work anyway. But I could probably just ask Co-pilot. Please, help. Oh, my god. Set the file extension to .jpg with a header. What are you going to do? Oh, no. Sometimes Co-pilot is actually magic. I was just curious if it was going to do that for me. No, we're not going to do that. But this is cool. I'm excited by this because it really does add -- it just adds so much convenience. So let's ship that. We did the thing. I'm going to review my changes here so we can walk through what they are. What we did in here is we don't need to console log anymore. I don't need that in my business. Hey, speaking of Chris Biscardi, how you doing? 26 months you have been putting up with me. That's fantastic. So this is -- those changes, good, good, good. So down here, we are sending a 200 because originally we were sending a 301. Sending a 301 is a re-direct, which was breaking downloads. It works in the browser, but it breaks downloads. So the 200 is saying this is the image. You don't need to redirect. This is the image. So we can download that properly. Then to set the file name, we are able to use this content disposition and pass in the file name with the current episode slug. So probably could benefit from some air handling here, but I'm not going to do it because I don't want to -- and honestly, this isn't used for anything critical. If it doesn't work, I can always go and kind of deal with one of these on its own. So let's do fix, allow download of images. I don't know how to command enter that. Isn't this thing cool? Check this out. That'll deploy that. I like VS Code. Then if I go out to my site -- oh, got to go to teams to get that. Then we should see the site deploying. We can kind of watch that go. Then Netlify does have a dark mode. Check this out. If you go here and go to Netlify Labs, there are experimental features. We're actually -- like, keep an eye on this tab if you're a heavy Netlify user. We're starting to look at giving early sneak peeks of things in here. This is pretty nice. What this will do is it will put an appearance settings into your user settings. So this doesn't add the toggle. It just gives you the option in your settings that otherwise wouldn't be there. So when we come down here, you get your appearance settings. You can either use your system settings or explicitly set it to light or dark if you vastly prefer one over the other. But yeah, this is really nice. You know what else is cool? Okay, so let's verify this thing worked, and I want to show everybody I'm pretty excited about today. What? Yeah, toast! What didn't you like? Connection reset. So that just seems like something -- let's just retry that. I'll clear the cache and do it again. I'm not familiar with code blocks. That's one I haven't tried. I guess this is the part that I get excited about and why I wanted to show this kind of serverless local debugging process. But you know what's even cooler, if you're going from scratch, this is a new feature. So let me actually make sure I have the latest version of the Netlify CLI. Then I'm going to show you something that just shipped that I'm really excited about. While we're waiting for that to download, I'm going to show you something else that just shipped that I'm really excited about. So the Jamstack conf website went live today, if yo haven't checked it out. So Jamstack Conf is live. The team is -- they're just so good. Our marketing team does such a good job with stuff. We have this cool website, some retro elements. It's very kind of how it started, how it's going. I'm always a fan of anything that features me as a general thing. But here's what I'm really excited about. This time if you register, you can personalize your badge. So I find this so much fun. Now, I already have a ticket number, so I've already done this. I have it over here somewhere. Where did it go? It's here. So check this out. I have a badge that I've personalized. You can then personalize your badge. If you share this on Twitter, which I guess I can just do now. Have you registered for Jamstack Conf? You can get a -- sign up for free today and get your personalized conference badge. We're doing like a nostalgia theme. Like, this part is fun. You get to upload a photo of you as a kid. Look. Look at my -- oh, just so much rage and anger. And this, you could charitably call this a shadow, but what this actually was, was a chin strap goatee. Then I bleached all of my hair and spiked it up. There's a reason I'm bald today. It's because I did dumb stuff like this. (Laughter) Just absolutely abused my hair for my entire childhood, and it just packed up and left. But look, look. I'm wearing a -- this is that material that when it rubs against itself going like swish-swish-swish. It's get all these pockets and stuff on it. I have a pinky ring on, everyone. That's a pinky ring. Then this is like a stretch, ribbed -- like, I don't even know what this thing was. It was made out of Spandex or something. You can't see what I was wearing below this, but I had like JNCO jeans on. There are a lot of problems. Like, so many issues with my fashion choices as a kid. Because I couldn't pick a fashion. I wasn't a metal kid. I wasn't a raver kid. I wasn't a hip-hop kid. I just mashed it all together in a very jarring way that I'm not super proud of. This is the part that I think is really cool. It includes your personalized badge as the preview image. Like, I think this is cool. I love these types of projects. This is something that Phil worked really hard on with Zach Letterman and Justin Schwan from our design team. It's really fun, really fast, kind of pleasant to use. I would highly recommend, first of all, go to the conference. It's going to be great. Even if you're not planning on going to the conference, play with this badge generator. It's just fun. Right? I just love it. Oh, my goodness. Yeah, when the '90s is considered a long time ago. I saw something posted yesterday where somebody was saying it's the same distance in time right now between today and 1980 as it is between 1980 and 1939. Woof. That one hurt me a little bit. So anyway, this is fun. You should go and do this. The late 1900s. (Laughter) Yes, ouch. Oh, my goodness. All right. So now we have our poster.jpg, so we can get that. This is the deployed function. So what should happen now, if I save this image, it's saved as right to left, RTL support. I save. Good. And now I have that JPG. So we're in solid shape. This is great. Awesome. And we were able to do that locally. I didn't have to deploy it and check it. I was able to get it all working locally. Now, check this out. This is even more fun. So if you want to use TypeScript in Netlify, we have this whole thing for functions where -- whoa. What? Hold on a second. What version am I using here? 6.7.1. That should work. Let's npm install Netlify CLI latest. I think 6.7.1 is the latest. I'm worried that was my version of Node doing something weird. So I just switched to Node 16. I mess with my Node environment so much that a lot of times when it's an issue like that, I just worry that I did something silly. So, that's doing what I want. Then we have Netlify functions help. Now we can, like, build functions or create functions, and that's what I wanted to look at. Let's functions create. So Netlify functions create. I don't know. I learned a little bit of TypeScript earlier. I'm going to do TypeScript. We'll get a hello world function. There's a lot of stuff we could do, but boom, there we go. We'll call this one -- yeah, we'll keep hello world. So it found my functions folder, and it created hello world.ts. If I look, I've got helloworld.ts. There's a couple things I think are interesting. The big one is this is, like, TypeScript. Note that I don't have, like -- I don't have a TS config or anything like that. There's no settings for it. So I'm not compiling my TypeScript, but if I go and run this with Netlify Dev, then I will be able to -- once it gets done. Come on, you can do it. It's slow because I have to make like 15 calls to my API to not rate limit it because I'm pulling down transcripts and stuff to build the individual pages. So I think each of those calls takes a little bit of time. Some are dependent on others. There's kind of a stacking problem of calls. Okay. We go out here. Now if I go to Netlify functions hello world -- uh-oh. Hello world is treated as an es module file. Yep, that's -- oh, you know what I did? I should have done this in an empty file. How about we start in a clean project instead. We'll make a new directory called, like, serverless local dev. I'm going to move into it. So the problem that's happening is that I have, like, other functions in here that are using the CJS style. It's like exports.handler. In order for this to work, because it's the ESM, this is reliant on using ES Build, which I can't do on that project without doing some refactoring. So this is just me needing to get into a new project. So let's get init here. Then I'm going to run that same Netlify functions create. Are you going to make me do this? Okay. So let's do Netlify init. And yeah, we'll create deploy site manually. So I'm going to go on here, and we'll call this -- what did I call this? Serverless local dev. Okay. Now that I'm in it, I should be able to run that Netlify functions create. We'll put it in Netlify functions, yep. I'm going to use TypeScript. Good. Like I said, this is a brand new feature. So we haven't really tried it a bunch. It also might be that we need some additional documentation. So let's choose the hello world. We'll use that again. Now I'm going to run Netlify dev. There it is. Okay. And the reason that worked, if I open this up, is that because this is a clean install, the functions are able to run -- did it put this package JSON in here? Oh, it did. That's interesting. So it installed all the stuff that it needed for this to work. That's cool. I didn't know it would do that. So now we have all of these things that we need for this to actually work. I'm curious, if I delete this, and what if I do like an npm init yes -- now I'm curious how this is going to play out. Bear with me while I figure these things out. And I'm going to create, like -- let's create a Netlify.toml. Let's also touch like an index.html. So in my index.html, what I want to do is just create a simple website. We'll say, like, serverless is neat. Right. Then out here we'll do h1. Co-pilot knew my heart. In here what I want to do is set functions. Oh, get out of here. Node bundler to ES Build. This should use ES Build instead of a more traditional web pack approach. That means our serverless functions can be put wherever we want. So I'm also going to specify where the functions directory is. We'll say functions is going to be Netlify functions, right. Then if I come in here, I'm going to create a new one. So we'll say Netlify functions create. Let's see. Lambda package. Yeah, open an issue for that. What we're trying to figure out is how to -- so with Babel, you're taking everything and pulling it in and doing a lot of transpilation and rewrites. With ES Build, it's more of these modules depend on each other. So resolution and that kind of stuff is tricky. So as you hit edge cases, let us know. This is where we want to be. We want to be on the ES Build train because it's so, so, so much faster. We want to be -- ah, okay. So check out what just happened here. So because I had a package JSON already, it installed the function stuff in the top-level package JSON. So my hello world didn't need all that stuff. That's what I would prefer. I would prefer that everything is there. Is there a repo? Yes, it would be Netlify -- let's see. Is it CLI? I should probably do this in one I'm not logged in, in. Do we have an ES Build? So this might be a good place to start it. And if that doesn't work, you could put it in the Netlify CLI. I think our build bot is -- is this one open? This one is open, so you could also put it here. Honestly, open it anywhere, and the team will move it as necessary. If you're hitting those issues, definitely file an issue. If you have ideas on what's wrong, any kind of reproduction is always super helpful so that they can just run it and figure out what's getting misplaced. Typically what we've noticed with the ES Build it you have is that due to the way that common JS modules get written with a lot of dynamic requires and things like that, we'll see that the resolution isn't catching some edge case of dynamic requires. So it's not pulling in a file. Then that file that's not getting pulled in is missing and causes the ES Build result to crash. So typically what we're looking at is trying to find all the different edge cases for ways that people in the Node ecosystem dynamically required files or otherwise resolved where a file should be located so we can figure out how to back that out. But we're getting there. It's getting better every day. So now that we have got this hello world, we can run this. Here we are. We have our hello stranger. I think this one is set so we can get the name in there. If I set a name on this of name equals Jason, right. So I love the simplicity of this. Now we're up and running. Remember, there's no step that I'm going here to, like, build this. I'm just running it locally as TypeScript. I didn't have to set up TypeScript. There's no -- I set this Esbuild bundler, but that's not for the TypeScript. That's so I can do something like -- we'll call this boop. I want to export function. We'll call it async function handler. In this async function, I'm going to return status code of 200 and the body of boop. So now I have this function called boop that I've used. And notice this is ESM, which a lot of serverless function stuff I've written in the past has been using common JS, which is the exports.handler. But because I set it to Esbuild, I can go to boop and we get this boop back. Now I'm using ESM for everything. I would write my site, my front-end code in ESM. I'm writing serverless functions in ESM. There will be some bumps as we get through this kind of Esbuild transition where all the common JS code is moving over, but once we get through that, which will be -- you know, it's powerful. It gets really powerful. For function serving, yes, there is a built file. If you want to see it, you can go kind of look at how that works. But this one, for example, is just pulling in the source. The hello world, I think -- yeah, they get compiled down. I'm not 100% sure. I think this is just the Esbuild artifact. The thing that I like about this is look how much smaller this is than a typical Webpack artifact. It's good. It's much smaller, much faster. You'll see to build this site with the functions that are there so milliseconds, not seconds. Whereas with a Babel Webpack process for serverless functions, it might take five, ten seconds to bundle a bunch of functions. With Esbuild, it takes like 300 milliseconds or something like that. It's just wild how fast it happens. It's not JavaScript anymore technically? Yeah, this is JavaScript. This one is JavaScript. There's no special syntax or anything at all. It's just using ES modules, which is the spec. Then Esbuild will bund that will down to make it runnable. Yeah, this is a really powerful thing. And if you want to write TypeScript, which you can, then this Netlify functions package includes types so you can write TypeScript. This is actually really nice. So check this out. If I want to get, like, event. -- here's all my available pieces. I get my auto complete here. I can go to -- let's see, what's one that's interesting? Let's go to the headers and see what it gives me. That gives me an array, but it's smart enough to know that it is an array. So it'll do like the for each. Actually, I think that's Co-pilot doing stuff. Thanks, Co-pilot. But what I love about this is that it's smart enough to pull these things out. If I get the context here, which is a special Netlify thing that gets attached, then you can also see there's the client context, the function name, the version, whether or not you using Netlify identity, if you're doing logging stuff or you need to check what your memory limit is. There's all sorts of cool stuff here that you can do that I think is really powerful stuff. So I find this to be just kind of a nice flow. If you know TypeScript, if you want to use TypeScript, getting that auto complete and that type ahead, especially if you try to use something that's not set, like if I was down here and I typo and do const body equals event -- first of all, it auto completes for me. But it shows me that I don't -- it doesn't exist. If I get it right, I get -- it's a string. I know it's a string, so I can actually deal with that. So it's pretty handy to be able to do that TypeScript style dev. Again, this all runs locally. So as we're working, if I want to make more changes, I can say, like, I go out to my browser and I want to make a change. I can say hello world. I leave it blank. It says hello corgis. Let's make another change. We can say corgi friends. As soon as I get out here, refresh. So this experience of being able to debug is just so nice. Yeah, Jacob, what you're saying is the functions themselves are being run through AWS Lambda. What we're looking to do is send the lightest weight possible run time. So Lambda has different layers and requirements. It all gets a little esoteric, so I won't try to speak to exactly what our team is doing. But the goal is effectively make it as fast as possible to bundle and deliver the functions that you're writing and to kind of reduce any incidental overhead of what that bundling process would be. Parse time gets down into the microseconds, but we're looking to just kind of make this stuff as non-painful as possible for you by abstracting away all the stuff that you don't need to care about. One of the things that I think that makes serverless daunting is there's this big gap between zero and one. If you're at zero and you hear of serverless functions, you write the code you need. What do I do? You Google it and fiend out you don't just get to write a serverless function. You actually have to figure out how to get a serverless function deployed. Then what the gateway is and then how to line up -- like, how do you proxy requests from your domain to the gateway so that they get to the function and then you've got to do all this stuff around, like -- there's a lot. There's a lot that has to go into it. That's what I think -- it kept me from doing serverless for a long time. That's just as complicated as standing up a server. And I get how a server works. I can deploy a Docker ocean on Digital Ocean. That's easy for me. When I learned about the way that Netlify does it and the way that other companies are starting to kind of mimic that approach of you have a folder and you put functions into that folder and then that results in your code being deployed and it just works, that was when serverless functions started to make sense. That was when I was like, okay, this is better. Now for me to get this thing up on the internet, I just have to write this code. If we look at my website -- let me delete that part because we didn't use that. But if I look at -- like let's look at a simpler one. Let's look at my schedule. When you look at my schedule, we use a builder. We want to make a request to Hasura. So I pull in what the event is to see if you want to limit it to the next ten episodes or something like that. Then I just figure out what the current date it. So we'll show any episode that started like two hours before this moment. We make this request. Okay. All good. What we get back from this request, we just send off a status code of 200, which is http ok. That's what you get when you request most pages. We send off headers. You can get any origin can request this API. It only allows the get request so you can't post or delete this. It allows on the content-type header. And it sends back JSON. Then I just send back whatever comes back from that GraphQL request and wrap it in this builder so it caches after I use it. And that's it. That's the whole app. In doing that, I'm able to get an API up. Now I have an API that's available that lets you pick up my episodes. If I look at a formatted version of this -- let's go here. I look at schedule, and we can see the preview. You can see in this preview, I didn't have to do very much. I just made a request to my API. Now I've got this each episode is set up. It's got all the details I want. This is just kind of a pass through of the way that Hasura -- I think this comes out of Sanity. So I have Sanity plugged into Hasura as a third-party source. But this is allowing me to kind of stand up an API really, really quickly. All I had to do to write that code was know how to write this query to get the data that I wanted and send back these responses. That's a whole API. Now, that is what you do on express or a Node API or Ruby or PHP or whatever. But there's also that boilerplate part. I have this, but I also have to set up the routing. I have to set up the various stand-up and start command and what do we do if it goes down and how do we scale this thing. We got to get it in a container. We have to get that container on a service. All that work becomes -- it's like chores. It's the yak shaving that allows yo uh to write this little bit of code. So what I like about serverless functions is if all I need is this little bed of code, who cares? Just ship it. I don't care at all. I just want to ship it. So, yeah, American2050, we don't have to send an options request first. If I was going to do a post, I do have an example of that somewhere. Where would I find that? Let's go to my account. Let's see how good GitHub's search feature is. I'm going to search for options in this user. Then we're going to look at code. Let's see. Oh, I've said options a lot of times. Can I make it, like, case sensitive? No? No, that's not going to work, is it? No, it's not going to work. Anyway, you can just do a check that's like if the request type is options, then return the headers. Maybe we can find one of these. That's a lie. I know it's there. Maybe. There we go. So let's look at this one. Yeah, so if -- this is how you would do it for a post. If you need to be able to post, you can just check if the method is options and return the right headers and ok. The options request will succeed, and then the post request will just skip over that and do whatever it needs to do. But yeah, this is dope. Okay. So how is everybody feeling? We managed to fill up a whole episode, and we fixed two bugs on the Learn With Jason site. Like, I'm feeling pretty solid about that. What a good time. Let me drop this in the chat for anyone who wants to check out how that sort of thing works. Is this what we built together, Eco? Yeah, it is. Yeah, this is fun. I think at this point, maybe what we should do is look at some more resource. So what if you want to learn more? There is a great course on Explorers that is up and running with serverless functions. This is from Ben. Ben has hosted the show before. He's an excellent teacher. He's a lot of fun. This is a whole -- it's a free course. You don't even have to sign up to watch it. You can just go watch this. It will walk you all the way through getting serverless functions. Get a project initialized, get it deployed. All those good things. It's good, good stuff. Then I also think there's -- what else? What else are good resources for serverless? Probably the biggest one is this Netlify Dev. I know that I work for Netlify, but legit, this has made a huge difference for me in being able to work locally. It just runs a CLI locally, manages your environment variables, and it'll create functions for you. It's just got a bunch of convenient stuff. You can set up your -- like the Netlify Dev is a subset of the Netlify CLI, but I find I use the Netlify CLI and GitHub CLI just every day. For, you know, working on the Learn With Jason site, working on my personal site. Whenever I'm standing up a quick project, I use the GitHub CLI to initialize a repo and get everything set up that way. Then I use the Netlify CLI to set up the project and do local development and deploy it. That workflow, it probably saves me 15 minutes a project, but especially in my role where I'm doing two, usually, new projects every week, it adds up. I spend so, so much time working on my project instead of setting up boilerplate and getting things deployed, with the help of workflows and tools like this. So I think it's a really, really nice setup. Oh, yeah, functions.netlify.com is a really good one. It's got a bunch of examples. Then CSS tricks has a serverless site that I can't remember where it is. So let's Google it. Here it is. Serverless.csstricks.com. If you followed Chris, he's an excellent teacher. He has a good mind for kind of breaking stuff down. So, this is a really wonderful site if you're not 100% convinced on why this is useful. This is a good kind of argument for why it's useful. I mean, there's so many other use cases for it, too. We used it today to set up an API end point and to do the image. So those are two, I think, pretty compelling use cases. Another cool use case is, to the best of my knowledge -- Phil is going to write an article on how this thing works, but the conference here, I'm pretty sure this badge -- so this is using Cloudinary because we just use Cloudinary for everything because it saves us a ton of work. But I'm pretty sure this is all generated with serverless functions as well. So I think this is an on-demand builder so this page is cached. It's rendered by this, so we generate the -- we generate this, this image, and then we generate this text with your name. And then create a URL for it. So this, I think, is saved in Supabase, I think s what we're using in the back end. And you get a unique URL. So again, go play with this. This is fun. This is another interesting application that you can build with serverless functions because we use serverless to write to the database so that we can keep those credentials secure. So there's a lot of really fun things that you can do. It's really powerful. There's a lot of high potential there that you can play with. You know, the tools out there are getting better all the time. You've got AJC Web Dev. Redwood has a lot of cool stuff. Serverless is getting built into Gatsby and a whole bunch of platforms. Like eleventy. It extends your capabilities without requiring you to become an expert in a whole different part of the stack. It allows you to do a huge amount of additional tasks and much more powerful things that would typically feel like you need to get a server and learn all the opsy things that that server entails. So that, I think, is a good reason to dig into this and start playing and see what you can learn. Yeah, Ben is saying there was a good stream on Eleventy with Zach. Did you post that one? That could be fun to share as a resource. Drop a link if you can. While you're doing that, I'm going to do a quick shout out to our captioner. So, we've had Rachel here with us all day from White Coat Captioning. Thank you so much, Rachel. And she's here through the generous support of our sponsors, which are Netlify, Fauna, Hasura, and Auth0, all of whom are kicking in to make this show more accessible to more people, which I very much appreciate. Let's get this link of Ben's stuff up here. So here's another. First look at Eleventy serverless. So, this is definitely me being -- I'm going to say this is like 10% me being a shill for Netlify and 90% me being legit excited about this as something happening in the ecosystem. I am reasonably convinced that the future of the way Jamstack -- not just Jamstack, the future of the way frameworks are going to work, I think, is going to be built around something like this. I don't think that pure static rendering gets the job done. I don't think throwing out all the benefits of static rendering and just going full server is worth it. It's too expensive. It's too hard to scale. So this approach of default to static and have an escape hatch for a server-rendered page or set of pages, which is what Eleventy is doing here, that is the future of this path. I think that's where we're headed. I think we're going to see, you know, the tools like Svelte Kit. We can put ourselves in a position where we only render what we have to and go as far as we need to. On a given page, I might need to rerender that every single time somebody refreshes the page. Great. For that page, that makes sense. But you shouldn't have to rerender every page on your site because one page needs to be dynamic. That's what I don't like about this push back toward all server-side renderering for everything. It feels so wasteful. It's like it makes a headache. I've watched so many teams take their site down over the weekend because they tried to set up clever caching for all the pages that didn't need to be dynamic but they made them dynamic because of that one section. Just don't do that. Do static for everything. Opt into dynamic when it needs to be dynamic. An approach like this with serverless as a way of delivering those few pages that need to be dynamic, it's a good approach. I have a lot of -- I'm very optimistic about the potential there. So, you know, put me down as -- I'm not usually a futurist. Usually I'm excited about what's happening right now. But this one I feel like there's stuff coming. Anyways, how about we -- how about I stop standing on my soap box. Instead, let's maybe tear this one down, yeah? Call this thing cooked and done. So I hope everybody is excited about this. I hope you're all excited to play with some serverless functions and see what is possible. Make sure you check out the schedule. We've got some really, really exciting stuff coming up. We're going to do MongoDB Atlas. This is like databases that are really compatible with serverless. This is I think go to be fun. We're looking at TakeShape, which is a way to pull in -- you know, one of the complaints you hear about the Jamstack is the data spreads out. There's a different service for everything. TakeShape is trying to make that easier. There's an approach that allows you to plug in your third-party services into one global API. This is similar to like OneGraph. A few other services are offering the same thing. We've got Brandon Roberts coming on to talk about reactive state management in Angular using a tool he maintains called NGRX. I have no idea how reactive state management works in general. I'm really excited to learn about that one. We're going to learn about step Zen. We're going to learn about Battlesnake. I still haved no idea what that is. We're going to do machine learning stuff. We're going to build a game with TwilioQuest. Just look at this list. Look at these amazing people. Marie Poulin is going to come on. We're going to talk about Notion. We're going to look at next.js. We're going to look at observability, personalization. There's just good things. So many good things coming and happening. So definitely go and get into this schedule. You can click this button up at the top, add it to Google calendar, and it will become yours. You will be able to see episodes when they're happening. It's not obtrusive. You don't have to put in your email or anything. It just shows up. Is that a stream schedule for February? Sure is. That's because Stephanie just had a baby. So she wanted to wait until she was well out of the maternity leave before she scheduled anything. So congrats to her. I'm super excited for her to come back and teach us some stuff. Yeah, congrats, Stephanie. That's going to be great. So, that, I think, is -- I think that's a good stopping point. Who should we raid today? Anybody have a preference? Let's see who's live. I see Brent is live. Anybody else? Anybody else live? ManniMoki. I've never seen ManniMoki. Are they live now? Let's go do it. This channel is intended for mature audiences? Uh, is that just because they swear? Or what am I getting into here? I don't know. Ben has never steered me wrong. We're doing it. Let's go see ManniMoki. Bye!