DEV Community

Cover image for How to Use Next.js 4 Server Actions to Manipulate Data on the Server
Fonyuy Gita
Fonyuy Gita

Posted on

How to Use Next.js 4 Server Actions to Manipulate Data on the Server

Next.js 4 Server Actions: Simplifying Data Manipulation with MongoDB

Next.js 4 is the latest version of the popular React framework that enables you to create fast and scalable web applications with minimal configuration. One of the new features that Next.js 4 introduces is the server actions, which allow you to directly create or modify data on the server without going through an API endpoint.

In this blog post, I will explain what server actions are, how they work, and how to use them with MongoDB as the database. I will also show you how to create a simple blog application that allows users to create, read, update, and delete posts using server actions.

What are server actions?

Server actions are asynchronous server functions that run only on the server but can be invoked from both server-side and client-side components to perform data mutations on the server. These server functions use the “use server” directive at the top of the function body or file to ensure they are never executed on the client side. These server functions can also use the Next.js cache API to revalidate data on demand, and redirect the user to a different route after completion.

For example, suppose we want to create a server function that accepts a JSON object with the title and content of the post, and inserts a new record into the posts collection in the MongoDB database. We can write the server function as follows:

// Import the required modules
import { connectToDatabase } from '../lib/mongodb';
import { revalidatePath, redirect } from 'next/cache';

// Create a server function to create a new post
export async function createPost(formData) {
  'use server';

  // Get the title and content from the form data
  const { title, content } = formData;

  // Validate the input
  if (!title || !content) {
    throw new Error('Missing title or content');
  }

  // Connect to the database
  const { db } = await connectToDatabase();

  // Insert a new post into the posts collection
  await db.collection('posts').insertOne({ title, content });

  // Revalidate the home page to show the new post
  revalidatePath('/');

  // Redirect the user to the home page
  redirect('/');
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the server function uses the “use server” directive to indicate that it should only run on the server. It also uses the connectToDatabase function to connect to the MongoDB database, and the revalidatePath and redirect functions to update the cache and navigate the user to the home page.

How to invoke server actions?

Server actions can be invoked from both server-side and client-side components using the invoke function from the next/server module. The invoke function takes the name of the server function and the optional data to pass to it as arguments, and returns a promise that resolves to the result of the server function.

For example, suppose we want to invoke the createPost server function from a client-side component that renders a form for creating a new post. We can write the component as follows

// Import the required modules
import { useState } from 'react';
import { invoke } from 'next/server';

// Create a component that renders a form for creating a new post
export default function CreatePostForm() {
  // Use state hooks to store the title and content of the post
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');

  // Define a function that handles the form submission
  const handleSubmit = async (event) => {
    // Prevent the default behavior of the form
    event.preventDefault();

    // Invoke the createPost server function with the title and content as data
    await invoke('createPost', { title, content });

    // Clear the form fields
    setTitle('');
    setContent('');
  };

  // Return the JSX element that renders the form
  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="title">Title</label>
      <input
        id="title"
        type="text"
        value={title}
        onChange={(event) => setTitle(event.target.value)}
        required
      />
      <label htmlFor="content">Content</label>
      <textarea
        id="content"
        value={content}
        onChange={(event) => setContent(event.target.value)}
        required
      />
      <button type="submit">Create Post</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the component uses the invoke function to call the createPost server function with the title and content of the post as data. It also uses the useState hooks to store the title and content of the post, and the handleSubmit function to handle the form submission.

How to use server actions with MongoDB?

MongoDB is a popular NoSQL database that stores data in JSON-like documents. To use server actions with MongoDB, we need to install the mongodb package and create a helper function that connects to the database and returns the database object. We can write the helper function as follows

// Import the required modules
import { MongoClient } from 'mongodb';

// Define the connection URL and the database name
const url = 'mongodb://localhost:27017';
const dbName = 'blog';

// Create a global variable to store the database connection
let cachedDb = null;

// Create a helper function that connects to the database and returns the database object
export async function connectToDatabase() {
  // Check if the database connection is cached
  if (cachedDb) {
    // Return the cached connection
    return cachedDb;
  }

  // Create a new connection to the database
  const client = await MongoClient.connect(url, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  });

  // Select the database
  const db = await client.db(dbName);

  // Cache the database connection
  cachedDb = db;

  // Return the database object
  return db;
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the helper function uses the MongoClient class to connect to the database using the connection URL and the database name. It also uses a global variable to cache the database connection and reuse it for subsequent requests.

We can then use the helper function in our server functions to perform data operations on the MongoDB database. For example, suppose we want to create server functions to read, update, and delete posts from the database. We can write the server functions as follows:

// Import the required modules
import { connectToDatabase } from '../lib/mongodb';
import { revalidatePath, redirect } from 'next/cache';

// Create a server function to read all the posts
export async function readPosts() {
  'use server';

  // Connect to the database
  const db = await connectToDatabase();

  // Find all the posts in the posts collection and return them as a JSON array
  return await db.collection('posts').find().toArray();
}

// Create a server function to update a post
export async function updatePost(formData) {
  'use server';

  // Get the id, title, and content from the form data
  const { id, title, content } = formData;

  // Validate the input
  if (!id || !title || !content) {
    throw new Error('Missing id, title, or content');
  }

  // Connect to the database
  const db = await connectToDatabase();

  // Update the post with the given id in the posts collection
  await db.collection('posts').updateOne({ _id: id }, { $set: { title, content } });

  // Revalidate the home page to show the updated post
  revalidatePath('/');

  // Redirect the user to the home page
  redirect('/');
}

// Create a server function to delete a post
export async function deletePost(formData) {
  'use server';

  // Get the id from the form data
  const { id } = formData;

  // Validate the input
  if (!id) {
    throw new Error('Missing id');
  }

  // Connect to the database
  const db = await connectToDatabase();

  // Delete the post with the given id from the posts collection
  await db.collection('posts').deleteOne({ _id: id });

  // Revalidate the home page to remove the deleted post
  revalidatePath('/');

  // Redirect the user to the home page
  redirect('/');
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the server functions use the connectToDatabase function to connect to the database, and the revalidatePath and redirect functions to update the cache and navigate the user to the home page. They also use the db.collection method to access the posts collection, and the find, updateOne, and deleteOne methods to perform data operations on the collection.

Conclusion

Server actions in Next.js 4 provide a simplified way to perform data manipulation directly on the server without the need for an API endpoint. In this blog post, we explored the concept of server actions, demonstrated how to create a server function to create a post, and showed how to invoke the server function from a client-side component. We also covered the integration of MongoDB with server actions by establishing a database connection using a helper function. With the power of Next.js 4 and server actions, you can build efficient and scalable web applications with ease.

Top comments (2)

Collapse
 
iamdoctorj profile image
Jyotirmaya Sahu

But isn't this inefficient in a way that we need to repeatedly create a db connection to mongodb again and again whenever the server action is called? Or is the db connection persistent?

Collapse
 
rjmhrzn002 profile image
Rojen Maharjan

"What a post! Thank you so much! I couldn't leave without appreciating this post searched everywhere."