Blitz.js is the Fullstack React Framework. It's heavily inspired Ru on Rails and is focused on making you as productive as possible. It's built on Next.js and adds all the missing pieces you need for building a fullstack app with a database. By far the biggest innovation of Blitz is the new "Zero-API" data layer that abstracts away the API so you don't have to mess with REST or GraphQL APIs!
Simon will introduce all the important parts & guide you through getting started with Blitz, so you'll know if you might want to use it or not.
Build Fullstack Apps in Record Time with Blitz.js
Blitz.js is the Fullstack React Framework. It's heavily inspired Ru on Rails and is focused on making you as productive as possible. It's built on Next.js and adds all the missing pieces you need for building a fullstack app with a database. By far the biggest innovation of Blitz is the new "Zero-API" data layer that abstracts away the API so you don't have to mess with REST or GraphQL APIs!
AI Generated Video Summary
Blitz.js is a full stack React framework that eliminates the need for REST or GraphQL. It provides a zero API data layer and allows developers to focus on building applications. Blitz offers features like React Suspense for asynchronous data loading, Prisma Studio for immediate updates, and a routes manifest for type-safe links. It also supports writing to the database through mutations and provides default caching with React Query.
1. Introduction to Blitz.js
My name is Simon Knott, a developer tooling engineer. I work on tools to help you build better applications. I am the creator and developer of Queral, a job queuing solution for serverless deployments. I'm also a level two maintainer of Blitz.js, a full stack React framework. Blitz is meant for building applications, not just websites. It's a batteries-included framework inspired by Ruby on Rails and built on Next.js. It features a zero API data layer abstraction that eliminates the need for REST or GraphQL. Today, we'll learn how to use Blitz by building a project and exploring its important features.
This is me. My name is Simon Knott. I'm a developer tooling engineer, which means I work on tools that help you build better applications and build your applications better. By day, I am the creator and developer of Queral. Queral is a wonderful tool, a job queuing solution for serverless deployments. I happen to really like it. I hope you like it. Maybe you'll use it someday.
And I'm also a level two maintainer of Blitz.js, which is the reason I'm here today. And let's talk about Blitz.js, which is the reason you are here today. Blitz is the website of Blitz. Blitz calls itself the full stack React framework. And there's a couple of things in there. What does that mean? React framework. Blitz is a framework that you can use to build applications using React. And the full stack means that it's not only about the front end part of the thing, which you'd, for example, find in create React app or in Next.js. But it deals with the full stack from the front end layer to the back end layer, including the database. And Blitz is really meant to build applications as opposed to websites or document-heavy sites. I like to make this distinction from time to time where there's these kinds of websites that are more suited to like static generators. They don't change a lot. Things where you'd use Jackal or Next.js or whatever. And then there's applications that have a lot of interactive things, maybe even a database. And Blitz is very much for that. And then on the website, it has this subtext here, which includes a lot of things that are helpful to know about Blitz. It says, Blitz is a batteries-included framework that's inspired by Ruby on Rails, is built on Next.js, and features a zero API data layer abstraction that eliminates the need for REST or GraphQL. There's a lot in here to unpack. So what does batteries-included framework mean? That means that Blitz will give you most of the things you need to build an application by default. It brings everything with it. It's inspired by Ruby on Rails, not in the sense that we try to recreate Ruby on Rails, but we are very... Well, Blitz is very... It tries to have the same productiveness, the same productivity that Ruby on Rails enabled back in the days, but with a more modern feature. It achieves that by being built on Next.js. A couple of months ago, in the beginning, Blitz was really a compiled to Next.js framework, so we took your Blitz.js code, and then we compiled that into a Next.js application. That's changed a bit. Blitz now has its own Next.js fork. But what still holds true is that Blitz really is an extension of Next.js, so to speak. And everything that works in Next.js will also work in Blitz. And then there's this last thing, which really is, I think, the main feature of Blitz really, which is the zero API data layer abstraction. That's what we'll see in a bit. And that zero API data layer abstraction allows you to create applications and work with data from the database without having to think about building APIs, rather than the rest for GraphQL. And eliminating the need for GraphQL obviously is a shot against Redwood, which is similar to Blitz.js, but takes a slightly different stance in using a lot of GraphQL or promoting the use of GraphQL. We have the zero API data layer, and I will show you what that is.
All right. I think the easiest way for us to learn how to use Blitz is to actually build a Blitz project. And the rough plan for today is so we have three hours together, right? And we'll roughly divided into three for the next 60 minutes, maybe 70, maybe 75. I will give you an introduction into Blitz.js. I will build, I will showcase how to build a prototype for a for a conference workshop sign up page and I will try to show you I will show you the most important features of Blitz that way. Then we'll take a short break just so you can refresh, get some water, get some fresh air. And then we'll go into breakout sessions and you will, in pairs of two or three or four people, we'll see.
2. Breakout Sessions and Creating a Blitz Project
And then we'll go into breakout sessions to work on your own small Blitz applications. Afterwards, we'll come together as a group to discuss what you've built, experiences with Blitz, and answer further questions. Let's start creating our application by installing the Blitz CLI and creating a new Blitz project. The newly created project comes with TypeScript pre-installed and a variety of pre-configured files for a better development experience.
And then we'll go into breakout sessions and you will, in pairs of two or three or four people, we'll see. Work on your own small Blitz applications. We'll do that for roughly an hour, twenty or so. And I will go around and answer any questions that might pop up, help you with any problems you have. So the goal for that is for you to really get to grips with blitz and just try to use it. And a lot of the time, that is when you really learn what some tech feels like. And then afterwards, we'll come together again as a group and talk about what you've built, talk about experiences you've had with Blitz and answer further questions.
Okay? Cool. So, let's start creating our application. I hope this is big enough for you to see. If not, let me know. So, let me just go to my temp directory. To use Blitz, you can install the Blitz CLI. You do that by running npm install, save globally, and then Blitz. I won't do that because I already did it and it will take a while. But after you've done that, you have the Blitz CLI installed locally. And that says error. We're not in a Blitz project. But you will see the Blitz version, that I am currently on Blitz 0.41.1 which I think is the most current version. And now to create a new Blitz project, you can run Blitz new and then the name of your project. We'll call it React Advanced Workshops. And now it will do some magic. It will ask you a couple things. What package manager do you want to use? We will use npm. What form library do you want to use? You'll see why that matters in a bit. Let's just use the recommended one. And then it will generate a couple of files for us. And while it now installs npm dependencies, let's open this. No, let's not open that directory. Let's open the generated directory in vscode.
3. Exploring Folders and Database Schema
Then what's interesting is the DB folder and the app folder. The DB folder contains a schema.prismo file. Prisma is a database client that comes with Blitz by default. The app folder contains pre-generated files for the development server and the pre-generated page. The file-based routing allows for easy customization of pages. The default database schema includes tables for user, session, and token for authentication.
Then what's interesting is these folders up top, some test folder with some pre-generated utilities for you to test. Public folder, that is the same one that Next.js also have, just with sort of static files. And then the mailers and integrations folders, we can ignore those. Those are just two conventions on how to build mailing things.
And then there's this app folder that contains a couple of things. And before we look into that, let's start our Blitz development server. We do that by running Blitz dev in the main directory, similar to how you would run Next Dev or something. This will start up a development server. And now if we open up Safari, we can go to localhost 3000. And we have this wonderful pre-generated page. This page lives in app slash pages slash index to TSX. If you've used Next before, you might recognize this. So this is file-based routing. If the file lives at index.tsx the pages folder, that means it will be available at the index route. If it's called workshops, it will be available at slash workshops. And in that you can see that it exports as default a React component. This is just some blitz special type for React components, but this could just well be some React component.
And if we want to change something about this, let's not put congrats here but congratulations. Let's save them on characters here. We see that this has auto reload or hot reloading or whatever that feature is called. So this is like the file that generates this page. And there's a couple other files, the 404 page, which is shown when you go to a not found page obviously, and that app and document file. We don't need to talk about that. You probably won't need to look into that today. And then there's a couple other things here. There's this core thing which contains some of the components you see, lots of pre-generated ones. And then this user's directory, which we will look into later. Ravi wrote it's fast refresh, I guess. Oh, yeah. I think it's called fast refresh. Thank you. And this is the general photo structure. What's important for now is that in the app folder, there's a pages folder. And in the pages folder, you can place new pages. Let's look into this database schema here. Bits by default comes with a schema for you that does some of the authentication things. So it has a user table, a session table and a token table. This just works with the pre-generated authentication logic that Blitz gives you. So you might have seen these buttons, sign up and log in. We'll just sign up, let's put in some email.
4. Building Workshop List and Creating API
We want to build an application that shows a list of upcoming workshops and allows people to sign up. We'll add a new model called Workshop with fields for ID, name, date, and description. We'll use Prisma as our database client and generate dummy data using Prisma Studio. To display the workshops, we'll create a new page and define a function called workshops list. We'll return a header and an unordered list with dummy data. To get the data from the database, we'll need to create an API.
React advanced. Password is test, test, test. Create account. Nice. Now we have an account. And this logic is not something that's built into Blitz, but we just generate it for you. So, there's, like, some password changing or reset methods, and you can just change it if you want. But this is useful for today. And we won't need to look into this. But what we want to do is we want to build an application that shows a list of upcoming workshops, for example, at the React Advanced Conference, and that allows people to sign up for these workshops. So, we will add a new model called Workshop. We'll give it an ID. For now it's an integer. We'll mark that as an ID field and we'll auto increment it by default. We will give the Workshop a name, which is a string, and we'll maybe, this is GitHub Copilot speaking, we will give it a date, which is a date time. We will give it a, let's give it a description, which is a string, and I think that's it for the moment. And then what we do is we open up a terminal and run blitz Prisma generate. That's just something you need to run after you updated your schema. We'll talk about why you need to do that later. And just as a reminder here, Prisma comes pre bundled with Blitz. But if you want, you can swap that out for some entirely different database client. You can use whatever you want, but we at Blitz, we're big fans of Prisma. So that's why we bundle it. All right. And we want to show a list of these workshops. I think a good idea is to just put in some dummy data first. To do that, we can use the Prisma Studio. If we run that, it will open up a browser tab and have this very nice... Why doesn't it want that? Oh, I had that before. I think we need to run Blitz Prisma migrate. Maybe. Why is that still a thing? DB push, is that a thing? Yes, that works better. Apparently in AT, I need to do some more digging into how Prisma actually works, so you always forget what commands to run in what order. And this is like a very easy to use database UI. And we see in the user table, we have that user that we created, react-advanced-assignment-knob.de user. Let's add a new workshop. Add record, give it a name, uh, let's intro, the date is 2021, 10, 28, 16, no, at 15 UTC, the description is, let's learn everything there is to, and save the record. And let's just add another one. I think there is one on, uh, editor experiences, also today. Let's just have an hour later, this also seems interesting, just so we have to, to rise. Didn't it put anything here? Okay. And now we want to, we want to display these values. And what we'll do for that is we'll create a new page in the pages folder, we'll call it workshops.ts.tsx because we want to use jsx apparently, obviously, and we will define a function called workshops list. We'll export it as default and in that workshops list we'll return, let's call this add a main thing as a header and then an unordered list just with some dummy data in here and then add it to our experience. And now if we open up a local host slash workshops, we will see a wonderful list of workshops, but obviously we want to get this like we don't want to have this um we don't want to have this hard-coded but we want to get this from some database from our database. And now think a second what would you do to to build this with for example NexJS or with anything else. You would most probably uh think about creating some kind of an API. Maybe not in this workshop's case. This is the site that we're currently building could well be some statically generated document but imagine we're building a more complicated application here. So we'd most probably create some kind of an API.
5. Choosing API Architecture
There are several questions you need to answer when deciding on the API architecture for your application. REST, GraphQL, and RPC are some options to consider. While these choices may seem important, in most cases, the choice between REST and GraphQL doesn't significantly impact the user experience. However, it is still a crucial decision to make.
And there's a couple questions you need to answer when you when you do that. Do you want to have a REST API? Do you use GraphQL? Maybe is it RPC? If you use REST, how are your routes called? What HTTP methods did you use? How does authentication work? And all these kinds of things that are really hard questions to think about, but if you think about the grand scheme of things and what that means for your application, it doesn't really matter a lot. For most applications, there will never be a customer that comes to you and says, oh, I love that you built this using GraphQL. It makes my experience so much better. That just won't happen. And in the end, whether you use REST or GraphQL, for the most cases, is not a big difference, but still, it's a big decision to make.
6. Fetching Data with Blitz
And when Brandon Beyer, the original creator of Blitz, asked how data fetching would look if it was easy, Blitz provides a simple solution. By creating a magic folder named queries and defining a function to fetch data from the database, we can easily call this function from the frontend. Blitz automatically generates an API endpoint for the function, making it a zero API data layer. This allows developers to focus on building applications without worrying about the backend implementation.
And when Brandon Beyer, who's like the original creator of Blitz, when he first built Blitz, he asked this question of how would this look like? How would data fetching look like if it was easy? What is the easiest developer API we can have for that? And well, to fetch data, what do you do? You just call code. That's what you do. You just call some function. That's what you can do with Blitz. So let's do that.
We will go into this app folder, and we will create a new magic folder called queries. And it's important that it has this name because this is a magic name. I'll talk about that more later. In the queries folder, we'll create a file called getworkshops. The name doesn't really matter here. And in getworkshops, we can define a nice function called getworkshops. We'll export it as default. And in that function, we can just import a database from DB. This import from DB is just a shortcut to import db slash index.tsx. So if I put a slash here, you see that it has all the files in here, but we'll just import from DB. And then inside of getworkshops, we can do mdb.workshop.findmany. And we'll find, we'll just find all the workshops. We'll await the result and return it. The nice thing about Prisma is, and if you look at the result type of this, it will, does it say something nice? I think it says something nice. It will now return an array of workshops. That's the very nice thing about Prisma is that it gives you type safety really easily. And so now this is a function that gives you all workshops. And now, how do we call that from the front end? To be honest, it's really easy. We can just import getworkshops from Query slash getworkshops. And now you have a TypeScript function that you can just use. So what you could do is let's just do getworkshops. And like this returns a promise. And once we, once we have that, let's just console.log the results, just to show you that this works. So I think your development server just crashed because we didn't restart it after, after updating the Prisma schema. That's something we need to do because it needs to reload the Node.js files. And it can't do that for some reason because Prisma does some not super Node conventional things. But now that's restarted, this should work. And if we look into the console, we can see that it contains the two arrays queried from the database.
Okay, so this seems very magic. How does this work? We have this function in queries slash get workshops.ts. That is just a very normal plain TypeScript function. It looks very normal. And we just import that in our React component, which can just call it. And what will happen is that this thing here only runs in the frontend, while this thing here only runs in the backend. So how does this magic work? Like you can just import a backend function and use it. But with Blitz, you can. So I told you that this query's name is magic. And the way this works is that Blitz will check your imports. And whenever it finds an import from a folder named queries, it will replace this import and instead write just some pseudocode here, async function, get workshops, return, fetch slash API slash query slash get workshops. And it will also automatically generate an API endpoint for this. So the way this works is that Blitz recognizes these imports and generates an API for you, right? It's an auto-generated RPC API, because it's pretty much a remote procedure call. And that is what Blitz means when it says zero API data layer. This is not zero API because there is no API, but because you don't need to care about it. Similar to how serverless is not about not having servers, but about not caring for servers, or not having to deal with them. All right.
7. Using React Suspense for Asynchronous Data Loading
We can just call functions as normal TypeScript imports, enabling type checking. With Blitz, we use the use query hook from React query to fetch data from the API. React Suspense is a beautiful feature that allows components to suspend rendering until data is ready. It ensures a synchronous-looking code while handling asynchronous operations.
So, we have found that we have functions that we can just call. And the nice thing is because these are just normal TypeScript imports, our types work. That is a thing that I personally find to be the most beautiful about this 0API data layer design. Because in the code, this is just like normal function import, and that enables TypeScript to do all its normal type checking work. Normally with a REST API, you need to do some serious work to get some notion of type safety. With that, not at all.
Don't need to think about it. We know React, it can be a bit weird to work with promises inside of components, so we could do some standard user fact thing here and save that into some state. We won't do that, but instead we use the use query hook from Blitz. This is built upon React query, which you may have heard of. React query is a really great abstraction around querying APIs and let's just make use of it. That is a common theme you find at Blitz. We tend to, if we find a really good existing library, we tend to integrate it well, as opposed to rebuilding it ourselves.
Now, what we can do is we can write use query, get workshops, and I think we don't need to give it any parameters, but we put undefined in there, and then this will return an array of workshops. And as you can see again, this has the perfect TypeScript type. Now if we want to show this, let's just map over the workshops. So we have a list item where we put in the workshop name. Now if I save this, you will see that this works and it shows the right things. But once I reload this page, it will hopefully throw an error. Oh, it even catched the error. This threw an error. Okay, what is this? Let's read through that error, because it's really interesting. It says a React component suspended while rendering, but no fallback UI was specified. Add a suspend fallback component high in the tree to provide a loading indicator or placeholder to display. What does this mean? Maybe some of you have heard of React suspense. That's a new upcoming feature. So there is, there's been suspense for a while for loading, for loading components. But in an upcoming React version, there will be React suspense for data loading. And what is this suspense? It is one of the most beautiful things of React, in my opinion. So if we look at this code, this code looks very synchronized. We call use query, give it get workshops and it will immediately return an array of workshops. This code looks like this is there immediately. But in reality, calling the get workshops API, getting the data that takes a while, this is not a synchronous operation. You can see that by this function returning promise. So how does use query use get workshops and then get back a non-promise array of workshops? How does that work? That is where React Suspense comes in. So the way this works is react will try to render this function. It will go through this hook. And this hook will kick off this query. It will call the API, the generated API, try to get some workshops, and then we'll see, oh, shoot, I don't have data. I can't give anything back. I am not ready to render. And it will say, please stop React. I suspend. I am not ready yet. That is what use query says. It's implemented by use query, like throwing a special error, I think. But what you need to know is that use query can suspend rendering. It can say, please React, do not render this component yet. I am not ready yet. But I will tell you when I'm ready.
8. Building Details Page for Workshops
And until I'm ready, please suspend. Please show a fallback. A React component suspended while rendering. But no fallback UI was specified. So it is suspended because use query didn't yet have data. We can add that by wrapping it into a suspense boundary. This is how you query data. Another cool feature is Prisma Studio, which allows for immediate updates. Use query has instances where it tries to poll for new data. You don't need to deal with live data, Use query will handle it. You can also set options for refreshing data. Now let's build a details page for workshops. We create a new page with a parameterized route and a function to display the details. The page will show 'hello, world' for now.
And until I'm ready, please suspend. Please show a fallback. And that is what React says. A React component suspended while rendering. But no fallback UI was specified. So it is suspended because use query didn't yet have data and what React tries to do is it tries to find the nearest suspense boundary that is the suspense fallback component. And we can add that by not exporting the workshops display, but by wrapping it into a suspense boundary. So you can do that by importing suspense from React and then adding a suspense boundary that has loading as the fallback. Why is this oh we need to assign that to some value. Yeah, let's just ignore this. And now what you see is that this works and it will show loading really for a second here while it loads from the backend. Cool. So that is how you query data. I want to show you another very cool feature. So this is Prisma Studio. Is this still working? Yes, it is. I will now change something in here. I will change this to editor experiences extravagant. Just to have another word with me in there. And now when I press save on the left side, nothing happens, right? But when I go into this thing and now I will click into it, you will see that it immediately updates. And what does this do? So use query does not do, like it doesn't have a live subscription to always get the newest value. But it tries to do some pseudo live value thing where it has a couple of instances or a couple of moments where it tries to poll for new data. For example, when I re-open this tab, because we want to have the feeling of live data without actually having to deal with the WebSocket stuff. So whenever a user comes in and reopens the tab, we automatically update the values in here. And that is something that you don't need to deal with. Use query will do that on its own. Another thing you can do is you can post it with options and say, I want to refetch this every 200 milliseconds or something. And then what you see here in the background is that this will kick off new refresh. And you call every 200 milliseconds. Or you can say that I want to do some other things. It's not important right now. All right. So, this is how we do queries. Now, let's build a details page for these workshops. So, we will add a new page. What we want to have is we want to go to localized workshops and then the idea of a workshop. And we want to see a details page for this workshop. And so, what we do for this is we want to create like a page at this route. So, we add a directory here in pages, called workshops. And then we add a new file. And we call that ID.tsx. And we put the ID into square brackets. Those square brackets mean that this is a parameter. This is like a parameterized route. And here, anything could go in here. One, two, three, or foo, or bar, or whatever. And in here, again, we write a function, called maybe workshop details. That will return hello, world, for now. And we will export that as default. Now, if we go to that site, we can see that we have hello, world.
9. Accessing Parameter and Showing Workshop Details
Now we want to have access to the parameter and show workshop details. We'll use the getWorkshopSingular query with the ID parameter. We'll add a suspense boundary in the app file to handle the lack of an ID. If there's no workshop, we'll display 'not found'. If there's a workshop, we'll show the name, description, and date.
Now, what we wanna do is we wanna have access to what parameter this is. For that, we can import a special hook. Let's call that, what that hook is called use param. And we give that the name of the parameter we wanna refer to. And then we can also give it a return type. Which just makes sure that this has derived type. And let's just try if this works. Hello world ID, we go to some other ID, we see that we have a nice parameter here.
And we maybe also want to show actual workshop details. So we'll need to build another query. So we need to fetch the workshops details, right? So that's what we use another query for. We'll call that getWorkshopSingular. And let's just copy this thing here. You could rename it. And now this gets one parameter called ID. And then instead of finding many, we find a unique workshop where the ID is the ID that's being put in. And we will now use the query for Blitz again. So we'll import useQuery. We'll also import the queries or the query query slash kind of workshop. I know we pause. I think I should turn off Copilot. This is a bit disturbing. Pilot toggle off disabling globally for now. And we will put in the ID parameter here. This will now say that the type doesn't work. Because type only find is not assignable to type number. That happens because of some internal ways of how Next.js works. There may be instances where this component is rendered without having an ID available. And we'll just put some placeholder in there for now.
And what we see is that this again throws the same error. It says we don't have a suspense boundary. And now what we'll do instead of wrapping every single component with that suspense boundary, we'll just be lazy and go into this app file, which is the main wrapping component. And we'll add a central suspense boundary around here that will now work for every single page. I don't think this is something you should do on the actual application code because you should have different suspense boundaries that show actually useful stuff. But for now, this is fine. And so we'll get back here a workshop. This could be a workshop or it could be no. So let's do if there's no workshop, that's return not find. And if there's a workshop, let's actually show some nice things. Let's add h1 tag. Can we format this nicely? This has workshop.name. That's a paragraph that has a description and let's add a date field. What's the time? Time. Workshop.date. To isostring. So it will say not found for 1, 2, 1, 2, 3. But if we go to number 1, this works. And if we go to number 2, this works too. And if we go to number 3, that doesn't exist, it will say not found because it uses this here. It might happen a lot that you don't actually want to, that you have queries that could return nothing because these IDs could be nonsense.
10. Checking Workshop Existence
We can check if the workshop exists in the get workshop query and throw a not found error if it doesn't. This allows us to focus on the happy path and avoid cluttering the component with unnecessary conditionals.
And because that is quite usual, and you wouldn't want to stay at the same page, but you'd expect people to redirect it to 404. We can do a nice thing here. Inside of a get workshop query, we can check if the workshop actually exists. And if there's no workshop, we will throw a new not found error. And if there's one, we'll return it. And this changes things because now, the return type of get workshop is always a workshop. If there's no workshop, it will not return, but it will throw a not found error. And in here, we also know that this now will never be null. We can return this logic here. And if you reload this, it will automatically redirect you to the 404 page. And what I really like about this is that you can think in the happy path, right? This component isn't cluttered with a lot of conditionals about what happens if the workshop doesn't exist, all these things. But it only includes the part that you actually want to think about the happy one. All right.
11. Building Links and Navigation
We can create links in Blitz.js by wrapping elements with an anchor tag and using the link component. This allows for instant rendering and prefetching of assets, improving navigation speed. We'll explore more features of links later on.
And we have this works for number two. If we go to number three, we'll get a 404. Cool. Amazing. Whatever this was, that was interesting. Cool. So that is how we can build details page. And we'd likely also want to have links here, right? So let's go back to our workshops page. And let's wrap that list item with an anchor, and put in href to slash workshops plus workshop.id. And I think we also need to raise up the key for it here. And now we have links. Nice. What's interesting to see here, maybe you notice that if I press on here, it will reload the full page. And if you've worked with Next.js before, you know that that is not necessary at least with Next.js. And in Next.js you use the link component, the same components available with Plits. So if you wrap this anchor in a link component and move up these properties again, then you will have instant writing here. We will immediately show you all the data. And what's nice about this is that not only it doesn't do a full HTML reload, but that it also will automatically prefetch any assets it needs as soon as this link is rendered. So if we go to the index page and then open up the network tab, let's go to slash workshops. And you see that, where is it? Or maybe it already had it in cache, but as soon as this link is rendered, it would immediately prefetch this subsequent page to just make navigation smoother faster and whatnot. Right. So this is linking. There's another feature to links that I'd like to show you later on. But just keep that in mind. We'll get back to that. And if I forget it, please remind me that I showed it to you.
12. Adding Participants and Subscriptions
Now we have a list of workshops and want people to be able to subscribe. We add a participants relation, a many-to-many relationship. We include participants in the query, show their emails, and display them in a list. We add a subscribe button and use mutations to write to the database.
All right. So now we have a list of workshops. We can navigate into it. And maybe we also want people to be able to subscribe to these workshops. So we will go back into our schema here. And we'll add a new relation. We'll see that we will add a participants array or participant's relation, which is a list of users. For every workshop, there's a list of participants. And for every user, there's a list of workshops. So this is a many-to-many relationship.
And we will stop our development server. We'll run Blitz, Prisma. Generate, no Blitz Prisma DB push to update the database. And we will restart our development server. And now what we can do is let's start by showing the participants. So, I think we need to do here, let's just check if this is true, we'll need to say include and we want to include participants. Oh, it is automatically reloaded. Prisma, that's nice. I was just thinking about sometimes you need to restart the TypeScript development server if you make changes to your schema.prisma because TypeScript needs to re-index their generated typings, but that wasn't the case now. Which I am very fine with. And maybe we don't want to include everything about participants but only will include selected fields, namely, let's just keep it at that name. I think the name is totally fine for what we want to do. So, what you can now see is that this returns a promise of an array of workshops and then an array of participant objects which have a name. I think there's no names in our database, so let's use emails instead.
And in our workshops file, we can add a... Oh, no, that's a workshops list. In our workshop details file, we'll add a header, participants. We'll add an unordered list in there and for every... Oh, wait, I added... I changed the wrong query. I didn't need to change this query, but I need to change the GetWorkshop query. So, for every participant we show a list item that has the participant's email and those id we'll just also use the participant's email. Why doesn't this... oh, it's called key, not id. This is now saying undefined is not an object. Oh, no, it's working. All right. All right, we have a list of participants. The question is how do you actually subscribe to an event? And for that, let's add another button. That's not an upper button but a first button. Let's give it the title subscribe. And when that button is clicked, we'll just for the moment alert I want to subscribe. So this works. All right. We've talked about queries, right? Queries are for fetching data from the database. It's for reading. Now what we want to do is we want to write to the database. And for that, we can't use queries because queries are for reading. But we want to use so-called mutations. And they work very similar to queries.
13. Writing to the Database in Blitz
To write to the database in Blitz, we create a magic folder called mutations and define an async function called subscribe to workshop. We update the workshop by connecting the participant with the user ID of the requesting browser. We use the context.session.authorize function to ensure the user is authorized. After calling the mutation, we update the data displayed by using the workshop meta.refetch function. This is how you write to the database in Blitz.
So instead of using this magic folder called queries, we'll create another magic folder called mutations. Instead of mutations, we'll create a file called subscribe to workshop. It is the workshops singular. And we will define an async function called subscribe to workshop. We'll export it as default. It will get a workshop ID as its parameter. And then we will again, as we know it, import database from DB and call db.workshop.update. We'll update workshop where the ID is the workshop ID we got. And we will write some data. We will update the participants and we'll connect the participant where the ID is. All right. What ID? We need to find a way to get the user ID of the requesting browser, or the requesting user. If we look at the main page, I think there's a user ID written here. My user ID is one. But we need to get the user ID of the user that is calling in. And for that, this mutation is always passed a second parameter. We'll get a type from blitz that shows what's in there. That parameter is called context. And under context, there's the session object. That session object contains information about the session of the client. For example, the user ID. And that user ID could be number or null. What you could do is say if there's no user ID, we'll just throw a new error. So unauthorized. Get out. But as always, there is a neater way. What you can do is you can call context.session.authorize. And once you've done that, you see that the type of this, this returns asserts this is authenticated session context. Once you've done that, you'll see that the type of user ID changes. The user ID after the call is a number. The user ID before the call is number on null. So this authorize is pretty cool. That's a nifty feature in TypeScript. By calling authorize, you know that this will be number. Because if this if the user is not authorized, if they don't have a user ID, then this call here will immediately throw an error. And now what we can do is we can just put in user ID here and we'll await that. And that's everything we need to do to add some value to that workshop. So now what we can do instead of calling window.alert, we can import from mutations, our subscribe to workshop mutation and we can, I think you need, nowadays you need to use the invoke helper function from let's, you can say invoke the subscribe to workshop mutation with the parameter id and wait that and then oh I guess that's it. Reload the page, we say subscribe and nothing will change but once we reload the page we see that there now is a participant. So why didn't anything change? Because the data that's shown here comes from this query and Blitz has no way of knowing that calling this mutation changed something about the value of this query. So what we need to do is we need to make sure that once this mutation was run that we update this thing here. And so we'll get the second parameter here which we'll call workshop meta. On our map we can say workshop meta.refetch, please. Please update this value and now if we open up our studio, it's still running. Yes, it is running localhost 5555 and we feels can we, I think we need to reload studio because we changed something up the data schema. We can remove this participant, just check things again. And now if we open this press subscribe, we see that things update. All right. So that is how you write to the database in Blitz. Similar to the use query helper, there's also a use mutation helper.
14. Overview of Blitz Features
You can use it if you want. It gives you some nice things, but it's not strictly necessary. So that's how we build a very, very basic workshops tracking application. I wanna show you this one other feature about links. We have the so-called routes manifest, a type-safe way of building links to other pages. Another thing is the Blitz repple, similar to Ruby on Rails console. You can use this to test your queries and debug things. Blitz install commands can be used to install libraries or set up projects with recipes. This is a rough overview of what Blitz can do, focusing on the zero API data layer.
You can use it if you want. It gives you some nice things, but it's not strictly necessary. All right. So that's how we build a very, very basic workshops tracking application in 40 minutes, I guess, 45 minutes. I promised you, I wanna show you this one other feature about links.
So take a look at this line 14 here, where we use the href, like a string-based link to another page. What might happen is that you change the route of a page and now your links don't work anymore. And there's no way for you to, or it's not super obvious that that broke something. And what we have in Blitz is we have the so-called routes manifest. You can import from Blitz, Blitz the route object, and on the route object, there will be functions that help you for every single page you have, for example, for a workshop details page. So what you can do is you can put into this href, instead of putting a string in here, you can say routes that workshop details, and now this will say to you, you didn't put an ID into here, and you will parse it, the workshop ID here. So this is like a type safe way of building links to other pages. I know this still works the very same way.
Yes. There is another thing that I'd like to show you about Blitz, which is the Blitz repple. I think you can, let me check, I don't remember how to open it to be honest. You open it using Blitz console, and what you can use this for, this is similar to Ruby on Rails console. You can use this to test, okay, why doesn't this, why doesn't it like this? Property session does not exist. Okay, let's just ignore it. You can use this to test your queries. You can, for example, do wait db.project.findMany, and then it will return, oh no, project db.workshop. We'll return all your projects, db.user, what we can do. We could do these things, or we could also call our queries. So for example, await getWorkshops, then that will test our queries. And this can be really helpful to working development so you can just use this to debug things.
Another thing is the Blitz install commands, which you can use to install some libraries or to set up some projects that we have recipes for. So for example, if you run Blitz install Tailwind, that will take a while because it's cloning the recipe. Tailwind. And it takes a while and it will set up some things so that we can use Tailwind. Tailwind. Telled you some things about what it will do. This will install all necessary dependencies and configure Tailwind. You can press enter to install dependencies. Then it will create some config files, write some style sheets. It will even update some of our existing files to add support for Tailwind. And then once we restart our development server, we will be able to style things using Tailwind. Let's reload this page. Oh, so if you see this already looks a bit different because Tailwind reverts all existing styling. But we could now, for example, say that this, what file are we in? We're in the ID file. That this had a one, should have the class name, background, green. So this is how Tailwind works. I always forget how to, or how these class names are called. Background color, BG, minus green, minus 500. And now we have a green background, wonderful. So you could use these Blitz recipes to really fast set up certain libraries or frameworks and tools that you might wanna use. Cool. So I think that is a rough overview of what Blitz can do. We've spent the most time on the zero API data layer because that is really the main differentiating factor with Blitz. That's why Blitz is amazing or why I like it. Alrighty.
15. Workshop Break and Caching
During the workshop break, the speaker addresses the audience and invites questions about Blitz. They mention that React Query provides some default caching, storing query results in the browser's local storage to show stale data while loading. The speaker emphasizes the importance of this feature and welcomes further questions.
It's 15 past the mic, look. So ready to get back into the workshop. Don't know about you. It was good for me to have that short break. Before we get into record rooms, are there any questions you have right now about Blitz? Questions that I can answer? Is there any caching of the API database calls? I think there is some form of caching by default. Yes, React Query does some. But any complex caching would be done by yourself. I think what React Query for example does is it will store the result of the query inside browsers local storage, so that when the page is opened up again, then it will be able to use stale data to show something while it's loading. Which is quite nice to give at least a bit of perceived performance. I think it's very good to know. Good to know. Definitely. Any other questions?