Introduction
Hello everyone, today we will be developing and deploying a notes app using Blitz JS framework.
We will be using the following tools, resources and frameworks on a higher level
Blitz JSTailwindRailway
What is Blitz JS?
Blitz JS is an amazing JavaScript framework that gives you a full stack web application, instead of just a front end or a back end. It is amazingly well type safe, it added the amazing features to the Next JS like authentication, middleware , direct database access and much more.
Advantages that Blitz JS provides out of the box
- Full stack instead of front end.
- Data Layer
- Built In Authentication System
- Code Scaffolding
- Recipes
- New App Development
- Route Manifest and can use
pagesfolder in which ever folder needed.
Miscellaneous
To reduce the redundant code written every time we use either Code generation or Code Scaffolding techniques.
Code generation is more useful but more restrictive as well, you kinda don’t own your code. But code scaffolding won’t be as useful but you have full ownership of the code you write and generate.
Blitz JS uses Code scaffolding.
Blitz JS has a powerful feature called recipes
Recipes uses Blitz JS Scaffolding to give us many powerful scaffolds.
For example you could install tailwind or chakra-ui or material-ui and many more in just a like click.
Example of installing tailwind in your project with recipes
blitz install tailwind
You could find list of all possible recipes over here, you can create your own recipe too.
blitz/recipes at canary · blitz-js/blitz
Development
Installing Blitz JS
Currently its better to use node version 14.5.0 for many reasons, the main one being the prisma package.
You could use package managers like nvm or fvm to manage node versions locally.
You could install blitz globally using yarn global add blitz command.
You could use your preferred package managers like yarn or pnpm or npm.
Setting up Blitz JS using Railway
We could setup the project in different ways like generating the project through cli, using a starter kit.
We would use railway starter kit so that it helps us reduce the setup time.
This would allocate a postgres db for us and creates required environment variables and saves them in railway and deploys on it.
We can reduce a huge amount of time using this step.
You can head over to
and select BlitzJS

Then enter the application name, choose repository access private or not and then generate and put in a secret.
If you don't know any secret, click CMD + K , then type Generate Secret and paste it in the box.
Wait for it to create and deploy, once it's done you can proceed to the next steps.
Local Setup
You can clone the repository that railway created for us by simply cloning the github repository.
git clone <url>
Very Important steps we need to make before proceeding
- The railway starter has an old version of
blitzcli which doesn’t support many latest and greatest features so please openpackage.jsonfile and modify the version of blitz to be0.38.5(this is latest as of date of writing this article) - Railway uses node version
16.5by default so we have to override it by specifyingenginenodeversion.
"engines": {
"node": "14.15.0"
}
- In
types.tsfile make the following change to the import statement.
import { DefaultCtx, SessionContext, SimpleRolesIsAuthorized } from "blitz";
- In the
_app.tsxfile Remove the
import { queryCache } from "react-query";
from the imports and add useQueryErrorResetBoundary to existing blitz imports.
import {
AppProps,
ErrorComponent,
useRouter,
AuthenticationError,
AuthorizationError,
ErrorFallbackProps,
useQueryErrorResetBoundary,
} from "blitz";
Modify the ErrorBoundry to be the following
<ErrorBoundary
FallbackComponent={RootErrorFallback}
resetKeys={[router.asPath]}
onReset={useQueryErrorResetBoundary().reset}
>
- Finally modify the
blitz.config.jsfile toblitz.config.tsfile and modify it like this
import { BlitzConfig, sessionMiddleware, simpleRolesIsAuthorized } from "blitz";
const config: BlitzConfig = {
middleware: [
sessionMiddleware({
isAuthorized: simpleRolesIsAuthorized,
cookiePrefix: "google-keep-clone-blitz", // Use your app name here
}),
],
};
module.exports = config;
_Note: I have already made a PR to the railway starters repo, once it gets merged these changes will automatically be reflected_
Once cloned and made the required changes mentioned above you could run install with your preferred package manager.
pnpm install # If you use pnpm
yarn install #If you use yarn
npm install #If you use npm
The output response would look something similar to this
pnpm install
pnpm dev
Let’s add tailwind to the project by leveraging the power of recipes in Blitz JS
blitz install tailwind
You need to install railway locally as the .env variables we have are from there. So to use the production environment locally we simply need to append railway run before the command to use that environment.
You could check the other commands which are available by running railway help
The output would look something similar to this.
railway help
You could modify the dev script in package.json file as below so that it will be easy during development.
"dev": "railway run blitz dev"
Database updation
Let’s update the schema.prisma to add Note model and add relations to the User model.
model Note{
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
text String
userId Int
user User @relation(fields: [userId], references: [id])
}
Now let’s push the new schema and update our postgres database.
railway run blitz prisma db push --preview-feature
The output would look something similar to this.
Basic UI
Create a new file at pages/notes.tsx and add the following snippet.
import { BlitzPage } from "blitz";
import React from "react";
const NotesPage: BlitzPage = () => {
return (
<>
<div> Create a note goes here </div>
<div> My notes go here </div>
</>
);
};
export default NotesPage;
Now if you visit [localhost:3000/notes](http://localhost:3000/notes) you would see this page rendered right up!
Queries and Mutations
Now the authentication part, oh Blitz Js provides the entire authentication for us including the error handling, providing hooks to use across the application and many more, we can just skip this step.
In your home page you can create an account using sign up and once you return back to home page you can see your userId. Isn’t that so cool.
Blitz provides an amazing console/playground to run/debug your db queries.
railway run blitz c
Any file inside the queries folder has magical powers that will help generate the query and many more for us. This queries can be consumed by using useQuery hook.
Similar goes for mutation anything inside mutationsfolder.
ctx.session.$authorize() this is a special line which makes sure the user is logged in, if not redirect to the login page.
It is very very handy and useful utility. Btw if you don't use the line there would be an error in the userId: ctx.session.userId line because userId can also be undefined if the user is not logged in. So this is an example to show how much type safe blitz is
Mutation to create a note
import { Ctx } from "blitz";
import db from "db";
export default async function createNote(
text: string,
ctx: Ctx,
title?: string
) {
ctx.session.$authorize();
await db.note.create({
data: {
text,
userId: ctx.session.userId,
},
});
}
Query to get all the notes
import { Ctx } from "blitz";
import db from "db";
export async function getUserNotes(ctx: Ctx) {
ctx.session.$authorize();
return await db.note.findMany({ where: { userId: ctx.session.userId } });
}
Mutation to delete a note
import { Ctx } from "blitz";
import db from "db";
export default async function deleteNote(id: number, ctx: Ctx) {
ctx.session.$authorize();
await db.note.delete({
where: {
id,
},
});
}
UI
Add Notes
import { BlitzPage, useQuery, invoke } from "blitz";
import React, { Suspense, useState } from "react";
const NoteMain = () => {
const [text, setText] = useState("");
return (
<div className="flex flex-row">
<div className="w-screen">
<input
type="text"
onChange={(val) => {
setText(val.target.value);
}}
placeholder="Enter note"
className="form-input px-4 py-3 w-4/5 rounded-full"
></input>
</div>
<button
className="w-1/5 p-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full"
onClick={async () => {
await invoke(createNote, text);
}}
>
Add
</button>
</div>
);
};
The above component is used to create add note feature to our notes app.
const [text, setText] = useState("") this is the standard useState to save the text before sending it to the server.
await invoke(createNote, text) this will pass text to createNote mutation and run it.
How cool is that!
Display Notes
const NotesDisplay = () => {
const [notes] = useQuery(getUserNotes, undefined);
return (
<div className="flex flex-wrap flex-row">
{notes.map((note) => (
<div
className="flex flex-col justify-around flex-space-between w-1/5 h-32 border-2 border-blue-200 rounded m-2 p-2"
key={note.id}
>
<p className="text-gray-700 text-base">{note.text}</p>
</div>
))}
</div>
);
};
This uses the useQuery hook to run the query and save the result in the notes variable.
Once we get the notes we iterate through the array and use some fancy tailwind css styles to display the note.
Delete Note
This is button use to delete the note and this uses same invoke function to run the mutation.
<button
className="float-right"
onClick={async () => {
await invoke(deleteNote, note.id);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 text-red-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
</button>
This delete button paired with notes display would look like this.
const NotesDisplay = () => {
const [notes] = useQuery(getUserNotes, undefined);
return (
<div className="flex flex-wrap flex-row">
{notes.map((note) => (
<div
className="flex flex-col justify-around flex-space-between w-1/5 h-32 border-2 border-blue-200 rounded m-2 p-2"
key={note.id}
>
<p className="text-gray-700 text-base">{note.text}</p>
<button
className="float-right"
onClick={async () => {
await invoke(deleteNote, note.id);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 text-red-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
</button>
</div>
))}
</div>
);
};
Now putting everything in our notes.tsx file that we have created we should be seeing something like this.
import createNote from "app/mutations/createNote";
import deleteNote from "app/mutations/deleteNote";
import getUserNotes from "app/queries/getUserNotes";
import { BlitzPage, useQuery, invoke } from "blitz";
import React, { Suspense, useState } from "react";
const NoteMain = () => {
const [text, setText] = useState("");
return (
<div className="flex flex-row">
<div className="w-screen">
<input
type="text"
onChange={(val) => {
setText(val.target.value);
}}
placeholder="Enter note"
className="form-input px-4 py-3 w-4/5 rounded-full"
></input>
</div>
<button
className="w-1/5 p-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full"
onClick={async () => {
await invoke(createNote, text);
}}
>
Add
</button>
</div>
);
};
const NotesDisplay = () => {
const [notes] = useQuery(getUserNotes, undefined);
return (
<div className="flex flex-wrap flex-row">
{notes.map((note) => (
<div
className="flex flex-col justify-around flex-space-between w-1/5 h-32 border-2 border-blue-200 rounded m-2 p-2"
key={note.id}
>
<p className="text-gray-700 text-base">{note.text}</p>
<button
className="float-right"
onClick={async () => {
await invoke(deleteNote, note.id);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 text-red-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
</button>
</div>
))}
</div>
);
};
const NotesPage: BlitzPage = () => {
return (
<div className="container m-8 p-8 h-screen w-screen">
<Suspense fallback={<div>Loading ....</div>}>
<NoteMain />
<NotesDisplay />
</Suspense>
</div>
);
};
export default NotesPage;
We use Suspense to show the loading UI and fallback when the queries are yet to be fetched.
We could have split them into multiple Suspense too but this is good for a starter project.

Deployment
Since we have used railway starter to setup the project we could just deploy by either running railway up or just push the changes to main branch.
You could look at the response here
Further reading
You can follow either one of the guides if you have generated blitz application using the blitz cli.
Example of using blitz cli to generate the project
blitz new google-keep-blitz
You can use either one of the methods described based on your preferred choice.
Deploy to a Server on Render.com - Blitz.js
Deploy Serverless to Vercel - Blitz.js
Deploy to a Server on Heroku - Blitz.js
Deploy to a Server on Railway - Blitz.js
Resources
You can find the complete code base here
GitHub - Rohithgilla12/google-keep-clone-blitz
For diving deeper into Blitz JS, the link for the documentation is below
As any thing has a few tradeoffs Blitz JS has a few too, you can look about them in detail over here.
This is a very minimal but fully functional notes application that you have built and deployed in no time.
Thanks Rohith Gilla
Comments (0)