Faster GraphQL With GraphCDN
with Max Stoiber
GraphCDN adds a smart caching layer for GraphQL APIs that promises better performance and reliability. In this episode, Max Stoiber will teach us how to use it in our own apps!
Resources & Links
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, we have Max Stoiber. Max, thank you so much for hanging out. How you doing today?
MAX: Thank you so much for having me. I'm really excited to be here. I'm doing fantastic. It is Christmas season. I get to wear my ugly Christmas sweaters. I have my rubber ducky here. I feel 100% prepared.
JASON: Oh! The duck! Ducky twins. Oh, I didn't know you had gotten one of those. That makes me so happy.
MAX: Oh, of course. I had to. It's not like I had much of a choice. They're awesome. It's on my desk all the time.
JASON: I am dropping a link right now. Chat, you, too, can have your own party Corgi rubber duck. That is delightful. Let me -- I just realized I didn't turn on my lights back here. What am I thinking? How can you get into a coding stream without spooky lights in the back?
MAX: Oh, man. Such good lights.
JASON: All right, everyone. So I feel like I've been following your work forever. You've worked on a ton of projects that probably most of us have heard of. Just in case, for folks who haven't heard of you, do you want to give us a bit of a background on yourself and what you've worked on?
MAX: Of course. Thank you for that nice intro. I don't know if that's true. I've certainly done a lot of stuff. Just yesterday, it was a big moment for me. Open source projects that I've created or co-created surpassed a grand total of 100,000 stars on GitHub.
JASON: Oh, congratulations.
MAX: Absolutely mind-blowing to me. I remember way back in 2014, '15 when I started with open source, my first 500 stars felt like an insane achievement. I can't believe that I'm at the point now where we're talking about hundreds of thousands. It's a mind-blowing amount. I'm really humbled by the fact that I got to build things and invent things that so many people enjoy using. And also a lot of people hate using. So it's all upsides and downsides. It's not all good. But a lot of people enjoy using my stuffing and that makes me very happy and grateful to be doing what I'm doing. Sorry. That was a long, rambling non-answer. What I've done is I've worked a lot in the React space. So if you're in the React space, you might have seen React boilerplate back in the day, styled components. You might have seen Keystone. Most recently I worked at Gatsby. I've done a bunch of stuff. That's why I'm here, and I'm also equally a huge fan of you, Jason. Like I said, I'm really, really excited to be here and finally be a part of the Learn With Jason show. Very pumped to be here.
JASON: I'm very happy to have you here. Ben, thank you for the sub. We're at a 16-month streak. That is wonderful. And yeah, the subs mean a lot, y'all. I mean, we're going to talk a little bit about the sponsors later, but the subs also go directly to supporting the show and allowing things like live captioning and bigger and better ridiculous things to happen on the show. So that's very much appreciated. So Max, we talked about your background, but I'm actually really excited about what you're working on now. Before we get into the details of exactly what you're working on, let's set a little bit of context, which is GraphQL has been -- it kind of splashed on to the scene, got a lot of press. Everybody was really, really excited about it. I think we saw the awareness of GraphQL maybe grew a little bit faster than the actual adoption of GraphQL. Thank you for the sub. I like the sub from -- that might be the first time a guest has ever subscribed to the show live.
MAX: I was like, hold on, I got a sub. Why?
JASON: Well, now you have access to that boop emoji. You can bury yourself in boops if you want.
MAX: Finally, finally.
JASON: But, okay. So I feel like GraphQL has been one of those things that a lot of people are aware of. Many people have wanted to use it, have maybe tried it, but aren't using it in production. What do you think is the -- what is your take on how ubiquitous or not GraphQL is?
MAX: That's a great question. I think GraphQL has certainly gained a lot of steam over the years. It's grown a lot, and I think what ultimately -- it's the same thing with my open source project. When people like using something, it grows, right. Because they keep using it. They keep recommending it to their friends. They keep sharing it. They keep wanting everybody else to use it. I think that's definitely happening with GraphQL. The people that are using GraphQL, they're huge fans and are talking about it. That's just a really positive sign. I think in terms of general adoption, lots of big companies use it, but it's still a minor percentage. I think the vast majority of existing APIs in the world are GraphQL based. Sorry, risk based. I think that's going to change. I think over the next two, three, five, ten years, just like GraphQL has been growing for the past five, it's going to keep growing. More and more APIs are going to be GraphQL based, which is a really exciting time. Wait, does that mean I can't be buried in boops? I'm just now reading the chat. What is that?
JASON: I know. I'm having issues.
MAX: What am I even here for? I am leaving. I am leaving.
JASON: (Laughter) I don't know what happened today. But we appear to have a bug in the chat. So it's not -- it's like working to a point. I'm not 100% sure what happened. So I'm going to have to debug that later. We'll see if it's just this view or all of them when we switch to pair programming. So I would agree with your take that GraphQL is still kind of in the minority. You're seeing it in the cases where it is just purely obvious. Like you need a GraphQL API. A great example of this is GitHub. Shopify. Like, these are huge data sets where building out rest end points where you could actually access all the data that you need for unique views is a nightmare. You know, you get into GitHub. You say, okay, I want the repo. If the repo gave you an issues array with just IDs and then you had to send off another request for each of those IDs to load the issues for the repo, but then inside the issues, you also had to go load, you know, individual requests for the IDs of the authors so that you could show their profile pictures, suddenly you're making hundreds or thousands of requests for a single view. That's just not tenable, right. But GitHub does a GraphQL API. Suddenly, you can dig into all that nested data in a single query and it's much more approachable and manageable. Shopify, similarly. You have products, variants, purchases, receipts, customers. All that data is connected but not necessarily in a way that makes sense for a fully normalized rest API. You need that kind of bespoke, like, let me just get whatever data I need for the view I'm creating. It does feel like in a lot of cases that complexity is maybe -- you know, sorry. Let me add some context. The trade-offs of building a rest versus a GraphQL API -- like, for a rest API, if I can set up a route and spit out some JSON from that route, I'm kind of done building my rest API in the strictest sense of the word. GraphQL, it feels like it's a little more of a lift to get started. So where do you think the line will be drawn on what should be GraphQL versus what should be rest? Are you suspecting that it'll be all GraphQL all the time? Or do you think it's going to be a balance?
MAX: That's a great question that I honestly don't have the answer to. I think to me, as soon as I start working with other people and other people have to use the thing that I created, GraphQL starts making immediate sense. They can look at exactly what I've done. They can look at exactly the data, the shape of the data they've gotten, and they don't have to talk to me. They don't have to send a request and hope I always send back the same thing. They know I will because it is GraphQL and because it is specified. To me, that's just -- like, I've worked with REST APIs. I've implemented a few of them. I would always grab GraphQL. Even though there's a slight start-up cost maybe that's also getting lower over time as people are building tooling in the community, it works so much better as soon as you are in a team. Almost anything I've ever done has been in the team, right. So to me, that's the point where it's like you know, I could just return some JSON, but really, do I want someone to have to look at this code to figure out what data I'm going to return? Nah, man. Just make it a GraphQL API. You know what I mean? And I'm sure there's use cases where you have data that just doesn't make sense for GraphQL. I haven't come across any personally, but I'm sure they exist. I'm sure there's use cases where it's like, I have no relations, I only need a list of things. Why do I need GraphQL for this? That doesn't make any sense. I always want the same data. Just give me that god damn data. That's fine. That's cool. I'm sure those use cases exist. The ones that I've had, GraphQL has been incredibly valuable for.
JASON: Yeah, and I'll give you a use case that I have where I built both the GraphQL and REST API and found myself mostly using the REST API. I list my episodes, and they don't really have a lot of relational data. It's just basically a list. Most of the time, I'm like, I just want to use a quick fetch request and I don't want to have to remember how to -- like, I know how to send a GraphQL query with a fetch request, but I can't always remember how to do it. With a REST API, just throw that URL into the fetch and I'm done. So I think you're right, though, that as data becomes more complex, and I think the point of complexity that it needs to reach is pretty small, the trade-off of setting up GraphQL, especially as the tooling is improving. I think when you look at, you know, something like, say, Hasura and how quickly it just gives you a GraphQL thing, or these tools that you can get, open-source tooling. We'll auto generate your full GraphQL API with TypeScript autocomplete for the SDK that we'll give you. All this. There's just so many incredible tools out there that are making this way less intimidating. Then the other thing that I've seen people talking about and watch how quickly and smoothly I do this transition, Max. This is going to be masterful. The other thing I see people complain about with GraphQL is that when you're looking at REST, REST is easy to cache. It is easy to reason about. You get HTTP status codes that correspond with what happened from the request. And with GraphQL, you can't do that as easily because GraphQL, you always send as a post. GraphQL will always send you back a 200. And you have to then parse the response, which means that caching isn't as easy because you can't do URL-based caching anymore. I have a feeling you've got some thoughts on that.
MAX: Absolutely. First of all, I'm just going to answer the question from chat. That is indeed a reindeer -- a ninja riding a reindeer. I don't know if you can see that. Let me move to the other side. That's a ninja riding a reindeer, which is the most Christmassy thing you could wear. What else would you be wearing for Christmas? Come on. You got to be wearing that. Before I answer your specific question, what's interesting I think always is tools tend to break down at transition periods. What I mean by that is, for example, say we make it really easy to build a GraphQL API from scratch. You're using Hasura. You're using one of these tools that gives you an auto generated GraphQL API. That works great to get started. But as you build a team and start scaling, it becomes a little more difficult and you have to work around some of the limitations in the system. On the other end of the spectrum, if you're GitHub, you've built so much tooling around your GraphQL API that hundreds of engineers can work on it day in, day out and it doesn't break, it works, the scheme is consistent. There's a bunch of tooling there to make that work at that scale. You could take that same setup and use it for your Learn With Jason website, and it would be really tedious to work with. That makes no sense. And I think often when people talk about trade-offs in engineering, because everything ultimately is a trade-off, they don't realize they're talking about different contexts. I think GraphQL is great for some contexts and not great for other contexts. You can't just say, oh, yeah, GraphQL is great for everything, but then someone else comes and says, no, I have this one context where it doesn't work so GraphQL is shit. No, but I have this other context and GraphQL is great. That's an unnecessary discussion. You're living in two separate worlds when you're like this person has a use case where it works, this person has a use case where it doesn't. That's fine. The trade-offs just don't fit. I don't know. I think that makes sense. Sorry. I just got to go back to chat for a second. The classic story of the samurai on Rudolph. Of course. Don't you know? It's not Rudolph the red-nosed reindeer. It's Rudolph the red-nosed samurai. That's what we're looking at here. (Laughter)
JASON: The reason they needed reindeer and specifically Rudolph wasn't because of fog. It was because of a smoke bomb.
MAX: Correct. Of course. When the ninja throws a smoke bomb, how are you going to know where everything is?
JASON: No, it does. And you know, it's interesting, too, because you're talking about this in the context of GraphQL, but this is also -- like, you can expand this out to just the web in general. One of the core principles that I talk about a lot is, like, hey, if you have data that doesn't change often, why not pre-render the page so you don't have to think about caching databases at all? Right? In some cases, that absolutely makes sense. An API makes a little bit less sense. You're not going to try to cache an API permanently as static files. That doesn't make sense. But you can if you've got -- you know, on the Learn With Jason site, I cache all the episode listings by pre-rendering them instead of trying to load the data on the client side and cache it and then make sure that people are getting a primed cache instead of hitting the origin. There's just no origin. The data is precompiled there. But as you get into things that are more private, when somebody logs into a dashboard, of course you don't pre-render that. That doesn't make any sense. It's one person's data. You absolutely don't want someone else to see somebody's cached data. Like, there's all sorts of reasons why you wouldn't cache that. That's when you move into different forms of rendering. So yeah, I think this makes sense, and it feels more universal than just GraphQL as well. This is sort of -- you know, it's that pragmatic mind set toward choose the tools that make sense when we're dealing with large, public data sets. Caching, pre-rendering, whatever you want to do to reduce the load on your origin. Great idea. Private data sets? Maybe not. Very, very high-frequency data change sets, maybe not. So this is really fascinating. And you mentioned e-commerce is a common use case. Do you find that, like, when you're setting up GraphCDN on one of these things, is it -- are people only using GraphCDN for a portion? Have you kind of set it up to handle, oh, we won't actually cache this -- we won't cache your cart. So you're kind of passing through when it makes sense. You're letting people make those decisions in your platform versus having to partially onboard.
MAX: Exactly. We give people fine-grain control. Essentially, they let you say if the query that we're looking at right now that's going through the gateway contains the type, product, then cache it for ten minutes. If the query that comes through the gateway that comes through contains user or shopping cart, cache it specifically to that user. So you have very fine-grain control. I'm passing all my traffic through, but I'm only interested in caching these specific queries or types or fields. And you have this fine-grain control where you can say, okay, for this use case, it makes total sense. This use case, private data, we're going to cache it per user. For this use case, it doesn't make sense at all. We'll just pass it through. I think the interesting thing about GraphCDN is we do more than just caching. We provide people with analytics and performance monitoring and error tracking. People pass all their traffic through us because they want to see those feature, even when they're not using the caching for everything. Even if they're only using it for a portion. But we absolutely give people that control because they need it. Almost no API is just public or just private or just read heavy or just write heavy. It is almost never that black and white. So people really need that level of control to be able to use this at all. Otherwise, they'd just be stuck.
JASON: Yeah, absolutely. Well, cool. So I feel like at this point, it probably makes sense to start looking at how to use this. I feel like we've got a good philosophical foundation of the trade-offs. So let me switch us over into pair programming mode, at which point we'll find out --
MAX: Chat, please try if the boop thing works. I feel sad that I haven't been showered in boops yet.
JASON: I'm very sad. Who knew that things would just break when I haven't touched them. I'm imagining that I probably let something, like a token, expire or something. But so let's do a quick shout out to our live captioning. We've got Rachel here from White Coat Captioning taking all these down for us. So if you want to see those, they're on the homepage of the site. Like they are every week. Thank you so much for that. That is made possible through the support of our sponsors. We've got Netlify, Fauna, and Auth0 all kicking in to make this show more accessible to more people, which I very much appreciate. Oh, boy. Lots and lots of boops are happening. None of them are falling down. No! Okay.
MAX: At least they're in chat.
JASON: They're in the chat. Yeah. So, okay. We're also talking to Max today. Make sure you go and talk to Max on Twitter. You can give him a follow. We're talking about GraphCDN specifically today. At that point, I am now just -- Max, what should I do first if we want to try this thing out?
MAX: First of all, I will say there's one more block we have to do before we jump in. If you don't have one of these things yet, go to the Learn With Jason shop. Freaking buy one because they're freaking amazing. Just a plug for you. Get one of these. They're amazing. Jason is going to post the link in chat, I'm sure.
JASON: I just dropped a link in chat. Yes, you can absolutely go get one of these rainbow Corgi ducks. I think I just have a bug like in general on my site today. And I don't know what's going on. Okay. We're going to debug that a different day. Something is up.
MAX: Something is really broken. That's weird.
JASON: Something is very slow, it feels like. I don't know exactly what's going on.
MAX: I feel like the boops are a big part of the stream.
JASON: Yeah, I'm not 100% sure what's going on. I'm getting 502s from my API. That's always nice. Unexpected --
JASON: Cool. Okay. Well, we'll dig into that after the stream. Figure out what's going on. Is my whole thing down? No, that works. I don't know. I don't know what's going on, y'all. I'm going to guess that I did something silly and have -- just need to go do a quick investigation of my functions error logs that I'm not going to do right now. (Laughter) But yeah, so let's see here. We are going to dig into GraphCDN. To do that, we're going to need a GraphQL API, which you have set up for us. Let me grab this.
MAX: So I set up a very simple GraphQL API based on a template. Go to that repo. It's a great template. I took that and extended it ever so slightly. Go star it on GitHub. I don't know if he's watching. Probably not. But hopefully he gets some stars from this. So what I would recommend we do is just create a simple website for it. It's a list of projects. Let's just try and render it so you can create new ones and maybe update an existing one. Then let's try and go from there. The whole thing is deployed on Netlify, of course. So there's a Netlify app we can visit afterwards where everybody can play around with the API. Then hopefully we can see the impact of caching and invalidation and everything else.
JASON: Great. Okay. So what I have done, just so everyone who wasn't following my terminal input can see, is I use a format on my computer where I keep everything organized by GitHub style.
MAX: I love that. I do the exact same thing. It is so major.
JASON: It makes my life so much easier.
MAX: Highly recommend it.
JASON: So I have cloned the serverless GraphQL Airtable extended repo. I just made sure I was using --
MAX: I mean, what a gorgeous name. Easy to remember. Easy to type. Super great name. Fantastic. (Laughter)
JASON: (Laughter) Okay. So now we have in here a serverless function. Netlify is configured to read that serverless function out of here. So we've got our Apollo server. We have some type defs. We're pulling in the details about getting projects. So let's see if I can infer, based on what's in this file -- and this is, I think, where the power of GraphQL becomes apparent. Chat, I would encourage you to follow along as well. See how much of this just make sense by reading it. And how much you know about this API just by looking at the definition here. So we have a type of project, okay. I'm assuming this is an API about projects. For each project, I have an ID, a name, a description, and a date. Good. We have an input, okay. So this is an API where I can add projects. So we can add a project, name, description, date. We can update a project. Again, ID, name, description, date. Then we have queries down here, which will let me get all of my projects, and that's an array of projects, a project with an ID, so I can search for an individual project, and that'll give me a single project, which is why it's not in brackets. Then we have a mutation to create a project or update a project, both of which use these inputs that correspond and return the project that was affected. Did I get it right?
MAX: 100%. And I just now realized there's an inconsistency in there that's making me nervous all right. The mutations called create project, but I called the input at project. Ugh!
JASON: It's all right. It's all right. So then in resolvers, we have the query projects, which maps to query projects, runs a function called get all projects, which I'm assuming returns an array of projects. Project itself returns a get project of args. I'm assuming the args are going to look like ID and whatever in an object. In a mutation, we feed in the fields from the project input. Create, same thing. We feed in the args. And these all map to utilities that do Airtable stuff, which we're not going to get into today but that we just know will work. But I do notice I have some keys I need to get. Now, you have sent those to me. I'm going to grab them out of --
MAX: In the meantime, you're absolutely right. If you want to use Airtable as a GraphQL back end, check out BaseQL. It gives you a GraphQL API for any Airtable base. I the reason I didn't use that here is because we're going to look at the invalidation later. I want to make sure we have a place to add that code. So I need to write a GraphQL API myself. But really cool. If you actually want to do this BaseQL, highly recommend it.
JASON: Yeah, yeah. Let's pull up BaseQL, actually, so we can show everybody this link. That is going to be easier, but we want to play around and break it, right?
MAX: Exactly, exactly. We could not break this. We want to break it a little bit. Got to make sure we can do that.
JASON: So I've added my environment variables. I'm going to use npm install to get everything we need. We have a few dependencies here. Airtable, Apollo server Lambda, why do we need that?
MAX: I honestly don't know. Maybe that --
JASON: Oh, that's what we need. Yeah, that makes sense. It's going to pick up our functions. So if I go to Netlify functions GraphQL. You turned on the playground. Thanks for that.
MAX: Of course, of course.
JASON: So now we can run a query. I'm going to prefix this with query and put a couple of these in here. So let's load all projects. This is my favorite. I'm going to hit control, space. It shows me everything I can get. I can grab these right out of here. I'm going to get the name. We'll get the description. And let's get the date. Then I'm going to run it.
MAX: Now, thankfully I've already put some things in there. Otherwise, this would be an empty list. I've got stuff in my Airtable.
JASON: This is wonderful. Then we can show one of the more powerful features of GraphQL. If we don't need the date, for example, we just drop it, and we don't get that data. This is really important when you start thinking about apps that do serverside rendering or rehydration or anything like that. If you're using, for example, Astro Remix, Next, et cetera, it doesn't just know what data it's using and stick that into the page. It actually stores the responses of queries as JSON that it then ships to the browser. So you want to make sure you're not overfetching because if you query for data you're not using, it'll get sent to the browser, even if you don't use it. This is actually kind of the unfortunate and slightly funny cause of that viewing sources hacking breach. There was a website here in the U.S. where the government side had queried everything about people, including social security numbers, like stuff you definitely shouldn't be querying. They weren't using it in the output on the page, but because trade queried it, it got stuck into that JSON file so somebody could get into the JSON and pull all this user data, which is why it's so important that you pare down data. With REST, you have to run a map and remove all the fields that you're not using or some other transformation on the data. With GraphQL, you just remove the field. You only want the name, we got you. Look at this. You want a list of names? Oh, my god, I can't type though. Look, just a list of names. That's all we're sending to the browser now.
MAX: I think the interesting thing is it really starts becoming powerful once you have relations in there. This simple API doesn't have any relations. It's just a list of projects. You can imagine you have project which have authors, which have their own list of projects and maybe they have teams and organizations that manage those projects. So you have this super deep relation thing. You don't always want all of the relations, but sometimes you want some of them, right. You probably want to show every project's name and its author, right? When you go to the author's page, you want to show the author. As your use cases become more complex, your GraphQL API becomes ever more important. You don't have to fetch all the data always at once, even though you don't need it. You can just get exactly what you need. That's it. That's really, really nice. Not just for security but also performance and general developer productivity. It's just nice.
JASON: Yeah, it's really, really nice. Let me add -- so what I'm going to do is add some query variables here. Actually, I don't need to do variables yet. Let's not get ahead of ourselves. I'm going to drop it in as a string. I'm going to take this one, move over here. Here's a cool thing. When I try to do this, it gives me an option, which is why I added these names. Now I'm going to load project by ID. There we go. Okay. Now I want to add something, and I'm going to say add new project. In here we can create project. There we go. Create project. That takes a project. Inside here, I need a name. I have to actually fill these out first, don't I? I need a description. And I need a date.
MAX: While you're doing this, I want to very quickly call out, I know that you've shown GraphQL on the stream multiple times before. Jason's using this API without having ever seen it before. Like, literally. He's never used this API before. He's never queried for projects, never created one, never done any of this before. He just goes into the playground, presses control, space, sees what's there, and goes and does it. I think that is an incredible, incredible thing about GraphQL that I absolutely love and think that's really underappreciated a little bit. I know people appreciate it, but it's awesome. That's just great. You can just go in and do this. Everything is just going to work. It's going to tell you exactly what you need to do. Just makes me happy to see that.
JASON: It really is nice. Then we can get back the ID and the name. So I didn't create an ID. This should theoretically do that for us. Okay. Now we have a new ID. If I load all projects, there it is.
MAX: There you go.
JASON: There's our new project. So just to reinforce what Max just said, I don't know what the Airtable looks like. I've never seen it before. I've never looked at this GraphQL API before. Because of the autocomplete and the schema, that was enough for me to know what was possible and to just do it. I think that self-documenting nature -- and here's the other really, really important thing. The documentation is the code. So this isn't a case where they can drift out of place. I start and I have this beautifully documented API, and then oh, no, something is on fire. I fixed it, didn't have time to update the docs. Now it's inconsistent. Like, I have to update the docs as part of fixing the API because of the way GraphQL is designed. And that's a really, really powerful way to look at this.
MAX: 100%. It just makes me happy to see you do this. And an important callout here is this only works because GraphQL is a specification. Someone sat down and very clearly defined what it means for an API to be a GraphQL API. That might sound like a minor point, but that's really important because that means you can build a GraphQL playground once and it works for any GraphQL API, no matter what you create, no matter which one you use. It's just going to work. That's one of the most important and most powerful things. But let's go and create a website for this incredible GraphQL API that we've now been talking about for ten minutes that doesn't even do that much.
JASON: What? What's going on here? How dare you. All my -- Emmet, why? No, I'm going to have to figure out how to write a doc type from scratch. Okay. So we're going to go doc type HTML. I'm going to get some of this wrong.
MAX: I'm 100% going to get all of this wrong. I don't know.
JASON: What else do we need in here? We need a head tag. We need a body tag.
MAX: Yeah, got to have the body. Got to have the head.
JASON: I think we might just have to let this be a little bit wrong. I need a meta char set. TF8? How wrong is this? I need a content view port. I'm going to skip it. I'm going to just say -- let's just set that up. Then I'm going to stop and restart the server so it picks up that local file for us. Then when I come back out here, we have that.
JASON: Oh, it things it's NunJucks. That's why it didn't work. Let's try this one more time. A-ha! Good catch, chat.
MAX: This is amazing. You can just stream yourself coding and people will fix your code for you. How good is that?
JASON: The ultimate hack.
MAX: Jason, here's what you got to do. Ah, man. Just someone sitting in the back being like, Max, you got to do this instead. This is wrong.
JASON: And it's nice it's real people in the chat and not just my inner monologue telling me I'm wrong. (Laughter)
MAX: We appreciate you, chat.
JASON: Yeah, megapair programming, mob programming, all of those things. Absolutely. Get out, Co-Pilot. Get out. We don't need you. We got the community. So I'm going to set up a quick function to load all projects. That's going to -- let's get a response. That's going to be await fetch. And we're going to get our own API. We'll go to Netlify functions GraphQL. I'm going to send this, because this is a GraphQL post. We always have to send GraphQL as post. I guess we don't have to, but that's the convention. I don't need any headers, right?
MAX: You will need a header to set the content type. That's one of the most common mistakes people make. In our engineering interview, we ask people to build something with GraphQL. That's the most common thing that people miss.
JASON: And can you remind me how this works? You add accept.
MAX: It's accept application/JSON.
JASON: Not my own name. What am I doing?
MAX: Then it's content-type. That's application/JSON as well. This tells both the browser -- that tells the server that the browser is sending JSON and wants JSON back. That's essentially what this is telling the server, which is really important. Otherwise, your GraphQL server will try to load your query data from somewhere completely different because it doesn't realize it's JSON. Super important thing that people often miss.
JASON: Now we're going to show maybe my third favorite thing about GraphQL, which is that we ran this and we made sure that it works. Now I just get to copy/paste it into my app.
JASON: So that will give us our projects. Now that we have this, I can do the most bare bones logging.
MAX: At least you're not using log for chain, you know what I mean? You're more secure than most Java apps out there. Sorry, that was a low jab. It could happen to you just as badly.
JASON: (Laughter) We can -- how do we want to do this? For now, let's console log it.
MAX: Let's make sure it works.
JASON: Then we call it. Which I named this, load all projects. All right. So let's start there. We'll head back to our homepage. I'm going to get the console open. I'm going to stick it to this side because most of what we're going to do today is actually going to be in the console. So here we go. We've got our list of projects. Let's get these on the screen. I'm going to simplify this data a bit by return -- well, let's just get projects out. So we can destructure a little bit, drop our projects in, reload. Nope, screwed it up. Oh, data. All right. So now this -- here's where -- so now we actually have an array. And this is not my favorite thing that I've ever written, but I'm going to leave it alone. It does the job, and this is not going to be production code. Now that we have our project --
MAX: Wait, this is not production code? Jason, what do you mean? I thought we were building a business here. Like, what are you talking about? My hackie GraphQL API Airtable thing isn't production code? What do you mean?
JASON: (Laughter) Okay. So let's see. List of projects. Then I'm going to put together an unordered list. For ease of use, we're going to give this a class of projects. So then down here, what I can do is I can get our project list will be document.query selector projects. Then I can --
MAX: I can't remember the last time I did all this stuff manually.
JASON: I know. It's been a while, right? Then we can do a for each project and inside of here, we're going to -- we'll create a new item. That is going to be a document, create element list item. Then we can go item.inner -- wait, inner text? Yeah. Equals project.name. Maybe we just start there to make sure this works.
JASON: Then we'll projectlist.appendchild item. What happens at the end of it that is we get a list. Hey! Look at it go.
MAX: Woop! It works.
JASON: So now we've got that. So the other thing I want to be able to do is actually create -- I don't need this to be a section, do I? I can just make it a form. So let's make it a form. And what we're going to do with our form is we'll create a label, and for this, we need something to take in the name, description, and date. So we can do name. Then we'll get an input, ID, name, name of name. It will work with just the name? It's not going to fail?
MAX: No, no. We can start with just name.
JASON: Type, text. Then we'll do a button of type, submit. We'll say save project. Maybe what we should do here -- that's why I was going to do a section. So I could add an H2 that says add a new project. We'll just move all of this.
JASON: Okay. Here we can add a new project. In that project, we're going to include a name. So we're going to have -- we'll call it add form. Down here we'll create a new function. I don't like how we're doing that. I'm going to do it all differently. Let's collapse this one. I'm going to put this as another async function. We'll call this create project. Boy, this naming inconsistency is really.
MAX: I know, man. It's getting to me. Ah!
JASON: So we'll create a project. That one is going to accept an event because we're going to attach it to the form event submit. So the first thing that we're going to do is event.prevent default. Then we're going to use my favorite browser API I've learned about recently, which is the form data API from event.target. Now we can get the name from data.get name. Then what we can do is we want to create a project. So we will actually take a lot of the code we wrote in here, which we can more than likely abstract out a little bit. But I'm not going to do it right now. And we will -- instead of the load all projects, we'll go back over here and get the create project. Put that in here. So now we've got the ability to create a new project, but I want to do it by variable. So now it makes sense to introduce variables. And I'm going to include name, and we'll just leave these other ones out for now. That should give us back our name and the variable that we need to send in is name which we pulled out of the form response. Then we check if the response is okay. And we'll console.log -- if I can spell it right -- response.json. This'll show us whether or not our submission worked.
MAX: Let us refresh the screen. Let's just do a window.location or something. I don't actually know. Do we know the location?
JASON: Should work, I think.
MAX: Man, my brain.
JASON: Then down here, we also want to add just a -- okay. We'll just add a little delineation. Setup versus execution. So we'll do a document.query selector, and that was create project.
MAX: I think add form.
JASON: Create form.
MAX: Perfect, perfect. By the way, I should say I don't know how many of you -- actually, I'm curious. If you're in the chat, write in there a yes or a 1 if you are a coder working at a company. Write in there a zero if you're a student or just learning coding or if you're new to it. I'm curious what the spread is. I say that because if you're a student looking at this -- because we're joking this is not production code. This doesn't look too different from what people write. Do you know what I mean? This is very similar to what you would see when you go work at a company. Maybe a little bit more different. But not that much.
JASON: No, that's fair. That's fair, yeah. We got a lot of ones coming in, in the chat.
MAX: That's awesome. So then you all know this is not that different from the code you write. Hopefully a little, but not too much.
JASON: Got a couple students as well. Welcome. And a professor. Hey, Cynthia. Thank you for joining. So let's try out web-based submission. Let's see if we got this right on the first try. Oh! Oh, chat. Let this go down in history that this might be the first time, I'm not lying, when I say it worked on the first try. (Laughter)
MAX: That is in credible.
JASON: Oh, my goodness.
MAX: I've never been able to do that. Incredible, incredible.
JASON: I'm very happy that just worked on the first try. Holy -- holy buckets, everyone. But here's the thing. We just did a bunch of stuff, and we also created a potential problem for ourselves because, note, this is fine, right? It's not the worst thing in the world, but it does take a certain amount of time to make this happen. So let's go to the network tab here. Let's just look at what happens. This takes -- let's make it a little bit bigger. 400 milliseconds to execute. We have to go to the function, which then has to go to Airtable, which then has to come back, get processed, and return data. If I had to guess, I would say that probably the bulk of that is going to be the HTTP request of getting over to Airtable, getting that data back, which is not a problem. Like, this is still plenty fast. This isn't like, oh, no, I'm in danger. As we get more and more data and as the data that we have gets more and more complex, it actually might start becoming an issue. Right? So what should we -- like, this is where I feel like I'd start looking at GraphQL or GraphCDN. How do we use it to improve this experience?
MAX: Let's do one more thing, Jason. Before we dive into GraphCDN, I want to make sure we do one more thing. Let's also update the project name. Let's add a way to update a project name. Let's use that in another form somewhere. It can be super hackie. But let's make sure we got that. I want to show you something afterwards when we're in GraphCDN that's important.
JASON: So I'm going to only fetch the ID and the name. What I'm going to do is I'm going to item, add -- oh, crap. What is it? Attribute. What are the data attributes? Set attribute?
MAX: I don't actually --
JASON: You know what, I'm going to do it like this. I'm going to set the ID to project ID so we can pull it in and grab it when we click on the update.
MAX: Ah, of course. Now I know where you are.
JASON: To update this, we need to -- what's going to be the fastest way to do this?
MAX: Maybe we just update the title to something hard coded?
JASON: Yeah, let's do that. So we'll add -- let's do it like this. We have load all projects. For each of these, we can --
MAX: While Jason is thinking about this, I hope the coding interview went well. Good luck on that job application. Those are freaking scary.
JASON: So I'm going to create a button, and I'm going to update link on -- or at event listener. We'll make it a click. In here, we're just going to hard code this. We'll have -- we won't even need the event. No, we won't need the event because we got it here. We're defining it here. So we'll be able to say take this response. Oh, boy. We're copy/pasting a lot of boilerplate for this. But it's going to be okay. We're going to take this code, put it in here, and what that code is going to allow us to do is --
MAX: You know what, we could even make it a little more real world. We could call that button redact.
JASON: Ah, yeah. That works.
MAX: Then it's not just a random button that does something. You're redacting things. Totally what we planned at the beginning of this stream. That's exactly how this website was supposed to work from the get go.
JASON: (Laughter). All right. So our update link is going to be a button, which means I should probably call it a button. So our update button is going to say redact title. When you click it, it will send off a request to update project. We don't need -- we do need an ID. So the ID for the project to do that is over here somewhere. I'm going to just use the -- this is how I develop in GraphQL. Come out here, type in mutation. We're going to say update project. I know we need an ID, so I'm going to put that in. Then down here, I'm going to start typing -- what was it? Update project. Then that needs project. And that is an ID, is that right?
MAX: That is actually an object that contains both the ID but also the properties you want to update.
JASON: Ah, got it. So in this case, we're going to do one of these.
JASON: Then what we'll get back is going to be the ID and the name.
JASON: Okay. So that will work. I'm not going to test it here because I want to actually see this happen. I'm going to drop this in. Oh, I screwed that up. Let me get this out of here.
MAX: I think there's another thing at the top that's maybe too much.
JASON: Oh, what did I do? I made a whole mess in here. Mutation, one, three. One extra, okay. Let's just clean that up a bit. So let's add in the ID, which will be project.ID. Because we have access to that out here, we don't need to pass it in or anything like that. We could, but we're not going to bother. This is faster. And we're trying to prove something, not write the world's best front-end code. When we refresh, I forgot to append it. So we're going to item.append child and add our button.
MAX: This append child business reminds me that I once wrote -- oh, yeah! Let's see if it works before I tell my story.
JASON: So --
JASON: Two in one show! First try, chat! This has got to be a record.
MAX: Jason's gold. That is my takeaway from this. That is my takeaway from this. I can't believe that worked first try two times in a row.
JASON: Here's the thing. This'll never happen again. This is one of those once in a lifetime, world-record-setting -- you know, we're getting right into the rut after that. It's going to get bad. (Laughter)
JASON: Okay. So we have now the ability to create and update and list projects. We've got about, call it, 20-ish minutes to set up GraphCDN. Let's see how it goes.
MAX: Absolutely. First thing you want to do is sign up for GraphCDN. It's going to hopefully ask you to input a -- actually, hmmm.
JASON: Do I need to deploy?
MAX: Sorry, yes, I think you might need to deploy. Or, actually, no. You don't. We can use GraphCDN CLI because our engineers worked on something called GraphCDN Serve. Ignore the entire signup flow. Go to your terminal and run npm install global GraphCDN. In your terminal.
JASON: Oh, just all the way.
MAX: Yeah, just all the way.
JASON: Npm install global GraphCDN CLI? Like that?
MAX: No, just GraphCDN.
JASON: Oh, got it.
MAX: Then run GraphCDN log-in, which will hopefully get you to the website and back to the CLI. Okay, nice. Now let's run GraphCDN serve. That actually needs some arguments that I don't know off the top of my head. Try running that and see what it does. It might give you an error that tells you what to do. Okay. That's not what we want. Try running -- ah, we might need a service already. Okay. So you do have to run through the onboarding flow. This is great user testing, by the way. You do have to run through the onboarding on the website because we need a service. But that's fine.
JASON: Can I do it with one of these?
MAX: Oh, yeah. Try that. Let's try it.
JASON: Love help. What is your production GraphQL API?
MAX: Okay, we do that have that. Let me get you that. Of course, this is again deployed on Netlify.
JASON: Oh, you deployed this GraphQL API already?
MAX: I've already deployed it on Netlify. You can also do it yourself if you want. I'm fine either way.
JASON: You know what, yeah, let's deploy it. Here's what I'm going to do. We'll just run through the whole flow. We'll do this really fast.
JASON: So I'm going to add a remote for the fork, yes. Okay. So now I have somewhat confused myself by putting this into my own, but now what I can do is I have -- we have a git remote for it. So I have origin, which is my upstream. I'm going to git add all. Okay. We're going to git commit and say getting ready for GraphCDN. I'm going to push it. Off it goes. Then I'm going to Netlify init. And we're going to create and configure a new site. Put it on my team. And we're going to call this GraphCDN projects.
JASON: We're going to authorize with GitHub through app.Netlify.com. Here we go. Excuse me. Okay.
MAX: Bless you.
JASON: Okay. Why did you open Chrome, you weirdo? This will happen fast. Oh, boy. Got something caught in my throat. This is going to get exciting for all of us.
MAX: I can narrate Jason's actions. Jason is opening OnePassword. He is getting it off the screen so we can't see the length of his password. He's going to authenticate into GitHub. Imagine if that was just your life. Imagine if that would happen every single time you did something. How annoying would that be? Just to have all your actions. Oh, my god.
JASON: You know, I could actually get into having a narrator. I feel like that would be fun for me. What on earth is happening right now? So the problem that I'm running into is that I think I broke some stuff on my browser. I am logged in. Oh, so it was already going. God, that's super frustrating. There is no build command. We're going to deploy the current directory. We have functions set up. Good, here we go. Adding deploy key to repository. I need to figure out why it keeps trying to open Chrome. That's not my default browser anymore. So that site is running. If I head over to app.Netlify.com, we will see here's my team. Here's GraphCDN projects. The deploy is starting up. Should happen nice and fast because we don't have to build it. It's just going to push these functions and the index file up. Build ready to start. Did you hang on me?
MAX: Starting up.
JASON: I'm just going to -- let's try there one more time just in case. There it is.
MAX: There you go.
JASON: Very rarely I hit that.
MAX: This seems fine.
JASON: We don't have too many npm packages, so this should happen nice and quickly. Off we go.
MAX: People, just a call out. How quick is it to deploy something to Netlify? Freaking good service. I got to plug your things now, Jason. Am I really plugging your things? I feel like you're plugging my things the whole time. I got to do something. Have I mentioned yet, if you haven't gotten one of these little, what do you call them, one of these little things. Rubber ducks. Man, my brain farted for a second there. If you haven't got one of these rubber Corgis, really should get one.
JASON: So something --
MAX: Wait, there's a Corgi emoji. Do I have access to that now?
JASON: You do indeed. Why did I get a 502 on this? Oh, I know why. It's because I didn't set up my environment variables yet. So watch this. I'm not going to run this on screen because I think it'll show the contents. But you can run Netlify env:import. That'll pull that into the app for you. So I'm going to run this. It does indeed show the values. So I'm going to show you just a little hint. Here's what it did. You see it over there? Isn't that great? So it just added those environment variables. Now if I clear this, I can bring it back over. And I'm going to do one more build. So let's trigger a deploy. Here we go. And this one will happen nice and fast because we should be cached.
MAX: How often have you messed up and shown on stream and had to clear?
JASON: Other every stream.
MAX: That must happen constantly.
JASON: To the point that if sound effects were happening, Ben would have just played the hacker command. At one point in the past, actually, we had one where the chat realized that I hadn't locked down an API yet. So they went in and started editing the database in the background.
MAX: Oh, no.
JASON: And we were just completely stumped as to what was going on. Because the data kept changing. Like, why isn't this working? Finally somebody edited the database to say you should probably lock down your API keys. We're like, oh, no! (Laughter)
MAX: (Laughter) Chat!
JASON: Now, there we go. We have our API keys in place, and we have ourselves a full-working project. If we go to the network tab, we can see, you know, this is happening nice and quick. Once we get to the production servers, we're a little closer to where I assume Airtable's production servers are. Things start happening fast. But they could be faster. So now that I have this, my end point is going to be functions GraphQL, right.
MAX: Yep, exactly.
JASON: If I go back to GraphCDN init, this is my end point, right?
MAX: Yes. Exactly.
JASON: Okay. No. Whoops. Oh, it just did the thing for me. Nice. I have an org. Created service.
MAX: So this has now created a GraphCDN service. We're not actually going to use this GraphCDN service. We're just going to run GraphCDN serve, which is basically like a development. Okay. Now we need to provide the back-end port, which is our local development port which I think is 8080 or -- this, yeah.
JASON: Okay. Does that mean I need to start the --
MAX: Yes, you will need to start the server. Essentially we're creating a GraphCDN service that's going to proxy through. So it's going to go through GraphCDN, down to you and proxy to your local GraphQL. You'll also need to do a dash, dash path.Netlify/functions. Just so it knows.
JASON: So here?
MAX: I think that should work.
JASON: Do the thing.
MAX: Let's hope this works, man.
JASON: Here we go.
MAX: This is a live test. We only recently added this to the CLI. This might break. If it breaks, we'll have to figure out another way to do this. I hope that it works.
JASON: GraphCDN is running.
MAX: So now go to local host 3010, and you should see a GraphQL playground.
MAX: Now try running some queries and see if it works.
JASON: Okay. Let me get our queries from over here. Nope, here. So we've got our queries. I'm going to load all projects. Ta-da!
JASON: Should I hit the spacing button?
MAX: I don't know if that does what we want. So what is happening, this is going to local host 3010. GraphCDN serve is redirecting that to GraphCDN. It's going through our gateway. Then it's going to Jason's local machine, to the local GraphQL API. It lets you look at your local GraphQL API. For exactly this use case, you want to change your GraphQL API locally, you have to also change your GraphCDN configuration. See what the cache does locally. That's why we have GraphCDN serve. From our website, rather than hitting -- oh --
JASON: Yeah, it just got generated for me, which is really handy.
MAX: This is now your GraphCDN configuration. This has one rule by default, which is cache everything for five minutes, which is exactly what we want. In the chat, is the gateway a Netlify function? No, GraphCDN is based on Fastly. This is running on Fastly and whatever is nearest to Jason. I don't know what the nearest --
JASON: Probably Portland.
MAX: I don't know the nearest point of presence. But the next metro city probably has one. So that is where Fastly's location is and where our gateways run. So from the index.html file, rather than hitting the local GraphQL API, we want to hit the local host 3010 API.
JASON: Okay. So I don't want to hit -- here we go. Let's go for every one of these -- nope. I'm going to go HTTP local host 3010. All right.
MAX: If you do the path afterwards --
JASON: Oh, that's right. I don't need the path afterwards.
JASON: Just make sure I did that on all of them. Good. Okay.
MAX: Perfect. Now let's open the website. This is now powered by GraphCDN. If you look at your network tab.
JASON: This one goes to local host 3010.
MAX: And if you scroll down in the response headers there, there should be some CDN headers in there. Scroll down a little bit.
JASON: GCDN. It's a cache hit.
MAX: So now this is cached in GraphCDN.
JASON: And look how fast that was. Much faster than the 400 we were getting before locally. So we have about seven-ish minutes left.
MAX: That's cool. Let me show you something that's really cool. If you try to create a project from the website, just create a new project.
JASON: On here? Here.
MAX: No, no, just on the actual thing.
JASON: Oh, right. Sorry, sorry. Got myself confused.
MAX: Where are you Jason? Create a project here. So what's going to happen, this is not going to show up. When you refresh, try save project. When you refresh, it's not going to show up. The reason is because --
JASON: Oh, it's cached.
MAX: But the cool thing is click on redact title on any one of these projects.
JASON: Do the same one we already did?
MAX: Sure. Now it's going to show up. So what just happened is the reason this works is because GraphCDN analyzes every query and every mutation that passes through the gateway. The project list mutation contains a list of four projects, or now five, right? We look at every single project and go, oh, this is the project with the ID X, this is project ID Y. When you press the redact title mutation, it triggers the update project mutation. From that mutation comes back a project with the ID X. Now we know, oh, look, we have the list of projects that contains the project with the ID X. There was just a mutation that passed through our gateway that returned the project with ID X. Hold on, we got to invalidate that list. We have to kick that out of the cache. That's for sure out of date.
JASON: So when we know the ID, GraphCDN can infer that data is out of date. But when we create a new project, the ID doesn't exist, so it wouldn't show up anywhere. So we would have to tell GraphCDN, hey, when I create a project, you also need to invalidate this list of queries.
MAX: Exactly. So we have an invalidation API. We don't have too into get into it, but you can do a fetch call from the server and be like, oh, a new project was created. Let me invalidate the projects list because I know a new item was just added. That's really the power of GraphCDN. We do this automatically for every mutation where we can. For some edge cases, you still have the power of going in and manually saying, hey, we created a new project. Let's just go do this.
JASON: All right. So where does one find -- here's the docs. We don't have time to get into it, but how do we -- is it manually?
JASON: Manually purge, okay.
MAX: Try that. Then there's a purging API explanation there. You can purge by anything. So you can really purge by, hey, I want to purge any query that contains the project with the ID X or Y or all projects. Anything you want. It's very, very, very granular.
JASON: Got it. Okay. That make sense. So that seems like something that would be reasonable. So we could get into mutation. We would purge project with an ID of whatever. That would clean the whole thing up. Okay.
MAX: You got it. 100%. And essentially, we do that under the hood for mutations. We say, oh, look, this mutation returned the project with the ID X. Let's just purge any query that contains any project with the ID X because it has probably changed. That's essentially how the automatic mutation works, in simple terms.
JASON: Yeah, so this, I think, this is a pretty quick overview. It's pretty powerful stuff. If I want to take this to production in our last couple minutes here, do I just swap out for my live end point?
MAX: Exactly. So you now have a live end point. If grow to your GraphCDN dashboard, you will see the exact URL. That is then pointed at your deployed API rather than local host. So you have serverless GraphQL Airtable extended to you. If you click through the API playground, you'll see the same thing. This is now pointed at the production GraphQL API. Good callout. Five million requests for free. That's very much on purpose. Developers like you or me that use GraphQL for their personal website, you should just be able to use this. You shouldn't have to pay us money. We're happy to support you. So everything counts as a request, but 5 million gets you very far. That gets you to about probably 100,000 monthly pages. Maybe a little more. Somewhere in that order of magnitude.
JASON: Okay. So this adds GraphCDN. So I'm going to push that up. Does anything need to change for this -- like, I don't need to configure anything, right? It's just going to work?
MAX: It's just going to work. That's it.
JASON: Very cool.
MAX: And of course, the project mutation we'll have to add to fetch call into the create project mutation in our back end to make sure we invalidate when we create a project. But this will work.
JASON: And is the long-term vision that I would be able to add a rule or like a dependency in a rule that says these are the mutations or queries that should invalidate this cache?
MAX: Yep, yep.
JASON: That's very cool. Because what's also exciting about that is that starts to open the door for, like, well now the computer can do that for me because GraphQL is statically analyzable. Hooray!
MAX: Exactly. That's why that's so powerful. We can look at your GraphQL API and go we know what you're sending here. We know what we're looking at. So everything is just going to work.
JASON: So let's just take a peek at how fast this becomes. 9 milliseconds to get that GraphQL back. And even on a tiny amount of data like that, it is a noticeable difference. This is very, very cool. Chat, if y'all want to go play with this, feel free. I imagine all of the titles will be redacted very shortly. But so, with that, that's all we've got time for today. What should somebody do if they want to go deeper here? What are next steps for folks who are excited about learning more about GraphQL, more about GraphCDN?
MAX: More about GraphQL, check out the GitHub GraphQL API. Play around with them. There are great ways to play around with things. There's a Star Wars API that doesn't require authentication. If you want to know more about GraphCDN, just sign up. We'll give you a demo GraphCDN service, and you can play around with that. That points at the GitHub GraphQL API. You can see what it feels like when it's super fast.
JASON: Yeah, this was the demo that showed up. So I just clicked the use demo data, and it built this for me.
JASON: Is there like a first steps kind of -- oh, here we go.
MAX: Yeah, we have docs for everything. Our docs will need to improve as well. We're working on those, but they should get you there, hopefully. If you have a GraphQL API you're running in production, hit me up. Feel free to DM me on Twitter. I'm happy to help anyone get set up. This is my job now. Thankfully. Which I'm very, very excited about. Feel free to ping me any time. Very, very happy to help anyone get set up with GraphCDN.
JASON: Yes. And if you want to see the code that we wrote today, it's already pushed up. So you can see it here. This will give you everything you need. The site is, again, posted at GraphCDNprojects.Netlify.app. You can find it there. Max, this was a blast. I really appreciate you taking the time to come on. Let's do a little bit of shouting out here. We've had Rachel with us from White Coat Captioning all day today. Thank you so much, Rachel, for being here. That is made possible through the support of our sponsors, Netlify, Fauna, and Auth0, all of whom are kicking in to make this show more accessible to more people. While you're checking out things on the site, check out the schedule. We have incredible stuff coming up with more being added every day. We have -- we're going to do a full-stack serverless app with Next and Fauna. We have marketing automation with Customer.io. TRPC is very cool. I have no idea how it works, but I'm excited to learn. N8n automation. Slinkity is very cool. How to write better content. A whole bunch more I haven't added to the site yet. Head over there, hit this add on Google Calendar button to get it added. You can follow on Twitch, and you'll get notified whenever we go live, as long as you keep that bell rung. And you can always find us on YouTube if you want to rewatch any of the episodes. Just head over to the episodes tab to see those. Max, any parting words for the chat before we wrap this up?
MAX: Well, I feel like I'm going to do one more plug. If you don't have the rubber ducky, do get the god damn rubber ducky. What are you doing if you don't have it? It is perfect. When the website works, go get the rubber ducky. It is amazing. I have it on my desk all day, every day. It is the best rubber duck I've ever had. Also the only one I've ever had because it was the only one good enough to sit on my desk all day. Jason, thank you so much for having me.
JASON: This little duck is going to listen to me weep as I try to figure out why my site is broken for no reason. Max, it's been an absolute pleasure. Chat, as always, thanks for hanging out. We're going to go find somebody to raid. That's all for this episode. We'll see you next time.