
Security News
/Research
Wallet-Draining npm Package Impersonates Nodemailer to Hijack Crypto Transactions
Malicious npm package impersonates Nodemailer and drains wallets by hijacking crypto transactions across multiple blockchains.
next-safe-action
Advanced tools
next-safe-action
is a library that takes full advantage of the latest and greatest Next.js, React and TypeScript features to let you define typesafe actions on the server and call them from Client Components.
13.3.0
release.Next.js >= 13.3.0 and >= TypeScript 5.0.
npm i next-safe-action zod
First of all, you need to create the safe action client:
// src/lib/safe-action.ts
import { createSafeActionClient } from "next-safe-action";
const action = createSafeActionClient();
export { action };
Then, create a file for an action:
// src/app/login-action.ts
"use server"; // don't forget to add this
import { z } from "zod";
import { action } from "~/lib/safe-action";
// This is used to validate input from client.
const input = z.object({
username: z.string().min(3).max(10),
password: z.string().min(8).max(100),
});
// This is how a safe action is created.
// Since we provided a Zod input validator to the function, we're sure
// that data that comes in is type safe and validated.
// The second argument of this function is an async function that receives
// parsed input, and defines what happens on the server when the action is
// called from the client.
// In short, this is your backend code. It never runs on the client.
export const loginUser = action({ input }, async ({ username, password }) => {
if (username === "johndoe") {
return {
error: {
reason: "user_suspended",
},
};
}
if (username === "user" && password === "password") {
return {
success: true,
};
}
return {
error: {
reason: "incorrect_credentials",
},
};
}
);
action
returns a new function (in this case loginUser
). To make it actually work, we must pass the action to a Client Component as a prop, otherwise Server Component functions (e.g. cookies()
or headers()
) wouldn't work in the server action body (defined above).
// src/app/page.tsx
import LoginForm from "./login-form";
import { loginUser } from "./login-action";
export default function Home() {
return (
{/* here we pass the safe action as `login` */}
<LoginForm login={loginUser} />
);
}
There are two ways to call safe actions from the client:
// src/app/login-form.tsx
"use client"; // this is a client component
import { useState } from "react";
import type { loginUser } from "./login-action";
type Props = {
login: typeof loginUser; // infer typings with `typeof`
}
const LoginForm = ({ login }: Props) => {
return (
<form
onSubmit={async (e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const input = Object.fromEntries(formData) as {
username: string;
password: string;
};
const res = await login(input); // this is the typesafe action called from client!
console.log(res);
}}>
<input
type="text"
name="username"
id="username"
placeholder="Username"
/>
<input
type="password"
name="password"
id="password"
placeholder="Password"
/>
<button type="submit">Log in</button>
</form>
);
};
export default LoginForm;
As you can see from the image, on the client you get back a typesafe response object, with three optional keys:
Here's an explanation:
data
: if action runs without issues, you get what you returned in the server action body.
validationError
: if an invalid input object (parsed by Zod via input validator) is passed from the client when calling the action, invalid fields will populate this key, in the form of:
{
"validationError": {
"fieldName": ["issue"],
}
}
serverError
: if an unexpected error occurs in the server action body, it will be caught, and the client will only get back a serverError
response. By default, the server error will be logged via console.error
, but this is configurable.Another way to mutate data from client is by using the useAction
hook. This is useful when you need global access to the action state in the Client Component.
Here's how it works:
// src/app/hook/deleteuser-form.tsx
"use client"; // this is a client component
import { useAction } from "next-safe-action/hook";
import type { deleteUser } from "./deleteuser-action";
type Props = {
remove: typeof deleteUser;
}
const DeleteUserForm = ({ remove }: Props) => {
// Safe action (`remove` which is `deleteUser`) passed to `useAction` hook.
const myDelete = useAction(remove);
return (
<>
<form
onSubmit={async (e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const input = Object.fromEntries(formData) as {
userId: string;
};
// Action call.
await myDelete.execute(input);
}}>
<input type="text" name="userId" id="userId" placeholder="User ID" />
<button type="submit">Delete user</button>
</form>
<div id="response-container">
<pre>Is executing: {JSON.stringify(myDelete.isExecuting)}</pre>
<div>Action response:</div>
<pre className="response">
{
myDelete.res // if got back a response,
? JSON.stringify(myDelete.res, null, 1)
: "fill in form and click on the delete user button" // if action never ran
}
</pre>
</div>
</>
);
};
export default DeleteUserForm;
The useAction
hook returns an object with three keys:
execute
: a caller for the safe action you provided as argument to the hook. Here you pass your typesafe input
, the same way you do when using safe action the non-hooky way.isExecuting
: a boolean
that is true while the execute
function is mutating data.res
: when execute
finished mutating data, the response object. Otherwise it is null
. It has the same three optional keys as the one above (data
, validationError
, serverError
), plus one: fetchError
. This additional optional key is populated when communication with the server fails for some reason.Image example:
The library also supports creating protected actions, that will return a serverError
back if user is not authenticated. You need to make some changes to the above code in order to use them.
First, when creating the safe action client, you must provide an async function
called getAuthData
as an option. You can return anything you want from here. If you find out that the user is not authenticated, you can safely throw an error in this function. It will be caught, and the client will receive a serverError
response.
// src/lib/safe-action.ts
import { createSafeActionClient } from "next-safe-action";
const action = createSafeActionClient({
getAuthData: async () => {
const session = true;
if (!session) {
throw new Error("user is not authenticated!");
}
return {
userId: "coolest_user_id",
};
},
});
export { action };
Then, you can provide a withAuth: true
option to the safe action you're creating:
// src/app/withauth/edituser-action.ts
// [1] For protected actions, you need to provide `withAuth: true` here.
// [2] Then, you'll have access to the auth object, in this case it's just
// `{ userId }`, which comes from the return type of the `getAuthData` function
// declared in the previous step.
export const editUser = action({ input, withAuth: true }, // [1]
async (parsedInput, { userId }) => { // [2]
console.log(userId); // will output: "coolest_user_id",
...
}
);
If you set withAuth
to true
in the safe action you're creating, but you forgot to define a getAuthData
function when creating the client (above step), an error will be thrown when calling the action from client, that results in a serverError
response for the client.
createSafeActionClient
optionsAs you just saw, you can provide a getAuthData
function to createSafeActionClient
function.
You can also provide a custom logger function for server errors. By default, they'll be logged via console.error
(on the server, obviously), but this is configurable:
// src/lib/safe-action.ts
import { createSafeActionClient } from "next-safe-action";
const action = createSafeActionClient({
// You can also provide an empty function here (if you don't want server error
// logging), or a Promise. Return type is `void`.
serverErrorLogFunction: (e) => {
console.error("CUSTOM ERROR LOG FUNCTION:", e);
},
});
export { action };
This project is licensed under the MIT License.
FAQs
Type safe and validated Server Actions in your Next.js project.
The npm package next-safe-action receives a total of 70,744 weekly downloads. As such, next-safe-action popularity was classified as popular.
We found that next-safe-action demonstrated a healthy version release cadence and project activity because the last version was released less than 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
/Research
Malicious npm package impersonates Nodemailer and drains wallets by hijacking crypto transactions across multiple blockchains.
Security News
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
Security News
/Research
Malicious Nx npm versions stole secrets and wallet info using AI CLI tools; Socket’s AI scanner detected the supply chain attack and flagged the malware.