Blitz exports a resolver
object which contains a few utilities.
"Resolver" as used here and for queries and
mutations refers to a function that takes some
input and "resolves" that into some output or side effect. This term is
commonly used for GraphQL. A resolver is separate from an API endpoint,
because in an API endpoint you have full access to the Node request and
response objects and you are responsible for managing the http stuff. But
resolvers are a layer removed from raw API handlers.
The below utilities are exported under the resolver
object instead of
resolverPipe
and resolverZod
since they are commonly used together.
This cleans up your imports and makes it simple to discover any other
utilities on the resolver
object.
resolver.pipe
This is a functional pipe that makes it easier and cleaner to write complex resolvers.
A pipe automatically pipes the output of one function into the next function. Here's a non-Blitz example:
// Without pipe
export function (input1) {
const output1 = function1(input1)
const output2 = function2(output1)
const output3 = function3(output2)
return output3
}
// With pipe
export pipe(
function1,
function2,
function3,
}
Benefits:
resolver.zod()
resolver.authorize()
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) => {
console.log("Creating project...", input.orgId)
const project = await db.project.create({
data: input,
})
console.log("Created project")
return project
}
)
The input type of the entire composed resolver function is determined by the input type of the first argument to pipe.
This means you will almost always want resolver.zod()
to be the first in
the pipe.
It accepts from 1 to N functions which take input data as first argument
and middleware ctx
as the second argument.
The output of the first function is the first input argument of the next function. The output of the last function is the final resolver result data that will be sent to the client.
The TypeScript types are automatically inferred from one function to the next.
// 1 function
const resolver = resolver.pipe((input: ResolverInput, ctx: Ctx) => ResolverOutput)
// 2 functions
const resolver = resolver.pipe(
(input: ResolverInput, ctx: Ctx) => A),
(input: A, ctx: Ctx) => ResolverOutput),
)
// 3 functions
const resolver = resolver.pipe(
(input: ResolverInput, ctx: Ctx) => A),
(input: A, ctx: Ctx) => B),
(input: B, ctx: Ctx) => ResolverOutput),
)
// ... etc
This returns a composed function of type (input, ctx) => Promise<result>
which is the standard resolver type.
resolver.zod
This is a handly utility for using
Zod, an awesome input validation
library. It takes a zod schema and runs schema.parseAsync
on the input
data.
Note: If you want to run the synchronous version;
schema.parse
, you can pass in the value "sync" to the second parameter of resolver.zod E.g.:resolver.zod(CreateProject,"sync")
You don't have to use this. You can add
const safeInput = CreateProject.parse(input)
directly in your main
resolver function. But then you also have to manually type the function
interface. The utility does everything in one step, setting input type and
validating the input data at runtime.
import { resolver } from "@blitzjs/rpc"
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),
async (input, ctx) => {
// stuff
}
)
const pipeFn = resolver.zod(MyZodSchema)
ZodSchema:
a zod schemaA function to give resolver.pipe
of type
(rawInput, ctx: Ctx) => validInput
resolver.authorize
Using resolver.authorize
in resolver.pipe
is a simple way to check
whether the user has the authorization to call the query or mutation or
not. For this, blitz uses
session.$authorize
from the
context object.
import { resolver } from "@blitzjs/rpc"
export default resolver.pipe(
resolver.authorize(),
// resolver.authorize('admin'),
async (input, ctx) => {
// stuff
}
)
const pipeFn = resolver.authorize(...isAuthorizedArgs)
...isAuthorizedArgs
: The same arguments as your
isAuthorized
adapterA function to give resolver.pipe
of type (input, ctx: Ctx) => input
paginate
This is a handy utility for query pagination
import { paginate, Ctx } from "@blitzjs/rpc"
import db, { Prisma } from "db"
interface GetProjectSInput
extends Pick<
Prisma.ProjectFindManyArgs,
"where" | "orderBy" | "skip" | "take"
> {}
export default async function getProjects(
{ where, orderBy, skip = 0, take = 100 }: GetProjectsInput,
ctx: Ctx
) {
ctx.session.$authorize()
const {
items: projects,
hasMore,
nextPage,
count,
} = await paginate({
count: () => db.project.count({ where }),
query: (paginateArgs) =>
db.project.findMany({ ...paginateArgs, where, orderBy }),
skip,
take,
})
return {
projects,
nextPage,
hasMore,
count,
}
}
const paginationData = await paginate(paginateArguments)
count
: () => Promise <number>
query
: (payload: { skip: number; take: number }) => Promise<T>
skip
: number
take
: number
maxTake
: number
take
. It protects your DB when "take"
is sent by the user.paginationData
items
: T
(Data resolver by the query
function)nextPage
: { skip: number, take: number } | null
(Next page
pagination payload)hasMore
: boolean
(True if there are more items to load.)count
: number
(Total number of items. Resolved by the count
function)