How to Build a Full-Stack Application Using Next.js and MongoDB

GarryGarry
5 min read

Building a full-stack application involves combining the front-end and back-end technologies so that they communicate seamlessly with each other. This article will guide you through creating a modern full-stack application using Next.js for the front end and MongoDB as the database. Next.js is a React-based framework that provides server-side rendering, API routes, and other features out of the box, while MongoDB is a NoSQL database that stores data in JSON-like documents.

By the end of this tutorial, you will have a working knowledge of how to:

  1. Set up a Next.js project.

  2. Connect to a MongoDB database.

  3. Create API routes in Next.js to handle CRUD operations.

  4. Build a responsive front-end that interacts with the database.

Let’s dive in!


Prerequisites

Before starting, make sure you have the following installed:

  • Node.js (v14+)

  • MongoDB (you can use MongoDB Atlas for cloud hosting or run a local instance)

  • Basic knowledge of React.js

Step 1: Setting Up the Next.js Project

  1. Create a new Next.js app:

    In your terminal, run the following command to create a new Next.js project:

     bashCopy codenpx create-next-app fullstack-nextjs-mongodb
     cd fullstack-nextjs-mongodb
    

    This will scaffold a basic Next.js project. Inside the fullstack-nextjs-mongodb directory, you’ll find the basic structure of the application.

  2. Install Dependencies:

    For this project, we’ll need additional packages to interact with MongoDB and manage the API requests.

     bashCopy codenpm install mongoose
    

    mongoose is an Object Data Modeling (ODM) library for MongoDB that helps manage database schemas and operations in a clean, organized way.


Step 2: Connecting to MongoDB

We’ll now set up the connection between Next.js and MongoDB using Mongoose.

  1. Create a .env.local file to store environment variables:

    In the root directory, create a .env.local File and add your MongoDB connection string. If you're using the cloud version, you can get this string from MongoDB Atlas or provide a local MongoDB URI.

     bashCopy codeMONGODB_URI=mongodb+srv://<username>:<password>@cluster0.mongodb.net/myDatabase?retryWrites=true&w=majority
    
  2. Create the MongoDB connection logic:

    Inside the lib folder (create it if it doesn’t exist), create a file named mongodb.js to manage the database connection.

     javascriptCopy codeimport mongoose from 'mongoose';
    
     const MONGODB_URI = process.env.MONGODB_URI;
    
     if (!MONGODB_URI) {
       throw new Error('Please define the MONGODB_URI environment variable inside .env.local');
     }
    
     let cached = global.mongoose;
    
     if (!cached) {
       cached = global.mongoose = { conn: null, promise: null };
     }
    
     async function connectToDatabase() {
       if (cached.conn) {
         return cached.conn;
       }
    
       if (!cached.promise) {
         const opts = {
           useNewUrlParser: true,
           useUnifiedTopology: true,
         };
    
         cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
           return mongoose;
         });
       }
       cached.conn = await cached.promise;
       return cached.conn;
     }
    
     export default connectToDatabase;
    

This code ensures that your app maintains a single connection to the database across multiple API requests, reducing overhead.


Step 3: Creating a MongoDB Schema

Next, we’ll define a schema for the data stored in MongoDB. Let’s assume we are building a simple blog application with articles.

  1. Inside the models directory (create it if it doesn’t exist), create a file named Article.js:

     javascriptCopy codeimport mongoose from 'mongoose';
    
     const ArticleSchema = new mongoose.Schema({
       title: {
         type: String,
         required: [true, 'Please provide a title for the article.'],
       },
       content: {
         type: String,
         required: [true, 'Please provide content for the article.'],
       },
       date: {
         type: Date,
         default: Date.now,
       },
     });
    
     export default mongoose.models.Article || mongoose.model('Article', ArticleSchema);
    

This schema defines the structure of our articles, specifying that each article should have a title, content, and date.


Step 4: Setting Up API Routes

Next.js has a built-in API route system that allows us to createback-endd logic easily. Let’s create routes to handle CRUD operations (Create, Read, Update, Delete) for the articles.

  1. Create an api directory under the pages folder, and inside it, create a new file articles.js:

     javascriptCopy codeimport connectToDatabase from '../../lib/mongodb';
     import Article from '../../models/Article';
    
     export default async function handler(req, res) {
       const { method } = req;
    
       await connectToDatabase();
    
       switch (method) {
         case 'GET':
           try {
             const articles = await Article.find({});
             res.status(200).json({ success: true, data: articles });
           } catch (error) {
             res.status(400).json({ success: false });
           }
           break;
         case 'POST':
           try {
             const article = await Article.create(req.body);
             res.status(201).json({ success: true, data: article });
           } catch (error) {
             res.status(400).json({ success: false });
           }
           break;
         default:
           res.status(400).json({ success: false });
           break;
       }
     }
    

This API route will handle GET requests to retrieve articles and POST requests to create new articles.


Step 5: Building the Front-end

Now, let’s create a simple front-end to interact with this API. We will build a page that displays all articles and a form to create new ones.

  1. In the pages directory, modify the index.js file:

     javascriptCopy codeimport { useState, useEffect } from 'react';
    
     export default function Home() {
       const [articles, setArticles] = useState([]);
       const [title, setTitle] = useState('');
       const [content, setContent] = useState('');
    
       useEffect(() => {
         fetch('/api/articles')
           .then((res) => res.json())
           .then((data) => setArticles(data.data));
       }, []);
    
       const handleSubmit = async (e) => {
         e.preventDefault();
         const res = await fetch('/api/articles', {
           method: 'POST',
           headers: {
             'Content-Type': 'application/json',
           },
           body: JSON.stringify({ title, content }),
         });
         const newArticle = await res.json();
         setArticles([...articles, newArticle.data]);
         setTitle('');
         setContent('');
       };
    
       return (
         <div>
           <h1>Articles</h1>
           <form onSubmit={handleSubmit}>
             <input
               type="text"
               value={title}
               onChange={(e) => setTitle(e.target.value)}
               placeholder="Title"
             />
             <textarea
               value={content}
               onChange={(e) => setContent(e.target.value)}
               placeholder="Content"
             ></textarea>
             <button type="submit">Submit</button>
           </form>
    
           <ul>
             {articles.map((article) => (
               <li key={article._id}>
                 <h2>{article.title}</h2>
                 <p>{article.content}</p>
               </li>
             ))}
           </ul>
         </div>
       );
     }
    

This component fetches articles from tback-endend, displays them, and provides a form to create new ones.


Step 6: Running the Application

To run the application, use the following command:

bashCopy codenpm run dev

Navigate to http://localhost:3000 In your browser, you should see a list of articles and a form to create new articles.


Final Thoughts

You’ve now built a simple full-stack application using Next.js and MongoDB! This setup can be extended with features like user authentication and pagination or even deployed to platforms like Vercel or Heroku.

Next.js makes it incredibly easy to build dynamic web applications with server-side rendering, while MongoDB provides flexibility in managing data. As you continue to work on more advanced applications, you can explore additional Next.js features like static generation, API optimizations, and MongoDB integrations with services like Mongoose middleware.

0
Subscribe to my newsletter

Read articles from Garry directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Garry
Garry