A Glimpse Into the Future of Fullstack Development with Blitz.js

Who am I?

Lead Blitz.js maintainer

 

Twitter: @aleksandrasays

GitHub: @beerose

Blog: aleksandra.codes

Agenda

1. What is Blitz

2. What are the core features

3. What are our plans for Blitz

4. What are we working on right now

What is Blitz.js?

Blitz is a Fullstack React Framework

What is Blitz.js?

Blitz is a Fullstack React Framework

✨ Inspired by Ruby on Rails

What is Blitz.js?

Blitz is a Fullstack React Framework

✨ Inspired by Ruby on Rails

✨ Built on top of Next.js

What is Blitz.js?

Blitz is a Fullstack React Framework

✨ Inspired by Ruby on Rails

✨ Built on top of Next.js

✨ Has Prisma by default

What is Blitz.js?

Blitz is a Fullstack React Framework

✨ Inspired by Ruby on Rails

✨ Built on top of Next.js

✨ Has Prisma by default

✨ Batteries-included framework

Fullstack & Monolithic

One thing to:

- Develop

- Deploy

- Think about

Blitz's core features

Zero-API Layer

Zero-API Layer

Zero-API Layer

✨ Blitz abstracts the API into a build step

 

Zero-API Layer

✨ Blitz abstracts the API into a build step

✨ It swaps the import with an HTTP call

 

Zero-API Layer

✨ Blitz abstracts the API into a build step

✨ It swaps the import with an HTTP call

✨ Blitz resolvers: queries and mutations

 

Zero-API Layer

✨ Blitz abstracts the API into a build step

✨ It swaps the import with an HTTP call

✨ Blitz resolvers: queries and mutations

✨ You can still add your own API

Zero-API Layer

Zero-API Layer — Blitz query

// app/products/queries/getProduct.ts
import db from "db"
import * as z from "zod"

const GetProject = z.object({
  id: z.number(),
})

export default async function getProject(
  input: z.infer<typeof GetProject>,
) {
  const data = GetProject.parse(input)

  const project = await db.project.findOne({ where: { id: data.id } })

  return project
}

Zero-API Layer — useQuery

import { Suspense } from "react"
import { useQuery, useRouter, useParam } from "blitz"
import getProject from "app/projects/queries/getProject"

function Project() {
  const router = useRouter()
  const projectId = useParam("projectId", "number")
  const [project] = useQuery(getProject, { where: { id: projectId } })

  return <div>{project.name}</div>
}

function ProjectPage() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Project />
    </Suspense>
  )
}

export default ProjectPage

Zero-API Layer — Blitz mutation

// app/products/mutations/createProduct.tsx
import db from "db"
import * as z from "zod"

const CreateProject = z
  .object({
    name: z.string(),
  })

export default async function createProject(
  input: z.infer<typeof CreateProject>,
) {
  const data = CreateProject.parse(input)

  const project = await db.project.create({ data })

  return project
}

Zero-API Layer — useMutation

import {useMutation} from 'blitz'
import updateProject from 'app/projects/mutations/updateProject'

function (props) {
  const [updateProjectMutation] = useMutation(updateProject)

  return (
    <Formik
      onSubmit={async values => {
        try {
          const project = await updateProjectMutation(values)
        } catch (error) {
          alert('Error saving project')
        }
      }}>
      {/* ... */}
    </Formik>
  )
}

AuthN & AuthZ out of the box

✨ New Blitz apps have auth

     setup by default

AuthN & AuthZ out of the box

✨ You can sign up and

     login instantly

✨ New Blitz apps have auth

     setup by default

AuthN & AuthZ out of the box

✨ There's Passport.js adapter

     for 3rd party login

✨ You can sign up and

     login instantly

✨ New Blitz apps have auth

     setup by default

AuthN & AuthZ out of the box

singup mutation example

 

query with auth example

// app/auth/mutations/signup.ts
import { resolver, SecurePassword } from "blitz"
import db from "db"
import { Signup } from "app/auth/validations"
import { Role } from "types"

export default resolver.pipe(resolver.zod(Signup), async ({ email, password }, ctx) => {
  const hashedPassword = await SecurePassword.hash(password.trim())
  const user = await db.user.create({
    data: { email: email.toLowerCase().trim(), hashedPassword, role: "USER" },
    select: { id: true, name: true, email: true, role: true },
  })

  await ctx.session.$create({ userId: user.id, role: user.role as Role })
  return user
})

AuthN & AuthZ out of the box

singup mutation example

 

query with auth example

// app/auth/mutations/signup.ts
import { resolver, SecurePassword } from "blitz"
import db from "db"
import { Signup } from "app/auth/validations"
import { Role } from "types"

export default resolver.pipe(resolver.zod(Signup), async ({ email, password }, ctx) => {
  const hashedPassword = await SecurePassword.hash(password.trim())
  const user = await db.user.create({
    data: { email: email.toLowerCase().trim(), hashedPassword, role: "USER" },
    select: { id: true, name: true, email: true, role: true },
  })

  await ctx.session.$create({ userId: user.id, role: user.role as Role })
  return user
})

AuthN & AuthZ out of the box

singup mutation example

 

query with auth example

// app/auth/mutations/signup.ts
import { resolver, SecurePassword } from "blitz"
import db from "db"
import { Signup } from "app/auth/validations"
import { Role } from "types"

export default resolver.pipe(resolver.zod(Signup), async ({ email, password }, ctx) => {
  const hashedPassword = await SecurePassword.hash(password.trim())
  const user = await db.user.create({
    data: { email: email.toLowerCase().trim(), hashedPassword, role: "USER" },
    select: { id: true, name: true, email: true, role: true },
  })

  await ctx.session.$create({ userId: user.id, role: user.role as Role })
  return user
})

AuthN & AuthZ out of the box

singup mutation example

 

query with auth example

// app/auth/mutations/signup.ts
import { resolver, SecurePassword } from "blitz"
import db from "db"
import { Signup } from "app/auth/validations"
import { Role } from "types"

export default resolver.pipe(resolver.zod(Signup), async ({ email, password }, ctx) => {
  const hashedPassword = await SecurePassword.hash(password.trim())
  const user = await db.user.create({
    data: { email: email.toLowerCase().trim(), hashedPassword, role: "USER" },
    select: { id: true, name: true, email: true, role: true },
  })

  await ctx.session.$create({ userId: user.id, role: user.role as Role })
  return user
})

AuthN & AuthZ out of the box

singup mutation example

 

query with auth example

// app/products/queries/getProduct.ts
import {Ctx} from "blitz"
import db from "db"
import * as z from "zod"

const GetProject = z.object({
  id: z.number(),
})

export default async function getProject(
  input: z.infer<typeof GetProject>,
  ctx: Ctx
) {
  const data = GetProject.parse(input)
  
  ctx.session.$authorize(),

  const project = await db.project.findOne({ where: { id: data.id } })

  return project
}

AuthN & AuthZ out of the box

singup mutation example

 

query with auth example

// app/products/queries/getProduct.tsx
import {Ctx} from "blitz"
import db from "db"
import * as z from "zod"

const GetProject = z.object({
  id: z.number(),
})

export default async function getProject(
  input: z.infer<typeof GetProject>,
  ctx: Ctx
) {
  const data = GetProject.parse(input)
  
  ctx.session.$authorize(),

  const project = await db.project.findOne({ where: { id: data.id } })

  return project
}

Code Scaffoldding

Code Scaffoldding

app/pages/projects/[projectId]/edit.tsx
app/pages/projects/[projectId].tsx
app/pages/projects/index.tsx
app/pages/projects/new.tsx
app/projects/components/ProjectForm.tsx
app/projects/queries/getProject.ts
app/projects/queries/getProjects.ts
app/projects/mutations/createProject.ts
app/projects/mutations/deleteProject.ts
app/projects/mutations/updateProject.ts

 will generate the following files:

blitz generate all project

Blitz Recipes

> blitz install tailwind

✅ Installed 2 dependencies
✅ Successfully created postcss.config.js, tailwind.config.js
✅ Successfully created app/styles/button.css, app/styles/index.css
✅ Modified 1 file: app/pages/_app.tsx

🎉 The recipe for Tailwind CSS completed successfully! 
Its functionality is now fully configured in your Blitz app.

Type-safe Routes

// app/pages/products/[productId].tsx

export default function ProductsPage() { ... }

// app/pages/products/index.tsx

import { Link, Routes } from "blitz"

export default function ProductsList() {
  return (
    // ...
    <Link href={Routes.ProductsPage({ productId: 123 })} />  
    // ...instead of <Link href={`/products/${123}`} />
  )
}                                        
                                        

Getting started

npm i -g blitz

blitz new my-new-blitz-project

The Future of Biltz

(and fullstack web development 😎)

The Blitz Pivot

Blitz framework  🔜  framework-agnostic toolkit

The Blitz Pivot

✨ Nearly 100,000 Blitz projects have been created

✨ We recently passed the 10,000 Github Stars milestone

✨ People tell us it makes them 5-10x more productive

Why we decided to pivot?

➡ Growth has stagnated in 2021

➡ The Next.js fork has hurt adoption

➡ We see a huge potential in a standalone toolkit

Objectives

Objectives

✨ Preserve the DX and features we currently have

Objectives

✨ Preserve the DX and features we currently have

Make it effortless for millions more developers to         benefit from Blitz

Objectives

✨ Preserve the DX and features we currently have

Make it effortless for millions more developers to         benefit from Blitz

Decouple Blitz from any specific framework

Objectives

✨ Preserve the DX and features we currently have

Make it effortless for millions more developers to         benefit from Blitz

Decouple Blitz from any specific framework

Enable us to ship features without the burden of         maintaining the Next.js fork

The Old vs The New Way

Blitz Toolkit's Features

Blitz Toolkit's Features

Transition

Transition

✨ The current blitz codebase may be moved to a different

     repo, but we will continue to make fixes releases as needed

Transition

✨ The current blitz codebase may be moved to a different

     repo, but we will continue to make fixes releases as needed

✨ We are working to make the new setup as close as       possible to the current setup

Transition

✨ The current blitz codebase may be moved to a different

     repo, but we will continue to make releases as needed

✨ We are working to make the new setup as close as       possible to the current setup

✨ We'll strive to make the migration to the Blitz toolkit as       seamless as possible

New Blitz setup

const {setupClient, gSSP, gSP, api, BlitzServer} = setupServer({
  plugins: [
    AuthPlugin({
      cookiePrefix: 'my-app',
      // choose one of the following
      storage: AuthPlugin.storage.prisma(db),
      storage: AuthPlugin.storage.redis({url: '...'}),
      storage: {
        getSession: (stuff) => redis.get(stuff),
        createSession: ...,
      }
    }),
    ZeroApiPlugin({
      middlewares: [],
      queries: [ /* resolvers here */ ],
      mutations: []
    }),
    FileUploadPlugin({
      s3: {...}
    })
  ],
})

New Blitz server setup

const {setupClient, gSSP, gSP, api, BlitzServer} = setupServer({
  plugins: [
    AuthPlugin({
      cookiePrefix: 'my-app',
      // choose one of the following
      storage: AuthPlugin.storage.prisma(db),
      storage: AuthPlugin.storage.redis({url: '...'}),
      storage: {
        getSession: (stuff) => redis.get(stuff),
        createSession: ...,
      }
    }),
    ZeroApiPlugin({
      middlewares: [],
      queries: [ /* resolvers here */ ],
      mutations: []
    }),
    FileUploadPlugin({
      s3: {...}
    })
  ],
})

New Blitz client setup

export const {
  useQuery,
  useQueries,
  useInfiniteQuery,
  useMutation, 
  queryClient,
  useSession,
  withBlitz, 
} = setupClient<BlitzServer>({
  plugins: [
    AuthPlugin(),
    ZeroApiPlugin(),
  ],
})

New Blitz client setup

interface ClientPlugin {
  events: {
    onSessionCreate?
    onSessionDestroy?
  },
  middleware: {
    beforeHttpRequest?
    beforeHttpResponse?
  },
  exports?
  withProvider?
}

New Zero-API Layer

New Zero-API Layer

Goals:

  • A standalone library
  • Runtime and framework agnostic
  • Use HTTP GET for queries by default, which enables easy caching
  • Support defining multiple resolvers in a single file
  • The most important: preserve the APIs and DX of current Blitz as much as possible

New Zero-API Layer

import {resolver, NotFoundError} from "blitz"
import db from "db"
import {z} from "zod"

const GetProject = z.object({
  id: z.number()
})

export default resolver.pipe(
  resolver.zod(GetProject), 
  resolver.authorize(), 
  async ({id}) => {
    const project = await db.project.findFirst({where: {id}})
    if (!project) throw new NotFoundError()
    return project
  }
)

New Zero-API Layer

import {useQuery, useMutation} from 'blitz.client'

const [project] = useQuery("getProject", { id: 1 });
const [createProjectMutation] = useMutation('createProject')
import { setupServer, sessionMiddleware } from "@blitzjs/next"

import * as projectQueries from "app/projects/queries"

export const { api, gSSP, gSP, Server } = setupServer({
  plugins: [
    ZeroApiPlugin({
      queries: [ ...projectQueries ],
    }),
  ]
})

New Zero-API Layer

import {useQuery, useMutation} from 'blitz.client'

const [project] = useQuery("getProject", { id: 1 });
const [createProjectMutation] = useMutation('createProject')
import { setupServer, sessionMiddleware } from "@blitzjs/next"

import * as projectQueries from "app/projects/queries"

export const { api, gSSP, gSP, Server } = setupServer({
  plugins: [
    ZeroApiPlugin({
      queries: [ ...projectQueries ],
    }),
  ]
})

Note: We are strongly considering keeping the current magical import for resolvers

What are we currently

working on?

What are we currently

working on?

🔨 Plugin system design

🔨 Extracting authN & authZ from Blitz

🔨 Building standalone packages

Takeaways

➡️ https://blitzjs.com/

➡️ https://github.com/blitz-js/blitz

We'd love your feedback!

Thank you! 💜