Blitz.js Documentation

repository·main·Indexed 41 days ago

https://github.com/blitz-js/blitzjs.com

Documentation for Blitz.js, featuring guides on authentication setup using @blitzjs/auth, session management with AuthServerPlugin and AuthClientPlugin, and client-side hooks like useSession(), useAuthenticatedSession(), and useAuthorize(). Includes details on session configuration, SecurePassword hashing, and integrating with Next.js App and Pages routers.

Tokens
86.2K
Snippets
274
Records
361
Agent score
51%

What's inside Blitz.js

  1. What is Blitz.js and what is it for?

    main

    Blitz.js is a fullstack framework designed for building database-backed applications ranging from tiny to large scale. It is intended for applications that require one or more graphical user interfaces, such as web or mobile apps.

    Key characteristics include:

    • Fullstack & Monolithic: Blitz treats the application as a single entity, simplifying reasoning about the app compared to separate frontend/backend deployments. It maintains separation of concerns while providing a unified development experience.
    • Convention over Configuration: Blitz provides a pre-configured environment (including ESLint, Prettier, TypeScript, routing, and database setup) to allow developers to start building features immediately. Configuration is optional, and 'escape hatches' are provided for bespoke customization.
    • Loose Opinions: While Blitz provides a guided path through conventions, it does not strictly enforce them, allowing developers to deviate from the standard file structure or patterns when necessary.
  2. What is HTTP Middleware in Blitz?

    main

    HTTP Middleware serves as an escape hatch for queries and mutations to access HTTP primitives (like cookies, headers, or the request/response objects).

    Key Mental Model:

    • Use Middleware for: Accessing HTTP-level data (e.g., setting a session object from a cookie) and passing that data into the application context.
    • Do NOT use Middleware for: Business logic. Business logic should reside in queries and mutations because they are more composable and easier to test.
    • Communication Pattern: Middleware communicates with queries/mutations via the res.blitzCtx object. For example, an authentication middleware can read a cookie, create a session object, and attach it to res.blitzCtx. The query/mutation then receives this session object in its ctx argument.
  3. Implement a Multitenant Data Model

    main

    To implement multitenancy, use a single database where every tenant-specific model includes an organizationId to ensure data isolation.

    Key components of the recommended model:

    • Organization: The top-level entity that owns all data for an account.
    • Membership: A join table that connects Users to Organizations, allowing a user to belong to multiple organizations. It should store the user's role within that specific organization.
    • User: Represents the individual, which can have a GlobalRole (e.g., SUPERADMIN for platform owners or CUSTOMER).
    • Ownership: When assigning entities (like tasks) to users, assign them to the user's Membership rather than the User directly to maintain the organization context.
    model Organization {
      id         Int @id @default(autoincrement())
      name       String
      membership Membership[]
    }
    
    model Membership {
      id             Int @id @default(autoincrement())
      role           MembershipRole
      organization   Organization @relation(fields: [organizationId], references: [id])
      organizationId Int
      user           User? @relation(fields: [userId], references: [id])
      userId         Int?
      invitedName    String?
      invitedEmail   String?
    
      @@unique([organizationId, invitedEmail])
    }
    
    enum MembershipRole {
      OWNER
      ADMIN
      USER
    }
    
    enum GlobalRole {
      SUPERADMIN
      CUSTOMER
    }
    
    model User {
      id             Int      @id @default(autoincrement())
      email          String   @unique
      role           GlobalRole
      memberships    Membership[]
    }
  4. Use Blitz Code Scaffolding and CLI Generation

    main

    Blitz prioritizes code scaffolding over code generation. While code generation (like graphql-code-generator) creates code you don't own and cannot easily customize, Blitz's scaffolding approach generates initial code directly into your project. Once scaffolded, you have full ownership and can customize the code as needed.

    You can use the blitz generate CLI commands to scaffold various pieces of code into your project.

  5. Understanding the Blitz Pivot to a framework-agnostic toolkit

    main

    Blitz is transitioning from a Next.js-specific framework to a framework-agnostic toolkit. This toolkit aims to preserve Blitz's core developer experience (DX) while supporting multiple frameworks such as Next.js, Remix, SvelteKit, and Nuxt.

    Key implications for users:

    • Existing Apps: Your current Blitz apps will continue to run. The current codebase will be preserved, and critical bugs will be addressed.
    • New Apps: It is safe to start new apps with the current Blitz framework. The transition to the toolkit is intended to be seamless.
    • Production Apps: If your app is near production, you do not need to rewrite it. You can ship with confidence.
    • Migration: The team is developing codemods to automate most or all required changes during the transition to minimize manual effort.
  6. Configure `isAuthorized` adapters in `sessionMiddleware`

    main

    The behavior of ctx.session.$isAuthorized() and ctx.session.$authorize() is determined by an adapter passed to the sessionMiddleware configuration.

    • ctx.session.$isAuthorized(): Returns a boolean indicating if the user is authorized.
    • ctx.session.$authorize(): Throws an error if the user is not authorized. This is the preferred method for securing queries and mutations.

    By default, new apps use the simpleRolesIsAuthorized adapter.

    import { Ctx } from "blitz"
    
    export default async function getUser({ where }: GetUserInput, ctx: Ctx) {
      // Throws error if user is not an admin
      ctx.session.$authorize("admin")
    
      return await db.user.findOne({ where })
    }
  7. How Blitz handles server and client communication

    main
    Blitz uses an RPC (Remote Procedure Call) pattern to make server-client communication seamless. When you import and use server code (like queries or mutations) directly in your UI components, Blitz automatically replaces the direct function import with an RPC network call during the build process. This ensures that the actual logic of your query or mutation functions is never included in your client-side bundle; instead, it is moved to an API layer that runs on the server.
  8. Manage user sessions with SessionContext

    main

    Blitz provides built-in session management via the SessionContext object, which is accessible on the server through the ctx.session property. It handles user tracking, request attribution, and CSRF protection. You can perform core authentication actions like logging in, logging out, and modifying session data using the methods provided by SessionContext.

    import { Ctx } from "blitz"
    
    // Example of accessing session on the server
    export default async function someQuery(input: any, ctx: Ctx) {
      const userId = ctx.session.userId;
      // ...
    }
  9. How `resolver.pipe` works for composing resolvers

    main

    The resolver.pipe utility is a functional pipe that allows you to compose complex resolvers by passing the output of one function as the input to the next. This approach reduces the need for explicit TypeScript types and enables clean, one-line composition of validation, authorization, and business logic.

    Key behaviors:

    • Type Inference: TypeScript types are automatically inferred from one function to the next.
    • Input Type Determination: The input type of the entire composed resolver is determined by the input type of the first argument in the pipe. Therefore, you should almost always place resolver.zod() as the first argument.
    • Function Signature: Each function in the pipe accepts the data from the previous step as the first argument and the middleware ctx as the second argument.
    • Final Result: The output of the last function in the pipe becomes the final resolver result sent to the client.
    import { resolver } from "@blitzjs/rpc"
    import db from "db"
    import * as z from "zod"
    
    export const CreateProject = z.object({
      name: z.string(),
      dueDate: z.date().optional(),
      orgId: z.number().optional(),
    })
    
    export default resolver.pipe(
      resolver.zod(CreateProject),
      resolver.authorize(),
      // Set default orgId
      (input, { session }) => ({
        ...input,
        orgId: input.orgId ?? session.orgId,
      }),
      async (input, ctx) => {
        const project = await db.project.create({
          data: input,
        })
        return project
      }
    )
  10. Understand the Blitz Typesafe Data Layer (RPC)

    main

    Blitz uses a Remote Procedure Call (RPC) pattern as an alternative to traditional REST or GraphQL APIs. Instead of building a separate API and performing manual data fetching from the frontend, you can import your server code directly into your frontend components.

    At build time, Blitz automatically transforms these imports into RPC API calls that execute the server-side logic on the server. This eliminates much of the traditional React architecture overhead. While RPC is the primary pattern, Blitz does not restrict you from using REST or GraphQL for data fetching if preferred.

  11. Invalidate and update query cache after mutations

    main

    After a mutation succeeds, you often need to update the UI to reflect the new data. Blitz provides several ways to manage the cache:

    1. setQueryData: Available from the useQuery hook. Use this to manually update the cache with new data. By default, calling this triggers a refetch to ensure synchronization. You can prevent this by passing { refetch: false } as the second argument.
    2. refetch: Available from the useQuery hook. Use this to manually trigger a reload of the query data.
    3. invalidateQuery: Imported from @blitzjs/rpc. This allows you to invalidate a specific query (by passing its resolver function) from anywhere in your application, even in components that do not own the query.
    // Using setQueryData from useQuery
    const [product, { setQueryData }] = useQuery(getProduct, { where: { id: 1 } })
    // ... inside onSubmit
    const product = await updateProductMutation(values)
    setQueryData(product)
    
    // Using invalidateQuery from @blitzjs/rpc
    import { invalidateQuery } from "@blitzjs/rpc"
    import getProducts from "app/products/queries/getProducts"
    
    // Trigger invalidation
    invalidateQuery(getProducts)