6. Server Actions

Server Actions are functions that run only on the server but can be invoked directly from the client (like a form submission or button click).
They allow you to:
Mutate data securely (e.g., insert/update/delete in a DB)
Trigger backend logic from the client
Keep your logic colocated with the UI
Avoid API route boilerplate
Fetch Data in Server Component using server action
//src/actions/index.js
'use server'
export default async function fetchList(){
const response = await fetch('https://dummyjson.com/products');
const result = await response.json();
return result?.products
}
// Now we use this API in any server component or client component
//1 . In server componennt : src/app/server-page-example
import fetchList from "@/actions"; // import that
export default async function serverActionsExample(){
const products = await fetchList(); // call that function
console.log(products)
return (
<div>
<h1 className="text-center">
Server-Actions Example LOL -- Sever Components
<ul>
<li>
{
products?.map((item,i)=>(
<p key={i}>
{item.title}
</p>
))
}
</li>
</ul>
</h1>
</div>
)
}
Server Actions in client Component
It is not recommended to use server actions in client component but when we are handle formdata that we use this in client component.
Ex: Lets suppose you use form in client component and store data of form in use state , then you simply call server (POST) method to do this.
// in client component 'use client' import fetchList from "@/actions" // import that import { useEffect, useState } from "react" export default function clientPageServerAction(){ const[products , setProducts] = useState() async function getListOfProducts(){ const data = await fetchList() console.log(data) setProducts(data) } useEffect(()=>{ getListOfProducts() },[]) return ( <div> <h1 className="text-center">Server-Actions in Client Components </h1> <ul> { products?.map((item,i)=>( <li key={i}> {item.title} </li> )) } </ul> </div> ) }
User-management App using Server Actions
1. Do basic setup of NextApp - Design Home page with ShadCn , Connect Database , Create Models ,Create routes
2. Create actions folder in src and define actions in it
"use server";
import dbConnect from "@/database";
import User from "@/models/User";
//add the user -> now we call this from client side componnet that have form data
//No need for HTTP API: You don’t need to create a separate API endpoint—actions are called directly, and data is sent via internal Next.js mechanisms.
export async function addNewUser(formData , pathToReValidate) {
await dbConnect();
try{
const newlyCreatedUser = await User.create(formData);
//DO Validations Here:
if(newlyCreatedUser){
revalidatePath(pathToReValidate) // refresh that route/page after adding it
return {
success: true,
message: "User added successfully",
};
}
else{
return {
success: false,
message: "Failed to add user"
};
}
}
catch(err){
console.log(err);
return {
success: false,
message: "Internal Server Error"
};
}
}
Now this action is for form submission from client component
// This function just used to call server action when <form action={handleAddNewUserAction}> is submitted // Inside it the react state like setState(),setLoading not work, so we have to use the form data directly const handleAddNewUserAction = async(e)=>{ if(Object.keys(addNewUserFormData).some(key=>addNewUserFormData[key].trim()==='')){ alert("all fileds are required") return; } const response = await addNewUser(addNewUserFormData , '/home') //// pass that route that needs to refresh/reValidate when adding new user console.log(response) if(response.success){ alert(response.message) setOpenDialog(false) setAddNewUserFormData({ firstName:"", lastName:"", email:"", password:"", }) } else{ alert(response.message) } }
Fetch usersList
// fetch user actions in actions folder export async function fetchUsersActions(){ await dbConnect(); try{ const listOfUsers = await User.find({}); return { success:true, message:"User fetched successfully", data: JSON.parse(JSON.stringify(listOfUsers)) // Converts mongoose document into a plain JavaScript object otherwise it throw error } } catch(err){ console.log(err) return{ success:false, message:"Internal Server Error" } } }
fetch from frontend
// fetch server action to get list of user const getListOfUsers = await fetchUsersActions(); console.log(getListOfUsers)
Delete User
export async function deleteUserAction(currentUserId , pathToRevalidate){ await dbConnect() revalidatePath(pathToRevalidate) try{ const deletedUSer =await User.findByIdAndDelete(currentUserId); if(deletedUSer){ return { success:true, message:"User Deleted Successfully", } } else{ return { success:false, message:"Unable to delete user" } } } catch(err){ console.log(err) return { success:false, message:"Internal Server Error" } } } // from client component onclick on button const handleDeleteUser = async (id)=>{ const response = await deleteUserAction(id , '/'); console.log(response) }
Edit/Update user
To Edit the user we make a context to store user info to edit
//src/context/index.js 'use client' import { createContext, useState } from "react"; export const UserContext = createContext(null); export default function UserState({children}){ const [currentUserId, setCurrentUserId] = useState(null); const [openDialog, setOpenDialog] = useState(false); const [addNewUserFormData, setAddNewUserFormData] = useState({ firstName:"", lastName:"", email:"", password:"", }) const value ={ currentUserId, setCurrentUserId, openDialog, setOpenDialog, addNewUserFormData, setAddNewUserFormData } return ( <UserContext.Provider value={value}> {children} </UserContext.Provider> ) } // Now create new component src/component/common-layout/index.js 'use client' import UserState from "@/context"; export default function CommonLayout({children}){ return <UserState>{children}</UserState> } // in layout.js <html lang="en"> <body className={`${geistSans.variable} ${geistMono.variable} antialiased`} > <CommonLayout>{children}</CommonLayout> </body> </html>
update the user -: on click edit
export async function editUserAction(currentUserId,formData, pathToReValidate){ await dbConnect(); try{ revalidatePath(pathToReValidate) const {firstName , lastName , email , password} = formData; const editedUser = await User.findOneAndUpdate({ _id:currentUserId, }, {firstName,lastName , email , password}, {new:true}); return { success:true, message:"User Updated Successfully", editedUser: JSON.parse(JSON.stringify(editedUser)) } } catch(err){ return { success:false, message:"Internal Server Error" } } } // form frontend const handleAddNewUserAction = async(e)=>{ if(Object.keys(addNewUserFormData).some(key=>addNewUserFormData[key].trim()==='')){ alert("all fileds are required") return; } const response = currentUserId !==null? await editUserAction(currentUserId, addNewUserFormData , '/') : await addNewUser(addNewUserFormData , '/')// pass that route that needs to refresh/reValidate when adding new user console.log(response) alert(response.message) setOpenDialog(false) setAddNewUserFormData({ firstName:"", lastName:"", email:"", password:"", }) setCurrentUserId(null) }
Subscribe to my newsletter
Read articles from Ayush Rajput directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
