
Security News
White House Authorizes Private Companies to Conduct Offensive Cyber Operations
A new federal program will let vetted U.S. cybersecurity firms help investigate and disrupt foreign cybercrime groups under government direction.
safe-action
Advanced tools
Validação estática e runtime para criação de server actions no NextJS App Router com Zod
// src/server/root.ts
import { prisma } from "your-prisma-instance"
import { getSession } from "your-session-lib"
import { CreateAction, ActionError } from "safe-action"
// Você pode adicionar metadados que serão compartilhados entre os middlewares
// Meta deve ser um objeto
interface Meta {
span: string
}
// Você pode inicializar o contexto da action
// Deve ser uma função com essas assinaturas: () => object | () => Promise<object>
// ⚠️ Caso não passe o contexto inicial, ele irá iniciar undefined: unknown
const context = async () => {
const session = getSession()
return {
prisma,
session
}
}
const action = CreateAction.meta<Meta>().context<typeof context>().create({
defaultContext: context,
defaultMeta: { span: "global" },
// ✅ Todos os erros que forem lançados dentro das actions vão cair aqui também
errorHandler: (error) => {
// ⚠️ O objeto error é serializado para poder retornar do server para o client
console.error(error)
}
})
export const publicAction = action
export const authedAction = action.middleware(async ({ ctx, next }) => {
if (!ctx.session) { // ⚠️ Vamos garantir que nessa action tenha uma session
throw new ActionError({
code: "UNAUTHORIZED",
message: "You must be logged in to perform this action"
})
}
// ⚠️ É importante utilizar a função next() para chamar o próximo middleware na stack
return next({
ctx: {
session: ctx.session // ✅ Passamos o contexto adiante inferindo a session
}
})
})
[!TIP] Utilize os métodos
.input()para validar os parâmetros da server action
[!IMPORTANT] Os métodos de parser só aceitam
ZodObjectentão usez.object()É possível encadear os métodos para criar objetos mais complexos
Ex.:
.input(z.object({ name: z.string() })).input(z.object({ age: z.number() }))
// src/server/user/index.ts
"use server"
import { z } from "zod"
import { authedAction } from "src/server/root.ts"
export const myAction = authedAction
.input(z.object({ name: z.string() }))
.input(z.object({ age: z.number() }))
// input terá seu tipo inferido com base nos métodos de parser
// ✅ input: { name: string; age: number }
// ✅ ctx: { session: Session }
.execute(async ({ input, ctx }) => {
// faça alguma coisa com os dados
// ✅ retorno inferido automaticamente
return {
message: `${input.name} ${input.age}`,
}
})
[!TIP] Utilize os métodos
.output()para validar o retorno da server action
[!IMPORTANT] Os métodos de parser só aceitam
ZodObjectentão usez.object()É possível encadear os métodos para criar objetos mais complexos em combinação dos parsers de input
Ex.:
.output(z.object({ name: z.string() })).output(z.object({ age: z.number() }))
// src/server/user/index.ts
"use server"
import { z } from "zod"
import { authedAction } from "src/server/root.ts"
export const myAction = authedAction
.input(z.object({ name: z.string() }))
.input(z.object({ age: z.number() }))
.output(z.object({ name: z.string() }))
.output(z.object({ age: z.number() }))
// input terá seu tipo inferido com base nos métodos de parser
// ✅ input: { name: string; age: number }
// ✅ ctx: { session: Session }
.execute(async ({ input, ctx }) => {
// faça alguma coisa com os dados
// ✅ retorno inferido com base nos parsers de output
return {
age: input.age,
name: input.name
}
})
[!TIP] Utilize os métodos
.middleware()para adicionar middlewares em uma action
[!IMPORTANT] Os middlewares precisam retornar a função next() para seguir com o próximo
É possível encadear os middlewares para criar lógicas mais complexas
Os middlewares tem acesso ao
input,meta,rawInput(input ainda não validado) assim como octxe a funçãonextpara seguir com a stackMiddlewares podem ser tanto funções assíncronas quanto funções normais
Ex.:
.middleware(async ({ input, rawInput, ctx, next }) => {...})
// src/server/user/index.ts
"use server"
import { z } from "zod"
import { authedAction } from "src/server/root.ts"
// ⚠️ por segurança rawInput sempre terá type: unknown por conta de não ser validado
export const myAction = authedAction.middleware(async (opts) => {
const { meta, input, rawInput, ctx, next } = opts
// ⚠️ retorne a função next() para prosseguir com a stack de middlewares
return next()
}).middleware(({ next }) => {
// ✅ você pode adicionar novas propriedades ao objeto de contexto
return next({ ctx: { userId: 1 } }) // ✅ ctx: { session: Session, userId: number }
})
[!TIP] Utilize os métodos
.hook()para adicionar hooks em uma action
[!IMPORTANT] Os hooks rodam em três ciclos de vida diferentes e tem acesso a valores que dependem de seu ciclo de vida
- onSuccess -
ctx|meta|rawInput|input- onError -
ctx|metarawInput|error- onSettled
ctx|meta|rawInputÉ possível encadear hooks do mesmo ciclo de vida para criar lógicas mais complexas
Hooks podem ser tanto funções assíncronas quanto funções normais
Ex.:
.hook('onSuccess', async ({ ctx, meta, input, rawInput }) => {...})
// src/server/user/index.ts
"use server"
import { z } from "zod"
import { authedAction } from "src/server/root.ts"
export const myAction = authedAction.hook("onSuccess", async (opts) => {
const { ctx, meta, input, rawInput } = opts
// ✅ E.g. Você pode utilizar hooks para monitorar e usar logs
await logger(`User with has logged in with data: ${input}`)
}).hook("onSuccess", ({ rawInput }) => {
console.log(`Input without validation: ${rawInput}`)
}).hook("onError", async ({ rawInput, error }) => {
await logger(`User failed to login ${error.message}`)
})
// src/app/page.tsx
import { myAction } from "src/server/user"
export default async function Page() {
// ✅ Parâmetros tipados de acordo com os parsers de input
const result = await myAction({ name: "John doe", age: 30 })
return (
<div>
{/* ⚠️ Sempre se deve verificar para ter acesso aos dados */}
{result.success ? (
<>
<h1>{result.data.name}</h1>
<p>{result.data.age}</p>
</>
) : (
<div>{result.error.message}</div>
)}
</div>
)
}
[!TIP] Para utilizar em um client component vamos criar um custom hook
// src/hooks/index.ts
import React from "react"
import { myAction } from "src/server/user"
// type helper para nos ajudar a pegar os parâmetros de uma action
import { type ActionInput } from "safe-action"
// Vamos utilizar como exemplo o componente toast do shadcn/ui
import { toast } from "sonner"
// Vamos criar um type para os valores que vamos precisar receber na action
type Data = ActionInput<typeof myAction> // ✅ Data = { name: string; age: number }
export const useCustomHook = () => {
const [isPending, startTransition] = React.useTransition()
const randomName = ({ name, age }: Data) => {
startTransition(async () => {
const result = await myAction({ name, age })
if (!result.success) {
// ✅ Você pode mostrar algum alerta ou toast para o usuário
toast("Algo de errado aconteceu", {
description: result.error.message
})
// ⚠️ return para parar o fluxo assim o resultado de sucesso será inferido
return
}
toast("Action executada com sucesso", {
description: `Dados recebidos ${result.data.name} ${result.data.age}`
})
})
}
return { isPending, randomName }
}
FAQs
Simple type-safe actions
The npm package safe-action receives a total of 70 weekly downloads. As such, safe-action popularity was classified as not popular.
We found that safe-action demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
A new federal program will let vetted U.S. cybersecurity firms help investigate and disrupt foreign cybercrime groups under government direction.

Research
/Security News
The campaign amassed more than 75,000 installs by targeting Russian-speaking users seeking access to blocked services.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.