Let's Learn Blitz.js!
with Brandon Bayer
Blitz.js is "a batteries-included framework" for building full stack apps with React. In this episode, Brandon Bayer will teach us how it works!
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're bringing in Brandon Bayer. Thank you so much for being here.
BRANDON: You're welcome. I'm glad to be here.
JASON: I'm excited to learn from you because I have been hearing a lot of cool things about Blitz.js. It looks like it's got a lot of potential. Let's talk about you first, for folks not familiar with your work, do you want to give a background?
BRANDON: Sure. I went to college for electrical engineering and in the course of that, took a couple software classes and thought, wow! This is fun! I started as an intern programming using low level embedded programming uses C. Embedded microchips. After a few years I took a bootcamp to learn Ruby on Rails. That taught me everything I needed to know to get into web programming. I have been in web programming ever since, React, done mobile apps. And for the past four years, I have been working as an independent software consultant. And that's been super-awesome as well. And last year created Blitz and here we are!
JASON: That's actually my dad's story too. My dad was -- he went to college for electrical engineering and got into software through that process. It's cool that that's a natural path. You start working with the hardware. What if I could make the hardware do stuff? You start chasing that down. Cool. Blitz, I've heard a lot about it. But I honestly haven't had a chance to look at it. I'm coming in day one here, right? What is the pitch for Blitz? What would I as a developer be reaching for it?
BRANDON: If you don't know what those are, it's all in one batteries included framework. Not just the frontend or the backend, it's the whole enchilada and integrates everything together so that you can just be super-productive and build out things end-to-end. So, anything that needs a database, right? You don't have to -- its key feature, besides all the integrations is the zero API data layer. and we actually abstract the API layer into a compile step. As a developer you know don't have to worry about -- you don't have to worry about REST APIs or GraphQL, it imports it and magically works.
JASON: Interesting. That's very cool. This is like -- I kind of like this idea of you are bringing back the best of things that we know worked well. Like Ruby on Rails is hugely popular for a reason. It gave you a lot of kind of decisions made out of the box so you could just build things instead of fig out how to build things.
JASON: Laravel, same problem. What's the boilerplate for having this work? Just get it out there, boom, boom, boom, you're done. That, I think, is really interesting. So, I mean, is -- and so, Blitz is React-based, right?
JASON: And so --
BRANDON: Built on top of Next.js.
JASON: On top of Next.js. Is this the sort of thing where if I know next, will I feel right at home?
BRANDON: Yes, you're going to feel right at home. There's a few things to learn, but not that much.
JASON: Very cool. Awesome. Okay. I think that maybe just brings us to a good point to get our hands dirty and actually see how this thing works. So, let's switch over to our pair programming view here. And then I'm going to do a quick shoutout to our sponsors. First, we've got the show being live captioned right now by Amanda from White Coat Captioning. Amanda, thank you so much for being here. And thank you to White Coat for getting the live captioning going. Made possible by our sponsors, Netlify, Fauna, Auth0 and Hasura. And you want to see that live captioning, head right to the home page of learnwithjason.dev. The transcripts are included with the shows. When you look at an episode, you can see the transcript there. And that brings us to clicking things on the Internet. Make sure you go and give Brandon a follow on Twitter. There's a Blitz.js Twitter if you want to stay up on that. And we are working today with Blitz.js. All right. So, the only thing that I've done so far is I ran the command npm install global Blitz. So, I now have the blitz. We're using version 0.38.4. And I don't know what to do next. What's my first step if I'm a Dev? I want to get my hands dirty, I want to build a Blitz site.
BRANDON: First thing is come up with a name for your project.
BRANDON: And run Blitz new and pass it that name.
JASON: Let's talk about what we're going to build today and frame the thinking here. The thing that Blitz is really good at, as you mentioned, is doing full stack applications, so like data. We want to make sure that we do something with that. This show is a big fan of corgis in general. So, I think we can do maybe a quick app that'll help us track our corgis. And then we can track maybe a little bit of data against them. Number of pets, number of boops, something like that. Where we can just integrate those things. And that will let us -- actually let the chat play along. We can maybe shift this thing and see how it goes and people can pet and boop and with don't have to worry about input validation. I love the chat, but they're terrible people. If we give them unbridled input, they'll do terrible things. [ Laughter ] So, let's call this app Corgi Tracker. You hackers, you -- you dirty hackers. That's exactly you, chat. You're the hackers. Yeah. We'll call it Corgi Tracker. How will I create that? Do I need to create the folder or will Blitz do that for me?
BRANDON: Blitz will create the folder for you.
JASON: Okay. Blitz new and then I put in the name? Like corgi-tracker?
JASON: Okay. Oooo... I don't know any of these. So, I'll just go with the recommended, I assume?
JASON: Okay. Retrieving the freshest of dependencies.
BRANDON: It was a pet peeve of mine. You generate a new app and all the dependencies are out of date. We fetch the newest one and it's pristine.
JASON: Something I enjoy is microcopy like that. Putting a little bit of personality into a tool is -- you know, it can cross the line into cheesy if you go too far with it. But, man, I love it when it sounds like something is written by a human and it's not just like just the most kind of stiff and lifeless robo copy. A person wrote this. Or a person. We should have a little bit of fun! And so, under the hood -- oh, this -- oh, shit, this set up a database? You made me swear! You made me do a swear! This set up the database for us, we've got the dependencies, formatting, committing. Dang, that's cool. Corgi. And did you say this is -- or let me run my status command. Interesting. Okay. You got me set up with a get everything.
JASON: Okay. That's cool. I'm into it. So, I'll just open this up in VSCode. Let's poke around.
BRANDON: One thing I usually say here is that it generates a lot of files. And so, initially, it can look a little overwhelming. Especially if you're used to generating new apps with Next.js, for example, which is just super-minimal. But what we're optimizing for is not just an initial, like, wow, this is so simple. But instead, we're optimizing for actually building real projects. We give you everything that you need. We set up ESLint, husky get hooks, test setup, jest with jest. Also, we have authentication, user authentication sign up and login for you and password flows.
JASON: Holy crap.
BRANDON: There's a lot there. But it saves you so much time. We found that it's worth it to have this a bit more initial overwhelming experience. But once you learn it, it's the same for every new app that you create.
JASON: Very cool. This is great. And we've got -- I see the SQLite database, I'm seeing Prisma. We've done an episode on Prisma. We -- I don't know how deep we're gonna get into it. But let me just pull up the episode for anybody who is interested.
BRANDON: So, we have Prisma set up by default because we believe it's the best for most people. But we are database agnostic. So, you can easily take out Prisma and install something else. We have an example for Fauna, for example, or anything else.
JASON: Nice. Very cool. Okay. So, I'm gonna make an assumption that I'm looking at my app. Would be my starting point here.
BRANDON: Correct. Yep.
JASON: Where -- I -- so, there's so much going here, I'm wondering if I should just run this and take a look at what exists already?
BRANDON: Yeah, you probably should.
JASON: Okay. Let's look at the package.json here and we're gonna run Dev. Okay. So, we're getting started. Compiling. All right. And so, this is using -- this is built on top of Next.js, you said.
BRANDON: Yep. It will look -- feel very familiar.
JASON: Got it, got it. So, we've got localhost.3000 here. Let's head over there and look at it. All right. So, including sign-up and login. To add a new model to your app. Oooo... this is cool. All right. Restart the server, go to projects. All right. So, I just -- I want to just sign up. I want so see how this flow works out of the gate. So, I'm gonna create an email. Write something that I can remember. Go away. Stop helping, please. Thank you. All right. So, now I'm logged in. I've -- I've created an account. And then if I log out, I can login. Wow. Okay. Well, that's a nice thing to never have to think about again.
BRANDON: Exactly. That is one of my favorite things.
JASON: Okay. So, let's poke around a little bit and see how this works. So, under the hood, I'm gonna make an assumption. This is our Next stuff. So, we've got our index and so, we're pulling the user, the user is coming out of app/core/hooks/currentUser. We can poke at that in a second. We're using the same kind of page-based routeing that you would use in any Next.js app.
BRANDON: Yep. Exactly the same.
JASON: Okay. Good. And then, all this looks very familiar. Nothing we haven't seen before. We've got kind of our logic on if you have --
BRANDON: Notice the routes. The link href equals a routes.signupPage. This is something we add on top of next. It's similar to Prisma, how they generate a fully tacked client, we generate this object with all the routes in your application. If you move that page to a different route, but keep the same name, then you don't have to update your links because it will be automatically updated. And then you also have kind of safety against broken links, right? So, if you, like, if you remove that page, then you're going to get a TypeScript error because it doesn't exist on that object.
JASON: You know, it took me a long time to warm up to TypeScript. But I hovered over routes and I can see every page that exists in my app here. I get it. I get why people like it. This is so cool to just quickly see, all right. These are the pages we've created. So, that's -- I mean, that's excellent. That's very, very, very cool. And then are we registering that somewhere or does it just know?
BRANDON: It just knows.
JASON: That's so cool. And then we've got using global, global styles. What else is happening here? Then we are getting a layout. Oh, what's -- is this a Blitz thing? This is new to me. Or is that a Next thing I have never used?
BRANDON: The suppressed first render?
JASON: The getLayout.
BRANDON: So, getLayout is -- this is not a Blitz thing. It's something you can do in Next.js too.
BRANDON: If you go inside underscore.app, sorry, underscore app, then you can see how we use this. This is taken from an Adam Wieland blog post where he goes over different ways to have layouts where the layout isn't unmounted on edge page transition.
JASON: Okay. I understand.
BRANDON: And they're adding this to the official Next.js docs very soon.
JASON: Okay. Cool. I like that. And then the suppressed flicker? I haven't seen that.
BRANDON: That's what Blitz adds. You have this issue because Next.js and Blitz statically render your loading screen. And so, the first -- the very first thing that you load on the page is going to be what was statically rendered. So, let's say the statically rendered alerting screen is the log out state. But as soon as the page loads, we know that you're logged in. Or -- if you are. So, there's a flicker, a short flicker there where you see the log out and then the login. It makes for a bad experience. And so, if you enable this flag on that page, then we actually hide that first render. Where it's the log out state and then as soon as the login state -- so, that like the login state is exactly what you see the first thing without the flicker.
JASON: Got it. Okay. Okay. That makes sense.
BRANDON: This is something that you do in Next.js itself, Vercel does it. But we wrapped it in. So it's easy to do.
BRANDON: I like that. So, I have a general idea here. I'm gonna dig into this core folder a little bit and I want to look at these hooks. So, getCurrent user. We're doing app/users/queries. We can dig into that a little bit. And users --
BRANDON: That's where the magic happens. This useQuery hook is built on top of React query. Have you used React Query?
BRANDON: It's very familiar with that, except that the query keys are automatically generated for you.
JASON: Okay. Cool. We have an episode in here somewhere. Let's find it. Do you want to learn more about how React Query works? Tanner Linsley, the creator of React Query did teach us about it. You can dig in there for the specifics. But the short version is it's dope. It dedupes and does some caching and makes things generally performant and faster. I'm happy to see it here. Because now I know my app is gonna be nice and snappy.
BRANDON: So, this is where your -- this is where the magic happens. So, just like the pages folder is a magic folder that gets turned into a route, the queries folder and mutations folder work the same way. They're magic folders that whatever is inside of that gets turned into an API endpoint. And so, here the getCurrentUser. How you fetch from that, import your component into your page and pass it to the use query hook. And then at build time, swap that out with the API client.
JASON: Okay. And it looks like there's no -- so, the CTX, this is strictly for typing?
BRANDON: And so, the CTX is a server side-only thing. So, it comes via middleware. That's how we implement sessions. Or you can add your own HTTP middleware to do other advanced stuff.
JASON: Okay. Okay. Cool.
BRANDON: So, the -- this is a -- I think you call a Unary function. The first input is the argument and the second is the context. You have to put all your function arguments into one object or whatever. And then it's all available there. So, it's just operates like a regular function call. Whatever arguments you pass into the useQuery hook comes into this function. And the result returned from here is returned on the client. And you'll notice that the TypeScript types are there statically without requiring GraphQL code generator for something like that.
JASON: Yeah. And the thing I like about this, if I hover over users here... well, actually let me just do some actual typing. I'll do db.user. I can see all the options available here. We can aggregate users, count 'em, create, delete. All those things right off of this db.user. And my assumption is we've only got a user because I only see that one folder.
BRANDON: There's like a session and a token model.
JASON: Session, token, user. Yeah.
BRANDON: Auth. And the cool thing with Blitz is that this not only works on the server, but the types flow all the way to your client now too since we take care of that.
JASON: And so, that means when I go out to -- to here, and I look at my index. We get the current user. And then if I get in here, I can get all of the types here. So, I've got email, ID, name and role. All right. So, this is -- I'm already stoked. I already like this. How do I -- like, let's add a page here. So, we want to track corgis. Which means we need to put corgis in our database. Which I believe means we need a model. Right?
JASON: That's database speak for a place to put stuff.
BRANDON: So, go to the terminal and we'll return the Blitz generate command. And it will generate all the code you need end-to-end for all the crud operations, create, read, update, delete.
JASON: Okay. I Blitz generate.
BRANDON: Do all.
BRANDON: And then space and then the name of the model.
JASON: We'll call it corgis. It will just be corgi -- is it users or user?
BRANDON: You can do it either way. We just handle it for you.
JASON: Let's be consistent.
BRANDON: We automatically change it to be -- the model in the database is singular, but the pages are plural.
JASON: Oh, okay. We'll call it corgis.
BRANDON: Doesn't matter what you give it.
JASON: We'll call it corgis.
BRANDON: Give it a name attribute. Name: String.
BRANDON: Is there any other attributes you want to add on this?
JASON: Yeah. Let's do -- pets will be -- is it number or int?
BRANDON: Int. I-n-t.
JASON: And then boops.
BRANDON: Exactly. Pass to Prisma.
JASON: Got it, got it, got it. Now we --
BRANDON: Do you want those to be required or optional?
JASON: Let's --
BRANDON: Or default them?
JASON: Let's default them to zero.
BRANDON: Colon default and I think it's in brackets, do zero.
JASON: Which brackets?
BRANDON: Square brackets.
JASON: Okay. And let's do the same thing here. And then let's also -- yeah. That's good enough. Let's start there. And then maybe we can play a little bit more afterward. So, name, pets and boops. We've got some defaults set. Here we go. I screwed it up.
BRANDON: So, with Zsh, you need to wrap those two as strings, unfortunately.
JASON: Oh, gotcha.
BRANDON: The last two arguments.
JASON: Like individually like that?
JASON: Okay. [Evil laughter] Look at all that stuff happening that I don't know how to do. Okay. Now we can see -- if you haven't seen Prisma stuff. We haven't seen that one in a while! So, if you're familiar with Prisma, or if you're not familiar with Prisma, don't worry about this. Because it's not really super-important. But if you are familiar with Prisma, you'll recognize this is a way that Prisma defines a model. It's a GraphQL-like model. The model, corgi, and then the ID, and you added created at, updated at, which is good. And then we have name, pets and boops. And then we have the integer, dateTime, dateTime, string. It looks like it doesn't pick up our default, though.
BRANDON: Yeah. I might have told you the wrong thing.
JASON: That's okay. We can fix it.
BRANDON: Oh. We changed that to an equals sign. So, okay. It used to be the brackets. But we fixed that.
BRANDON: All right. So, yeah, you'll have to -- don't run the migrate. Just do false.
BRANDON: And then we'll go add that manually in the schema and then come back and run migrate.
JASON: Okay., I'm gonna go back into the database. And then we have the schema. And then down here we've got all of our models. And let's add -- this is the one that we added. And so, I know this default here. Oh, did it -- holy crap! I have GitHub copilot now. Look at it go. Sheee... shut up, GitHub. Okay. So, that's amazing. All right. So, between Blitz and Prisma and GitHub, I think I just don't need to know how to code anymore. I'm just gonna tab my way to success. Amazing. Okay. Now we've got that. We need to rerun that migration. To do that...
BRANDON: In Blitz, run Prisma command like normal, but prefix it with Blitz. Blitz space Prisma, and migrate Dev.
BRANDON: This is to migrate your variables. Prisma has very minimal environment variable support.
JASON: I gave my migration a name. Now we have SQLite migration and it looks like it's done. And now we're -- we're fixed. We're ready?
BRANDON: Okay. So, now you should be able to start the server. Or restart it.
BRANDON: If we need to. And then access those pages.
JASON: Oh, it made pages for me!
BRANDON: Yep. Everything.
JASON: Okay. Okay. So, let's go back here. Here's our setup. I'm gonna go to corgis. Get outta here. Okay. I'm going to create a new corgi. And we're going to name this corgi. Chat! Quick! Corgi names! What you got? There they go.
BRANDON: You'll notice there's only a name field here. It doesn't have the other two to fields. Right now it's very dumb. All it does is just a name field no matter what arguments you pass for the pages. But we are -- we have a PR open. And we're working on adding support so that it will automatically add all the form fields for you too.
JASON: Okay. Did I need to make this into a list? Or, let's see. Create corgi. So, Doopy Dog. And name a corgi Tiger. I love when animals are named something other than what they are. How about Coco? That's a good corgi name. Sir Snifferton the Second. I wasn't going add a fourth -- but we'll go with English titles here. Sir Snifferton II. Okay. We have our list of corgis. Thank you, chat, as always. Great job, everyone. And I'm already pretty blown away here that I didn't have to write any code for that to work. Like we were just able to say what we wanted and it -- had I not typo'd in the generate, we wouldn't have had to edit in the schema either. We could have just said Blitz generate corgis -- or Blitz generate all corgis and then the field names and then just immediately started it and looked at this page and created our corgis.
JASON: That's cool. That is extremely cool. Okay.
BRANDON: You could go look at the queries and mutations for the -- if you want to kind of see like what it created for you.
JASON: Oh, my god, we haven't even been here 30 minutes and we've already built an app where we're like editing data. This is -- this is outstanding.
BRANDON: Folks are literally saying Blitz makes them 5 to 10 times more productive.
JASON: Yeah, I can see it. I can see how quickly you can prototype with this. Yeah, I want to put together an app, I need to be able to track projects. I need to be able to like link. And we know from the Prisma episode, if you want to look at that, that relationships are really easy. But it look like we have plenty of time today. So, maybe look at creating some relationships with that.
BRANDON: Yep. And we have code for that in the generators too.
JASON: Come on, man. Let's poke around in this corgis page. Let's look at the queries first. So, this is our, like, corgis model folder. And then we've got mutations and queries. Let's start with queries. And so, we can get a whole list of corgis. Let's see what we've got here. So, we've got the ability to... got a resolver.
BRANDON: Resolver.pipe is just a functional pipe. So, it just pipes in the input and the output through all the functions that you pass to it.
BRANDON: And we kind of ended up adding this by handles TypeScript types.
BRANDON: You'll see it more in the mutations with the TypeScript types. But resolver.authorize will ensure you're logged in. If you try access those pages, you'll get a login form instead.
JASON: Here we go. I'm logged out. I'm going to corgis. It showed up at corgis, but wants me to log in. So, then I'm gonna login. And now I'm back at my corgis page.
BRANDON: Yep. you can set it up to do redirects if you want. But we think not redirects is the best experience.
JASON: Uh-huh. Man. That is really cool. Yeah, yeah, yeah. So, this is great. Like, this -- this -- this feels very nice.
BRANDON: So, in that resolver.authorize, if you want only admins to access that, you can pass the admin string to authorize. Or it can be an array of rules. And then that will throw an authorization error if your user doesn't have that role.
JASON: We have already established, by default --
BRANDON: By default, I think it's just user role.
JASON: Okay. If we look at the schema and we look at our user. Users have roles. And there is a user role. So, I would need to modify my user to include like an admin or a author or something like that. Okay. So, I saw a question in the chat that is also a question I have now because I saw what was happening there. Let's look -- where was the corgis? App, corgis, under queries. We've got our list of corgis. And it says in multi-tenant app. I don't know what that means. What is a multi-tenant app?
BRANDON: Multi-tenant is any type of standard SaaS application. Where you have a business, you're selling this application and you have multiple separate accounts that can't view the other's data. That's multi-tenant. Each tenant has their own data, but it's all in the same database.
JASON: Got it.
BRANDON: Right now it says db.corgi.find many. But if you are users with separate corgis, add aware, user ID, the ID of the logged in user. Or an organization, then organization of the current -- the ID of the current organization.
JASON: Okay. Okay. I got you. So, we probably won't need that today. But like that would be -- if like I was tracking my own to dos, for example, I would only want the ones I created. In this case, we want the corgis to be public. That's perfect. That's what we want.
JASON: All right. So, then, we know if we want to create a corgi, we've got the button here where we can create a corgi and it takes in, let's see. We've got create a corgi. And it takes in an object. Which is the name. Zod I'm assuming is just the type checker.
BRANDON: Oh, this is -- yeah. Yeah. So, it's similar to --
BRANDON: But just -- the corgi -- your input must be an object with the name field. And if does not have that, it's going to throw an appropriate error. And also, if it has extra fields, it will just remove those. So, this is important whenever -- and so, you want to make sure that a user can't set themselves as admin or something. And so, it's very important that you're validating all your mutation input. And so, we have that set up for you by default.
JASON: Okay. Just to pseudo code this for a minute. If I had my new corgi and I put in like name is 27. This --
BRANDON: Would throw an error.
JASON: This would throw an error because this needs to be a string. But then if I did like, you know, whatever and then I said role: Admin, assuming this was like a new user, this gets stripped.
JASON: Okay. That's great. I mean, that's really cool. Because that's not a thing that I want to have to think about ever.
BRANDON: The other bonus here is that our form abstraction components that we have set up for you work with this schema. And, you can pass the same schema to your form on the client side and you get -- so, you have the same object that's doing both client and server validation.
JASON: That's -- that's great. Okay. All right. So, now I'm ready to change this thing. So, I want -- so, here's what I want. I want to be able to publicly list the corgis. No, I don't. Because I want you to be logged in when you do it. So, we're gonna have everybody create an account. They're gonna be able to see a corgi. And I want them to boop a corgi and have that number get updated in the database. So, to update that, I would need to add the boop. And that would be a z.num integer. Okay. Nope. Int.
BRANDON: So, that's TypeScript identifiers.
JASON: Can I make this optional?
BRANDON: Yeah. Dot optional on the end.
JASON: Like that. And then same thing for name, probably. Yep.
JASON: Okay. And so, that means that if the name doesn't get included, it would just skip -- it wouldn't change the name? It'll leave it alone?
JASON: Okay. And the boop, same thing. If it's not there, it will get --
JASON: Left alone. Excellent. So, now --
BRANDON: Actually, boop -- boop, you probably want to do -- I think... so, you might want to create a whole new mutation for this. Because boop, you don't want to pass in a number.
BRANDON: Yes. And Prisma has the increment feature.
JASON: Okay. Let's roll that all the way back. And instead we're going to increment boops.
BRANDON: I would just copy, update the contents of this update one and put it over there.
JASON: Okay. So, we're gonna take this, start here. And this one is just gonna include the ID because that's all we need. And then we're going to update where ID. And our data, we don't really need data. We just need the ID.
JASON: And so, then our multi-tenant doesn't matter anymore because we're always updating just one. And that ID we want -- I forget how Prisma does this. We have --
BRANDON: I forgot too.
JASON: Let's see if it will do -- don't you dare. Don't you dare. Is that it?
BRANDON: Add... oh, maybe...
JASON: Copilot. What is that? Add number is not assignable to type number. Okay. So, I think we're wrong.
BRANDON: Increment. I'm looking up the Prisma docs.
BRANDON: So, you pass an only to that field with increment colon one.
JASON: Increment 1. Okay. So...
BRANDON: So, inside of data, yeah, boops. Inside of their --
JASON: Okay. So, that's that. Increment 1. That's wild. Okay. [Laughs] Okay. This should let us update our boop count. Which mean this is -- did I change anything in here? I don't think so. So, then I can go into the app and we'll look at the corgis and we'll look at a given corgi. And we're just gonna get right here. So, I want to... let's see. It looks like you get a delete corgi mutation from use mutation. So, I'm going to update boops mutation. From useMutation. Update --
JASON: Oh, increment is what I called it. That's right. IncrementBoops. And it autoimported that for me. So, I'm happy. And then I'm going to say... I guess this should be like a button. We don't need a lot of copy around this. It's sort of self-explanatory. And then we'll do onClick. And just like update boops mutation. Make that a button. So, here's a question. Is that enough? Or do I need to do something to be reactive here?
BRANDON: One more thing you need to do is invalidate the useQuery cache.
JASON: Okay. And I would do that by?
BRANDON: Probably the easiest way is the useQuery returns as the second argument. There's an object. And inside that has a refetch function. So, comma and then you're gonna destructure that object to refetch.
JASON: Like that?
BRANDON: Yep. Like that.
JASON: Okay. So, let me -- let me move this out then.
BRANDON: And then we'll call refetch after... yeah. Make an async function, await updateBoops and then refetch.
JASON: Async, await, refetch.
BRANDON: You could use dot then. But this works too.
JASON: Yeah. We would have just done the dot then. So, update boop. I think I did that right. I'm pretty sure it's already loading for us. Oh, my goodness. Did we do it? Let's go in and boop Doopy Dog. Crap! Invalid type. Okay. So, I did something wrong.
BRANDON: We didn't pass the ID.
JASON: Oh! Ah-ha! Yeah, you're right. So, the ID needs to be here and that's gonna be corgi ID.
JASON: Oh, crap.
BRANDON: So, actually, no, you don't need it there. Just add it inside the -- you don't need to pass it from the own click. Just do ID: CorgiId. I think it's already --
JASON: Oh, that's right. Because it's here.
BRANDON: Use params hook. Yep.
JASON: Perfect. Number undefined is not assignable to type number?
BRANDON: So, this is -- this is a little bit a goofy thing. It's because corgiId is undefined on the first load because it's that statically-generated loading page.
JASON: So, we'll just short circuit here. We'll do --
BRANDON: You can do that. Or you can just add a colon. Or not a colon, but exclamation point on the end of corgiId. And that will TypeScript to shut up.
JASON: We don't need to positive TypeScript around like that. The check. I would rather have a console error. This should do what we want. So, let's clear the console and let's try it again. Look at it go! Boop, boop, boop! Boop, boop, boop! You can also see changing the updated app so we can see that is working the way that we wanted. Man, that's so cool. Okay. Amazing. All right. So, then I just realized, we need -- we need these corgis to have pictures. Right? So, let's migrate our schema to have a URL, right? And then we can just pull in from Unsplashed? Does that seem like a good next step? All right. So, let's go to Unsplash. And we're gonna look for corgi. [laughs]-okay. We're no shortage of images here. So, let's go into our database. Which will be down here. Let me get into the schema. We've got our corgis down at the bottom. And I want a image that's gonna be a string. And we want that to be optional, right? So, I can do it like that?
JASON: Okay. Okay. So, image is optional. And then I want to stop and we're gonna run Prisma migrate Dev again. And it's gonna ask me for a name. Add_corgi_image. Okay. And then I can start the server again.
BRANDON: By the way, you don't have to add the underscore on those migration names. Prisma will do it for you.
JASON: Slick. That's excellent. So, then when we get back in here, we've got an image. There we go. I'm gonna edit. And I need to update this to include the image. So, we can head back into our app here. Look at our corgis. Mutations, update. Needs to include --
BRANDON: Oh, yeah. Go ahead.
JASON: The image. And that one's optional. All right. And then down here. The rest of it's -- that's it, right? That's all we need.
BRANDON: Yeah. Except for the form. So, go to the corgi's, components, and then to the form. Under -- under corgis --
JASON: You broke up on me for a second there.
BRANDON: The form is -- you have a corgi form component.
JASON: App, corgis. Oh, components. Oh, gotcha, I understand.
BRANDON: You have a corgi form component under -- imported in this page.
BRANDON: That's -- yep.
BRANDON: So, there's a reuse for both corgi and update.
JASON: That's excellent. Okay. So, now we have the corgi form. And there's our image. So, I'm gonna go to here and let's see. Who is Doopy Dog? You can be Doopy Dog. Let's copy the image link. Drop it in. We're gonna update our corgi. Beauty. All right. And let's just add a couple more. You can be Tiger. Let's get a Coco in there. Oh, you're definitely Coco. Edit, add Coco. Update. And Sir Snifferton. Is there like a particularly regal-looking corgi in here?
BRANDON: There was up above.
JASON: There's a better one up above? Oh, with the glasses. That's right. Let's find that one. There we go. Sir Snifferton. All right. So, now we have a list of corgis. Each one of them has a -- an image attached now. So that means we can do a little bit of setup here. So, let's show our index. And in our index, right now we're linking to the corgi name. But we could also link to -- we'll do an image source. I'm sorry, did you just predict my whole... what is it? It's option... -- manage, oh, wait, no, that's not it. Where is it? What are you doing? Yeah, whatever. Corgi image. I'm so mad at GitHub for that. And then we come out here. I did it wrong. Multiple children were passed. But only one child is supported. Which means I needed to move this outside. Okay. Then we need to style this just a tad. Is there -- should I do this a certain way?
BRANDON: you can just add a style. Inline style tag. Or attribute.
JASON: And we'll do like a width of 200. We can do a height of 200. And we'll do an object fit. Go like this. Cover. And I think that'll just work. There we go. So, there's our -- there's our dogs. And then for each of these, maybe we do like a style. Oh, no. I started writing CSS. Display flex. And then we can do like a justify content.
BRANDON: Blitz does everything else for you.
JASON: What? No it doesn't! Get outta here. Okay. I'm gonna make this display block and then I'm done. Amazing! Space between. And then we'll get a little breathing room on our old puppers there. And this we can do margin none. Nope, wait, it was padding. Padding it what -- is it neither? Both? Help! Close enough, right? But this is beautiful. Here we go. Now we can click in, all right? And then I can use this same markup here in the individual corgi. So, we're gonna get this setup here. Did I do that wrong? I did something wrong. Do not use image. Leave me alone. Let me live my life. There it is. Okay. So, we click in, we see the corgi. And then we can boop it. Boop, boop, boop.
BRANDON: We looked -- there it is.
JASON: And then down here, I can also do like corgi.boops. Right? Okay. This is super-cool how quickly we're able to make this work, right? This is kind of incredible. Yeah, I have not written any code, Farshid. Between Blitz, Prisma and -- I have written like two lines of code. I've done the styles. That's what I did. Man, I'm very -- very impressed with how quickly this is all coming together, right? This is super-fast. So, I'm in here now. I have been able to -- I've got my list of corgis. I can create a new one. And that will let me do that. I can go back to my full list. And we've got all these dogs. We can edit one of these corgis. We can change the image or give it a new name. And then I added the boop mutation. Dang it, that's cool. Like, this is -- this is very cool. So, let's see. We've got about 30-ish minutes left of time. Is there anything that you're looking at that we haven't shown off that's like a cool feature of Blitz? Or can I throw a curveball at you?
BRANDON: There's a number of different features that we could talk about that's -- it's kind of there, but you don't really notice.
JASON: I mean, yeah. Please. Please do.
BRANDON: So, number one is did you have to handle anything with dates?
BRANDON: Did you have to utilize dates?
JASON: No. I didn't have the fields.
BRANDON: We do that for you. We built a custom serializer called superjson that automatically serializes and deserializes dates, sets, maps, regX, and some more, I think. So, it ensures data exactly what you pass over the application is exactly what you see on the client. And so, that's so you don't have to worry about, like we automatically convert it back to a date. That's one thing that is really cool. And then secondly is error handling. So, if you go to -- go to like one of those queries for get corgi or something. And so, we have that resolver.authorize. And that resolver.authorize, what it does, it just throws an authorization error. So, you could also throw any other error in here like, for example, the not found error. You could see that we just throw that. Okay? So, what happens is, server errors on the client. Like try catch works for the whole stack inside of Blitz. Like you probably know how to catch errors in React, right? Which is error boundaries.
BRANDON: And so, we have in underscore app, we have an error boundary set up for you.
JASON: Peek at that. Here's our app,
BRANDON: If error is an instance of authentication error, then we show the login form. Otherwise, authorization, we show that. This is catching that not found, authentication error. Any other error you throw on the server is caught here. It's easy for catching errors. You're not serializing, deserializing, checking if the code is 200 or whatever. You have it in your boundary.
JASON: Nice. This is nice. You're doing things that I just don't think about until somebody reports that it's broken. Like the error bound arise. Oh, somebody sends along string, this is the Java script error across my screen. I'm an accountant. Why am I seeing this? It's nice that you've kind of built this, at least some guardrails in here. And I also like that what's happening here is it's starting out by saying, hey. If you're not authenticated, just take you to the login form. If you have an authorization error, like you're not an admin, you're trying to see admin content, say that you're not supposed to see that. And then look to see if it's a different kind of error and show that. You're not leaking implementation details to somebody not authorized to see them because they didn't have the admin role and it triggers something. Oh, you can't see the dot whatever. Now they know what that field is called and they can go try to exploit that. Yeah. I think that's really nice. Like that's a super, super handy feature. That's just one less thing for me to worry about. Let's see. So --
BRANDON: A question in the chat about --
JASON: You dirty hackers.
BRANDON: There's a question in the chat about the socialization.
BRANDON: And so, that allows any clients that are calling that endpoint to -- to just use it as regular JSON. But also, then, we can -- another question, sorry if I asked that question in a query, can I modify how the result will like. Let's say I would like to respond with an image. I think yes. You can, in your query, you can return whatever you want. So, you have full control. Inside here, you could fetch from a third-party API or like whatever you want to do. Read from the file system.
JASON: Yeah. So, we could put like here's the corgi and then we could do like other thing. Waddup. And when this comes back, like this might throw a type error because this isn't actually in our schema.
BRANDON: It will work. It will just add the type to the client because you're returning it.
JASON: Come on! Okay. So, then in my corgiId down here, we'll see that now it's including other thing waddup. So, we have full control over what comes out. We can kind of change it however we want. Stop it. Quit reading my brain! Okay. So, then yeah. Content type followed. So, it held on to it for us. We've got all those details. Somebody also asked, can you use React Native with Blitz? I don't -- does it work?
BRANDON: Yes. So, we have someone right now that has a React Native app that is accessing the automatically generated -- can use React Native. And we have somebody using a React Native app, calling the automatically generated endpoints.
JASON: Got it. So, David just -- just asking your question again. Because I think you're looking for something specific. Are you looking to, like, you want to send back a buffer through this query response? Or like is there a certain data type you're trying to respond with?
BRANDON: So, this will --
JASON: Yeah, the buffer.
BRANDON: It can't be a buffer currently. That's something we could -- we could probably potentially add support for. But so, roughly JSON serializable except that we support dates, maps, sets, regX, et cetera which are not natively JSON serializable. But you can with our extra library.
JASON: Okay. So, to get around that now, you would need to like dump the buffer into Bay 64 and decode it when you got to the other end or something like that.
BRANDON: Yeah. Or just use an API route where you have full control over how the request responds and buffers.
JASON: Yeah. So, if we did an API route, we could instead of going through the query, you could use the query to get the data and then just return the response as a buffer. And you like send the right data type with it. Yeah. Okay. That would work. So, that's a good work around. That seems usable. Well, you don't miss out on the cool stuff with that. You could still call the API endpoint.
BRANDON: It's not as easy.
JASON: It's definitely like -- go ahead.
BRANDON: I was just gonna say that, David, feel free to open the issue if it's something that you really care about and you're using Blitz and you want it. Then we can look into how to support it.
JASON: Yeah. And Sean is recommending Uint8Array is a good way to move buffers around as well. But yeah. So, we've got 25 minutes, right? In --
BRANDON: There's one more --
JASON: Oh, yeah. Go ahead.
BRANDON: There's another question in here. When is the targeted 1.0 release? So, we're -- right now. The last major thing is finishing the Next.js fork migration. So, we've actually forked Next.js and adding our features directly into Core. It's going to be a much better experience. There's bugs, development time, bugs and kind of quirks right now that will just completely go away with that.
BRANDON: I'm working hard on that. And we are keeping up to date with Next.js. So, all new features they add, we just merge.
JASON: I just saw ajwwebdev dropped us an episode about talking about the forking. And I won't make you rehash this too much. But I am curious. So, does that mean that you are, like, do you expect drift here? Like you have a fork of Next that is basically no longer Next and you're just going to be porting over good Next features as they get released?
BRANDON: Currently we're merging everything that they do and just extending it. So, it depends on what the community wants. Right now the community wants to stay in sync. And so, we're just like kind of running a parallel track. So, we just have extra things that Vercel, or that Next.js doesn't. But we're still keeping track. Potentially at some point we would differ if we need to and if it makes sense. But for the foreseeable future not. Staying in sync.
JASON: Gotcha. Cool. Here's a thing that I'm just curious about. I would like to see how we can do relationships. So, I have an idea. Let's -- let's create a toy model and then we'll have each corgi pick a favorite toy. And so, we can set up a quick relationship and that's nice and straightforward. So, if I want to do that, I'm gonna make an assumption here that I'm going to do Blitz generate and then I'm not sure what happens next. Is it still all?
JASON: Okay. And I would do toy. And then I would be like a name string. And that's really it, right? Because the next thing is I would to, like --
BRANDON: And then... type belongs to. With a capital T.
BRANDON: BelongsTo capital T, corgi. And then do you want the pages to be nested under the -- the corgi model? So, do you want the route to be like slash corgi/1/choice?
JASON: What if it was, so, we could do /toys and it would show the toy and which corgis liked it? And the other way too. It would be a one to many relationship. Or many-to-many relationship. Because one toy would have multiple corgis. But each corgi would only like one toy. Let's do it that way. They have a favorite toy. It's a one-to-many relationship. Wait. Am I describing that right? Each corgi has one favorite toy. Each toy can be favorited by multiple corgis.
BRANDON: So, if that's the case, then we would generate just regular toy model and then we would -- on the corgi -- we would add a has one corgi. But we can just do the belongs to for the sake of relationship.
JASON: Okay. If we do belongsTo: Corgi, then this creates a... a one-to-one? Or like a sub-relationship?
BRANDON: It's a sub-relationship. It's like a parent-child.
BRANDON: And you can have many childs.
BRANDON: And then the -- just if you wanted to, you could pass bash-parent equals corgi. And that would nest the routes under the corgi routes. So, here the toys routes, toys are the top level. But if you added that flag, then it would nest it under the corgis.
JASON: Oh. Wait. I screwed something up. Corgi. Corgi. Was it -- that I was supposed to make it plural?
BRANDON: No, it's fine. So with this be must be something they added. Prisma added. It looks like we probably need to fix that to automatically. So, just run -- just save the file. Probably it will automatically format. Because it's complaining about the opposite relationship. So, corgi needs to have a has many --
JASON: Okay. So --
JASON: Toy. And that gives us an array of toys. And then each corgi. Yeah. This is, I think, what I wanted. So, now if I do --
BRANDON: Except toys. On corgi, make it plural.
JASON: Toys. Got it. Okay.
BRANDON: On corgi.
JASON: Right? Here? Toys? Yeah.
BRANDON: It's a delay here.
JASON: I know, we've had a little bit of -- a little bit of I think 4G betrayal. Let's see. Add toys. I'm gonna let Prisma do the work now. Hey! All right. So, now npm run Dev. And we're runnin'. Off to the races. And let's go create a couple toys. So, our toys are only gonna have a name. And I don't know what dog toys are called. So, I'm gonna give them toys that I know the names of. Actually, I need to go to toys, don't I? So, we're gonna create a toy. And they like LEGO. I screwed something up.
BRANDON: So, it doesn't have -- you need a corgiId on there.
JASON: Oh. We need to make that optional, right? Because like a toy could be --
BRANDON: If you want that.
JASON: Yeah. So, I'm just going make this optional. It would be this, right?
JASON: Do they both need to be optional? I assume yes.
JASON: Okay. And the toy is an array so it can be empty. Which is fine. Then I need to migrate one more time. All right. Start it on up again. I screwed that up. Okay. Compiled. Refreshing. All right. So, this one -- this corgi maybe likes LEGO. Let's add another toy. I will add one more. And we're gonna add, I don't -- yeah. Xbox. Is that how you spell Xbox? Probably not. I'm gonna do it anyways. All right. So, if I go back to my list of toys. Now we've got LEGO and Xbox. Those are the toys. And I want to attach these to corgis because what I want to be able to show is that for each corgi, I want them to have a favorite toy. And so, right now we're not seeing the toys at all because it's -- it's not there. But if I edit, I want to be able to choose one of these favorite toys which means we need to add a field. Right? So, I'm gonna go into the corgi form. And I want this to be probably a -- a select field, right? So, how does one --
BRANDON: So, we don't have a labeled select field. But you could copy the labeled text field and convert it into a select field.
JASON: Okay. TSX, right?
BRANDON: And so, you're gonna search for input and change it to select.
JASON: All right. So, labeled select. And that's gonna be a selectElement.
BRANDON: The HTMLSelectElement should have a capital S.
JASON: Yeah. I did a find and replace without thinking about case sensitivity. It's going to be a name, label, type is actually not gonna exist anymore.
JASON: Okay. And then we can get rid of -- we don't need that at all, right? We can just leave that out?
JASON: Correct. We can get rid of that. Okay. So, then let's make a select. And we need to load -- so, I need to load the toys. So, in here... so, I can do a const toys equals -- I need to get the query, right? So -- so, it would be const, loadToys. Did it just -- uh-oh. Okay. And that's gonna be useQuery. And that would be getToys? Look at the magic happening up here. So, we got useQuery. We got getToys. And now load toys should do is that. Why are you yelling? BRAND ON: You need to pass add null, second argument of useQuery. Second argument. Yeah.
JASON: Null. Doesn't like that. Get toys, input. BRANDO N: It gets an object. Sorry. So, I -- I think just --
JASON: Now it's happy. BRANDON: Yep.
JASON: Now my toys will be await, load toys. Is this async?
BRANDON: No, the toys will have the link in already.
JASON: Meaning I don't need to await it?
BRANDON: Correct. This is hooks.
JASON: Oh! This is already toys. I got it. I got it.
JASON: So, inside of our select, I want to do the option. And the value is gonna be, oh, wait. I need to... toys.map toy. Get out of here with your magic. I'm so mad. Get outta here! [ Laughter ] It's so upsetting how much it's getting right. Okay. So, we've got our toy map and then we get the option. And that should do it, I think.
BRANDON: What's that map error?
JASON: Select field, the map error. It says map does not exist on type toys? Yes, you do.
BRANDON: Oh, it's -- inside is an object. So, it's -- it's toys.the toys is an object there. So, toys.toys essentially will give you the map. And it's because we have -- because we support pagination. So, it has extra stuff in there.
JASON: Ah. Okay. So, let's rename that to toyData. Not -- get out of here. Toys, toys. Call it toyData. Not you. Can I learn how to type? Got it! Everybody's happy? All right. Okay. So, now that I've got this, this label select field. I should be able to labeled -- if I can spell it. SelectField name, toys -- no, toy. Toys. I'm gonna leave the options out. Because we -- we're basically kind of probably should have put the options out here. But I don't care.
JASON: So, then toys. All right. Not pretty because it's using the thing. But when I update this, it didn't save.
BRANDON: Oh, you need to update the mutation for update corgi.
JASON: Oh, you're right. You're right. So, we've got a mutation for update corgi and now I need to have toys is gonna be z.array. Is the ID number, this all correct? Yes, ID is a number. Okay. So, we're gonna edit.
BRANDON: Except it's gonna with a single -- this is a single one.
JASON: I thought we were sending in --
BRANDON: We're selecting a single toy for the select box.
JASON: You're right. You're right. Try that one more time. Let's get a -- Xbox, update. Still doesn't like it. What doesn't it like? Xbox, update.
BRANDON: What -- on the... go to the -- your terminal. And look at the logs.
JASON: GetToys. Starting with input.
BRANDON: Does it have the correct ID in there? The other -- it doesn't have the ID for "Toy." So, go back to your labeled -- or, sorry, the corgi form.
JASON: Corgi form. Has the toys labeled select field. Should be using the toy.id.
BRANDON: Oh, you have toys on the label select field. Change it to be toy. Inside the form.
JASON: Inside --
BRANDON: Inside the corgiForm. Toy. Okay. And go back to labeledSelectField. I think I saw a problem. The return from useField, you destructure select and it should still be input.
JASON: Okay. Input. Then I probably screwed up here. Yes. Do we use that anywhere else?
BRANDON: That should be it. I think.
JASON: . Okay. Let's try that one more time.
JASON: All right. So, now we're gonna edit. Did I save the other thing? Toy. Favorite toy. Favorite toy. Xbox. And let's just check this. Are we getting... the select comes in. And we get a value of 1 and 2. Good. Let's update. All right. Toy. But I do think that because it's -- we do need to make it an array, don't we?
BRANDON: Well... in the update, sorry, in the update mutation, what are we doing with that value?
JASON: We are -- we are passing it in unchanged. Which means that we're trying to pass a number into an array field. So, this is my fault. Because I said favorite toy. And then I stored it as a --
BRANDON: So, to set a toy on the corgi, we need to add a new field on that model in the database.
JASON: We did. But I think we did it wrong. So, we have our schema. And we've got the corgi. Then we have toys. But it needs to be toy. And it needs to be one toy.
BRANDON: You can keep what's there and just add a new one. Add a new field that's toy and then a single toy. Let's see... it would be...
JASON: What --
BRANDON: I think toy, toy and also toy ID? So, there's like both the relationship both ways here. We probably should have -- we should have when we first added the relationship.
JASON: Let's just drop toys. I think we'll do it this way. Now we'll have a one-to-one relationship of the toy to like each corgi has one toy. Each toy has one corgi. That's okay. Do I need to add this relation field?
BRANDON: I think it's -- how do we do has 1 in Prisma?
JASON: What don't you like? Relation field, toy. Provide the references. You only have to provide it in 1. Which means that we can drop it off this one because it's already set. Okay. So, then this should do what we want. Okay. So, I'm gonna run the migration.
BRANDON: So, in this case, it would be instead of "Toy" for the mutation input, change it from toy to be toyId.
JASON: Okay. So, I'll have to fix that on the forum input as well. But so to create and apply this migration, yes, I do. Migration canceled? No, I wanted to do that. Yes. Switch to single toy. Good. And then I'll go to my form. And we're passing the toyId. Yes?
JASON: Okay. So, let's start this thing up again. What happened? What? Oh. I ran the wrong command. Ha, ha! Um... do not need to use connect?
BRANDON: You use connect or if you pass toyID correctly, that will work.
JASON: Okay. This looks better. Now we have a toy ID. I'm gonna edit this. This should be ID2, update. No, expected number, received, string. So, I need to cast it as a string because it's a --
BRANDON: So, this is where... so, inside that useField. The second -- inside the object to the second argument to useField, add parse. And then parse -- set parse to a function that just takes a single argument. And then wraps returns number, capital number and the parentheses past that argument. So, it's like value -- takes in value and then returns number of value.
JASON: Oh, I understand. Okay. Okay. Let's make that work. Try this again. Reload the page. There we go. There's our toy ID. Okay. So, then if I go back to -- toys. And I look at Xbox. We don't have a corgi set. Ah-ha! But we would be able to put it back. So, I think -- I think what I am missing and we don't have time to do it, but I just set this up wrong. If I wanted to like a two-way relationship, I would need to do --
BRANDON: To many.
JASON: Yeah. So, there's a way to do that. If somebody wants to find that. Because we did run out of time. Where should we go in the docs to look that up?
BRANDON: Prisma many-to-many would be the best place.
JASON: Prisma many-to-many. That is how we would get to the point where you updated the toy on the corgi, it would also show the toy with like the corgi IDs that were attached to it.
BRANDON: If you could, if you could modify the query quickly. It would show the corgis. So, get -- sorry, getToys. Sorry, getToy.
BRANDON: And then I think inside the where, sorry, next to where. Include. And I think corgi will be in there? Or is it not?
BRANDON: Corgi: True.
JASON: What don't you like? No value exists in the scope for the short hand property "corgi"? Do we have to do like a query for it or something.
BRANDON: You need an extra closing bracket.
BRANDON: Or brace.
JASON: Okay. So, we actually already did do this. Corgi is -- okay. But there is no corgi ID set. So, we would need to get that synced up which is the part that I'm not doing. So, we would need to like add --
BRANDON: There's something goofy there.
JASON: Yeah, we would need to do another query that would be get corgis where toy ID equals 2. Or get all corgis that have toy ID set to 2. The querying part, we would be able to do that the same way here. We're setting aware. We would get like DB.corgis.findAll where toyID equals this ID. The setup of it is all the same as what we've done. And then we would get a list of our corgis. But, I mean, I'm gonna -- I'm not gonna lie, I'm pretty blown away with how far we were able to go. We were able to set up two different models, we were able to put in new things. We were able to get a list of corgis set up where we could dive into these dogs and we were able to customize what we saved. We added an image field, added the ability to class a favorite toy. And all of that is reflected in the output here. And we did all of that without really writing any code. And we didn't have to write any code for the fact that I am logged in. And if I log out and try to do this... go to my corgis, it just bounces me to the login. Or if I go to corgi 1, try to see my one corgi. I have to log in. Or if I try to go to my toys, I have to log in. That's fantastic. That's all the time we have today. Let's do a quick shoutout to the sponsors. Again, we've had Amanda with us all day doing live captioning. She's from White Coat Captioning, with us every week. That's made possible through our sponsors, Netlify, Fauna, Auth0 and Hasura. All kicking in to make this show more accessible. Follow Brandon on the Tweeter. And we have been using Blitz.js today. Brandon, where should someone go if they want to go beyond?
BRANDON: The Blitz.js docs are the hub for everything that you need to know. But I would mention we're an open source project and we love having new contributors. We have a how to contribute guide in our docs that kind of walks you through everything you need to know. We try to organize our issues around what's ready to work on, what's good first issues. And we try to respond quickly to issues and PRs. We would love to have anyone to help contribute and make Blitz the best that it can possibly be.
JASON: Awesome. Thanks so much. And y'all, check out the schedule. We have amazing stuff. Next week, Tim Kadlec coming on, perf auditing. Figuring out why something is slow is very challenging and Tim is one of the best in fact world at doing that. So, he's going to help us do some auditing. Figure out how something is slow. John Breen coming on, a commandline interface in Rust. Just so many good things going on. And I'm now scheduled into a November with incredible people. We will be adding new episodes all the time. If you add the show on Google calendar, they will show up so you can see what's coming and with the link right to the Twitch. Follow on Twitch, you can find us on YouTube. Oh, my goodness, we're out of time. Thank you Brandon so much for hanging out with us today.
BRANDON: You're welcome.
JASON: I really appreciate it. Chat, as always, you're the best. Stay tuned, we will find somebody to raid. We'll see you next time!