A Glimpse Into the Future of Fullstack Development with Blitz.js
Who am I?
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! 💜
A Glimpse Into the Future of Fullstack Development with Blitz.js
By Aleksandra Sikora
A Glimpse Into the Future of Fullstack Development with Blitz.js
- 1,661