Watch on YouTubeThe player loads when you press play.

In this video

In Task Hero, a small React to-do app, I define a task in Prisma and run a database migration. Then I write two backend functions, one to insert a task and one to read the list. Blitz lets me call them from the frontend and keeps the types all the way through, so I don’t have to write a separate API client.

When you add a task, the app needs to show that it’s waiting until the new item appears. I use Suspense for the loading state and invalidate the query after the mutation so the list refreshes.

Follow along with the code.

Read the automatic transcript

YouTube generated these captions and sometimes gets names, punctuation, and code wrong. Check the recording if something reads oddly.

0:00 — Blitz JS is a framework that combines the power of Next.js with a zero API type-safe backend. And today we're going to take a look at how Blitz works and how it can make you a more productive developer than you ever dreamed possible. So, let's dive in. And if you find this video useful, please do hit that like button. It really helps me out as a YouTuber who's just getting started. So, normally when you build a modern full-stack JavaScript application, you have to worry about what tech you're going to use to build the front-end and the same for the backend. And on top of that, you have to worry about how they're going to connect

0:30 — to each other. And this usually involves setting up a huge amount of boilerplate, a couple of repos, and making technical decisions and choosing libraries where you don't necessarily know whether they're the right choice or whether everything will work together nicely. And Ruby on Rails was one of the first frameworks to solve this problem for the web. You keep all of your front-end and backend code in one repo, and they'd interact seamlessly. Plus, Ruby made a lot of decisions for you, but they were the kind of decisions that didn't really matter, no matter what kind of app you were building. That, combined with

1:00 — Ruby's strong conventions, makes Ruby super easy to use and to scale. But there hasn't been a good solution like this for JavaScript heavy front-end apps that use React, until now. So, let's dive in and take a look at what we're going to build today. So, here it is. Our app is called Task Hero, and it's a simple to-do list app. So, I can type in a task over here, and when I hit enter, we're going to add in logic to send that task to the backend and store it in our database. And then we want to render out all of our tasks in this list over here. Pretty simple, but if you had to build

1:30 — this all the way from scratch with create React app and a custom Node API, this would take quite a while. But today, with Blitz, this is going to be super quick. So, the file structure that you're seeing here on the left is a bog-standard Blitz JS app that you can create by running the command line tool Blitz new and then typing in the name of your app and following the prompts. But if you want to follow along with this specific example, you can go ahead and clone the GitHub repo linked down below. So, let's actually dive in and start building some functionality. And as we do that, we're going to get a good overview for how a Blitz app is

2:00 — structured. So, the first thing that we need is some way to store our tasks on the back end, right? So, we head into this DB folder over here, where you can see we have a schema.prisma. So, Blitz has Prisma built in for dealing with the database. And if you've never used Prisma before, well, you're in for a treat. So, we have our database provider, which right now is SQLite, but we could change this to Postgres. So, let's create a new model, which will basically be a table, and let's call it task, cuz we want to store a bunch of tasks, right? So, the first thing that we need to add is a primary key or an

2:30 — ID, right? So, let's call our ID ID, and we're going to say that it's going to be an integer. And we just say by using this ID decorator that this is going to be our unique ID. And by default, we're just going to have it auto increment. Secondly, we want to store the label of the task, right? So, let's say label, and that'll be a string. And we also want to store whether or not the task has been completed. So, let's say completed, and that'll be a boolean. And you'll notice that we get some amazing auto complete here thanks to the Prisma VS Code extension. And then

3:00 — finally, just to sort our tasks, we can add a created at field, and this will be a type of datetime. And again, we can use Prisma's syntax over here to say that the default is just going to be now. Now, I don't know about you, but this looks way better than writing a SQL migration manually. But how do we actually take this and get it into our SQL schema? Well, we simply run blitz prisma migrate dev, and you can see here that it asks us for the name of our migration. So, let's say add task table.

3:30 — So, let's do that, and you can see that it's generated our migration for us and our database. So, now that we have this, how do we actually interact with a DB? Well, let's go to our app folder over here, and you can see that we have a pages folder. Now, our pages folder contains all of our different pages and routes. And if you have used something like Next.js before, you'll see that this file-based routing is very similar over here in Blitz. but we're going to get to routing in a later video. So, for now, let's create a new folder in app called tasks. And keeping all of our

4:00 — logic for tasks collected within this folder makes it easier to group different domains of our app. So, if we added some authentication, we'd add an auth folder over here, right? So, in here, we're going to create a new folder called mutations. So, in Blitz, any folder named mutations or queries, we can actually create files in there that can contain our back-end logic. And we'll look at the difference between the two in a bit. So, let's create a new file within mutations, and we're going to call it insert task .ts. And in this file, we're going to

4:30 — create a function that can insert tasks for us. So, let's say export default async function, cuz it's going to be an async function, and let's call it insert task. And in here, we're going to take in some sort of input. Now, we can actually go ahead and type this input. So, let's just call it insert task input. And what do we want to take in? Well, all we actually want to take in is the label of the task, which will be a string. So, let's type this input. And now, over here, we can go ahead and insert our task. So, let's bring in DB,

5:02 — which we can import from DB. And when I go dot, you'll see that we get a bunch of things auto-completed over here, but the most interesting one being task. So, you can see here that our database API over here is auto-generated based on our Prisma schema. So, we can now go db.task.create, and you can see here when we specify the data to be created, we get auto-complete for all the different fields in our database. So, we're going to say for label, it's going to be input.label. And for completed, we're just going to say false by default. And then, because

5:33 — it's async, we just need to await this. And it's as simple as that. We've just created basically an endpoint on the back end, which we can now call from our front end. But you might wonder, how do we call this from the front end? Does Blitz use REST? Does it use GraphQL? Well, it doesn't actually use either of them. So, let's go back to our page over here, and I'm just going to hide the sidebar. And what we're going to do here is we're going to say const and let's just do some destructuring, and I'm going to bring in the use mutation hook from Blitz. And then, all we're going to do here is import our insert task function from

6:04 — our back end. How cool is that? There's no API to deal with. All we're doing is importing code from the back end into the front end, and Blitz is going to hook all of it up for us. So, now I can say insert task mutation over here. And over here, I'm getting the function that we can call that will end up calling our back end. So, we have this input down here with an on press enter. And in the on press enter, we want to call insert task mutation, right? To insert our task. And over here, we need to pass in our label. So, let's do a new object. And

6:36 — you can see that we get auto complete. What's awesome about this is Blitz gives us type safety. So, basically, the types are shared between the back end and the front end, meaning we don't need to rely on Apollo code gen if you're using GraphQL or worry about Swagger docs or anything like that. It all just hooks up beautifully, and you can be rest assured that nothing's going to break if you rename something, for example. So, let's say label is going to be new task label, cuz that's our piece of state over here that holds the value for our input. We can remove the console log, and then

7:06 — let's also just await it so that we clear the task only once it's been inserted successfully. So, let's see how this works. So, I'm going to say insert task, hit enter, and you can see that after a little bit of a delay, the task was inserted. And that's because our endpoint ran. One other thing that we can do to make this a little bit nicer is add a loading state. So, I'm going to bring in is loading from our use mutation hook over here, and then I'm going to pass it to our input. So, you can see that if is loading is true on our input, it greys out like this. So, let's pass in is loading to it. So, now

7:38 — you can see that when I insert a task, we get a loading state, and then the task gets inserted. How incredibly quick was it to do that all from scratch? Now, next we actually want to display the tasks, right? Well, let's head back into our file structure over here. And in this tasks folder, we're going to create a new folder next to mutations called queries. So, you can compare queries and mutations to rest, where a query is a get request, where you can make that request over and over, and nothing's going to change on the server. And you call a mutation imperatively, like on a button click or on a form

8:08 — submit, not on page load like a query, and it actually makes some sort of change to the database. So, because we now want to fetch data, i.e. make kind of a get request, I'm going to create a new file in queries. And here, we can just say get tasks.ts. And in here, we're going to create a another export default async function get tasks. And what are we going to do in here? Well, it's going to be pretty simple. We're going to import our DB again, and we're going to say DB.task. find many, which is going to find all of

8:39 — them. And I can literally just return the result of it. So, now we have our server-side logic set up. Let's head back to our index page, and actually display out the tasks. So, I'm going to bring in, after some destructuring, use query from Blitz, and I'm going to pass in get tasks, which again, I'm going to import straight from my back end. And then as a second argument, we're going to pass in the parameters for the query. But since we don't have any parameters, it's just going to be undefined. And over here, for our

9:10 — And over here, inside the destructure, we're going to get this tasks variable, which as you can see, because of our enter and type safety, is an array of tasks. And we get a task over here. And let's open up this function. And now you can see that we're actually rendering out our three tasks that we've already inserted. So, now to actually fill it out. So, for the label, we're going to say task. label. And then for this checked value over here, we're going to say task dot completed. Now, one thing over here

9:40 — that's not ideal is we have different names for our checked prop over here and this completed value in the database. But it would make much more sense if they were named the same thing, right? So let's rename it on the back end. So just to show you how absolutely easy this is with Blitz, in schema.prisma, we're going to rename completed to checked. And just to make things easier, I'm going to set the default value to checked false. So now I can go ahead and run Blitz Prisma migrate dev, which is going to run our migration. So now as soon as we go back to index.tsx, you can see here that we get a type error. And

10:10 — immediately, you can see that our new type has come through. How awesome is that? And then finally in our insert task mutation, you can see that we get a type error here as well, but because we have a default value on this, we can actually remove that. But now you see we get the server error and it's telling us to add a suspense fallback. Well, that's because use query over here from Blitz works with suspense, which is freaking awesome. So what we can actually do is head into our page layout component down here. So let's head into layout.tsx. And I'm going to add a suspense boundary just around children cuz this is the

10:41 — contents of our page. So let's do that and see what that looks like. So let's add suspense, which is imported from React. And the fallback is just going to be for now a div which says loading. So now if you refresh, you'll see that we still show our heading because it's outside of the suspense boundary. And then while the tasks are loading, we get this loading div. What I love about this is how easy it makes handling loading states. And doing data fetching with suspense is something that not many frameworks can do right now. So now we have our tasks rendered out. One thing

11:11 — you'll notice though is when I insert a new task and hit enter and it saves, the new task doesn't appear. I actually have to reload for the new task to come in. Well, what we can do is pass an object to this use mutation and we can say when it succeeds, so on success, we're going to run this async function where we're going to await a call to invalidate query which we'll import from Blitz. So, what this is going to do is we're going to insert the task, it's going to succeed, and then we're going to tell Blitz to rerun the get tasks query, and only then is our

11:42 — loading state going to be set to false. So, I can go hello five, and you can see that the loading state shows all the way up to the point where the task has been inserted. How cool is that? So, in this video we've managed to build a full-stack app with Blitz.js, complete with a database, a type-safe back end, and a Next.js front end in under 10 minutes. I hope this video has conveyed the power of Blitz, and I'm excited for you to check it out, and let me know what you think. This was obviously a super quick, rather fast-paced video, but it was just to give you an overview

12:12 — and get you started. But, there's a lot more to cover on Blitz. So, let me know down below in the comments if you want to see more Blitz.js content and tutorials, and be sure to subscribe here to my newsletter and to make sure you don't miss them when I upload new Blitz tutorials. Finally, if you did find this video useful, please do give the video a like. Like I said, it really helps me out. And like I said, subscribe if you want to see more Blitz content, and I'll see you in the next one. Cheers.

Originally published on Jacques Blom.