Develop and Deploy a Blitz JS Application.
Tags: webdev, tutorial, typescript, tailwindcss
Published: 2021-07-29 12:48:43

## 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 JS`
- `Tailwind`
- `Railway`

### 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 `pages` folder 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

```bash
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](https://github.com/blitz-js/blitz/tree/canary/recipes)


## 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

[Railway Starters](https://railway.app/starters)

and select `BlitzJS`

![Railway Starter image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/drzs78r4sli3495zjbnq.png)

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.

```bash
git clone <url>
```

_Very Important steps we need to make before proceeding_

- The railway starter has an old version of `blitz` cli which doesn’t support many latest and greatest features so please open `package.json` file and modify the version of blitz to be `0.38.5` (this is latest as of date of writing this article)
- Railway uses node version `16.5` by default so we have to override it by specifying `engine` `node` version.

```json
"engines": {
    "node": "14.15.0"
}
```

- In `types.ts` file make the following change to the import statement.

```tsx
import { DefaultCtx, SessionContext, SimpleRolesIsAuthorized } from "blitz";
```

- In the `_app.tsx` file Remove the

```tsx
import { queryCache } from "react-query";
```

from the imports and add `useQueryErrorResetBoundary` to existing blitz imports.

```tsx
import {
  AppProps,
  ErrorComponent,
  useRouter,
  AuthenticationError,
  AuthorizationError,
  ErrorFallbackProps,
  useQueryErrorResetBoundary,
} from "blitz";
```

Modify the `ErrorBoundry` to be the following

```tsx
<ErrorBoundary
      FallbackComponent={RootErrorFallback}
      resetKeys={[router.asPath]}
      onReset={useQueryErrorResetBoundary().reset}
    >
```

- Finally modify the `blitz.config.js` file to `blitz.config.ts` file and modify it like this

```tsx
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.

```bash
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`

[Warp Terminal Sample Output](https://app.warp.dev/vE6MrP)

Let’s add tailwind to the project by leveraging the power of `recipes` in Blitz JS

`blitz install tailwind`

[Warp Terminal Sample Output](https://app.warp.dev/DKwbR5)

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`

[Warp Terminal Sample Output](https://app.warp.dev/2KRvGK)

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.

```scheme
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`

[Warp Terminal Sample Output](https://app.warp.dev/mEoMdK)

The output would look something similar to this.

### Basic UI

Create a new file at `pages/notes.tsx` and add the following snippet.

```tsx
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`

[Warp Terminal Sample Output](https://app.warp.dev/ZKV2MK)

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 `mutations`folder.

`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

```tsx
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

```tsx
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

```tsx
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

```tsx
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

```tsx
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.

```tsx
<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.

```tsx
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.

```tsx
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.

![Final Screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9p58pq4xytsm4t9m9jmk.png)

## 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

[Warp Terminal Sample Output](https://app.warp.dev/yKOgqK)

### 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

[Warp Terminal Sample Output](https://app.warp.dev/NKJ9J5)

`blitz new google-keep-blitz`

[Warp Terminal Sample Output](https://app.warp.dev/APvpY5)

You can use either one of the methods described based on your preferred choice.

[Deploy to a Server on Render.com - Blitz.js](https://blitzjs.com/docs/deploy-render)

[Deploy Serverless to Vercel - Blitz.js](https://blitzjs.com/docs/deploy-vercel)

[Deploy to a Server on Heroku - Blitz.js](https://blitzjs.com/docs/deploy-heroku)

[Deploy to a Server on Railway - Blitz.js](https://blitzjs.com/docs/deploy-railway)

### Resources

You can find the complete code base here

[GitHub - Rohithgilla12/google-keep-clone-blitz](https://github.com/Rohithgilla12/google-keep-clone-blitz)

For diving deeper into Blitz JS, the link for the documentation is below

[Get Started with Blitz](https://blitzjs.com/docs/get-started)

As any thing has a few tradeoffs Blitz JS has a few too, you can look about them in detail over here.

[Tradeoffs - Blitz.js](https://blitzjs.com/docs/tradeoffs)

This is a very minimal but fully functional notes application that you have built and deployed in no time.

Thanks

Rohith Gilla