Server action database mutations let you change data on the server safely and easily. They help keep your app data updated without extra setup.
Server action database mutations in NextJS
Start learning this pattern below
Jump into concepts and practice - no test required
'use server' export async function actionName(formData: FormData) { // perform database mutation here await db.model.create({ data: { /* your data here */ } }) // optionally return data or redirect }
Use the 'use server' directive at the top of the function to mark it as a server action.
The function receives form data or parameters and performs database changes directly.
'use server' export async function addUser(formData: FormData) { const name = formData.get('name')?.toString() || '' await db.user.create({ data: { name } }) }
'use server' export async function deletePost(postId: string) { await db.post.delete({ where: { id: postId } }) }
This example shows a simple todo form. When submitted, it calls the addTodo server action to add a new todo item to the database. The form uses semantic HTML and accessibility labels.
import { db } from '@/lib/db' 'use server' export async function addTodo(formData: FormData) { const title = formData.get('title')?.toString() || '' if (title.trim() === '') return await db.todo.create({ data: { title } }) } export default function TodoForm() { return ( <form action={addTodo}> <label htmlFor="title">New Todo:</label> <input id="title" name="title" type="text" required aria-label="Todo title" /> <button type="submit">Add</button> </form> ) }
Server actions run only on the server, so they keep your database credentials safe.
Use FormData to get form inputs easily inside the action.
Server actions simplify data mutations without needing separate API routes.
Server action database mutations let you change data on the server directly.
They use the 'use server' directive and receive form data or parameters.
This approach keeps your app secure and your code simple.
Practice
Solution
Step 1: Understand server actions role
Server actions run on the server and handle data changes securely.Step 2: Identify the security benefit
They keep mutation logic hidden from the client, preventing misuse.Final Answer:
To securely update data on the server without exposing logic to the client -> Option BQuick Check:
Server actions = secure server-side mutations [OK]
- Thinking server actions run on the client
- Confusing server actions with client-side fetching
- Believing server actions handle UI styling
Solution
Step 1: Identify correct 'use server' placement
The 'use server' directive must be the first statement inside the function file or function scope.Step 2: Check syntax correctness
'use server'; export async function addUser() { /* mutation code */ } places 'use server' at the top before the function, which is correct syntax.Final Answer:
'use server'; export async function addUser() { /* mutation code */ } -> Option DQuick Check:
'use server' directive must be at top [OK]
- Placing 'use server' inside function body after code
- Using a function call like useServer() instead of directive
- Putting 'use server' after function declaration
await addUser({ name: 'Alice' }) if the database is empty? 'use server';
async function addUser(user) {
await db.users.create({ data: user });
return await db.users.count();
}Solution
Step 1: Analyze database mutation
The function creates a user in the database with given data.Step 2: Check return value
After creation, it returns the count of users, which should be 1.Final Answer:
Returns 1 because one user is added -> Option AQuick Check:
Created one user, count = 1 [OK]
- Assuming count runs before creation completes
- Confusing 'use server' placement causing error
- Missing return statement in function
'use server';
export async function updateUser(id, data) {
await db.users.update({ where: { id }, data });
}Solution
Step 1: Check 'use server' directive syntax
The 'use server'; directive is correctly placed at the top of the module.Step 2: Analyze update method parameters
The update method expects an object with 'where' and 'data' keys, but here 'data' is outside the object, causing syntax error.Final Answer:
Incorrect object structure in update call -> Option CQuick Check:
update expects { where: ..., data: ... } object [OK]
- Assuming missing semicolon causes error
- Forgetting to wrap data inside update object
- Missing export keyword (actually present)
'use server';
async function deleteUserIfNoOrders(userId) {
const orders = await db.orders.findMany({ where: { userId, status: 'active' } });
if (orders.length === 0) {
await db.users.delete({ where: { id: userId } });
return 'Deleted';
}
return 'Has active orders';
}Solution
Step 1: Verify order check logic
The function queries active orders for the user and checks if none exist.Step 2: Confirm conditional deletion
If no active orders, it deletes the user and returns 'Deleted'; otherwise returns 'Has active orders'.Final Answer:
This code correctly checks orders before deleting user -> Option AQuick Check:
Conditional delete based on orders = correct [OK]
- Deleting user without checking orders first
- Misplacing 'use server' directive
- Returning wrong message ignoring condition
