Creating a backend for your application can be straightforward and rewarding, especially when using Next.js alongside MongoDB. Next.js is a powerful framework that simplifies server-side development, while MongoDB offers a flexible database solution for managing data. This guide will walk you through setting up a simple backend that performs CRUD (Create, Read, Update, Delete) operations using Next.js and MongoDB. We'll also explore how to test our API using Postman, making it easy to verify that everything is working correctly. This step-by-step approach is ideal for beginners eager to learn about backend development.
INTRODUCTION TO NEXT.JS AND MONGODB
In this guide, we will build a simple task manager application focusing exclusively on the backend using Next.js(version 15.0.4) and MongoDB. This project will involve creating robust RESTful APIs that facilitate essential CRUD operations for managing tasks, where each task will include key attributes such as a title and a description. Users will be able to create new tasks, view existing ones, update task details, and delete tasks as needed. We will utilize Postman to test our APIs and ensure their functionality throughout the development process. By the end of this tutorial, you will have a solid understanding of integrating Next.js with MongoDB and creating dynamic API routes for efficient task management.
Data Interaction Flow: Client, Next.js API, and MongoDB
Breakdown of flowchart
- Client (Postman/Web Browser): This represents the user interface where requests are initiated. Users can send HTTP requests to interact with the backend API.
- HTTP Request: The arrow labeled "HTTP Request" indicates that the client sends a request to the Next.js API route. This could be any type of request (GET, POST, PUT, DELETE) depending on the operation being performed.
- Next.js API Route: This component represents the server-side logic that processes incoming requests. The API route handles routing and business logic, determining how to respond based on the request type and parameters.
- Connects to MongoDB Database: Once the API route receives a request, it connects to the MongoDB database to perform operations such as retrieving or modifying data.
- Returns Data: After executing the necessary database operations (like fetching data), MongoDB returns the requested data back to the Next.js API route.
- Sends Response: Finally, the API route sends an HTTP response back to the client, which may include data or confirmation of an action taken (e.g., task created, updated, or deleted).
Additionally, the entire code for this project will be accessible in my GitHub repository at https://github.com/hezronokwach/nextjs-backend.git.
Join me as we dive into this exciting backend implementation journey!
SET UP YOUR PROJECT
In this section, we will go through the steps to install and configure Next.js, and MongoDB database.
Install dependencies
Before starting, ensure that you have Node.js and npm (Node Package Manager) installed on your computer.
Download Node.js: Visit the official Node.js website and download the latest version suitable for your operating system.
Verify Installation: After installation, open your terminal and run the following commands to check if Node.js and npm are installed correctly:
node -v
npm -v
Create a New Next.js Project
Next, create a new Next.js application using the following command in your terminal:
npx create-next-app nextjs-backend
This command will generate a new directory called nextjs-backend
, containing all the necessary files and folders for a Next.js project.
You can change the name of the directory to your liking.
On installation, you'll see the following prompts:
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like your code inside a `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to use Turbopack for `next dev`? No / Yes
Would you like to customize the import alias (`@/*` by default)? No / Yes
What import alias would you like configured? (@/* by default)? … No / Yes
Options When Creating a Next.js App
Would you like to use TypeScript?
Yes: Choosing TypeScript allows you to use a statically typed language that can help catch errors during development and improve code quality.
No: If you select this option, your project will be set up using plain JavaScript.
Would you like to use ESLint?
Yes: Enabling ESLint integrates a tool that helps identify and fix problems in your JavaScript code, ensuring best practices and consistency.
No: If you skip this option, your project will not include linting capabilities by default.
Would you like to use Tailwind CSS?
Yes: Selecting this option sets up Tailwind CSS, a utility-first CSS framework that allows for rapid UI development with pre-defined classes.
No: If you choose not to use Tailwind CSS, you'll need to style your application using traditional CSS or another framework.
Would you like your code inside a src/
directory?
Yes: This option organizes your code by placing it inside a src
directory, which can help keep the project structure cleaner and more manageable.
No: Choosing not to use a src
directory means your code will reside in the root of your project.
Would you like to use App Router? (recommended)
Yes: The App Router is the recommended way to handle routing in Next.js applications, providing features like nested routes and layouts.
No: If you opt out of using the App Router, you'll be using the Pages Router instead, which is the traditional routing method in Next.js.
Would you like to use Turbopack for next dev
?
Yes: Turbopack is an experimental bundler designed for fast builds and refreshes during development.
No: If you choose not to use Turbopack, your application will use Webpack as the default bundler.
Would you like to customize the import alias (@/\*
by default)?
Yes: This option allows you to set up custom import paths, making it easier to manage imports throughout your project.
No: If you skip this option, the default import alias will be used.
What import alias would you like configured? (@/* by default)?
Here, you can specify a custom alias for imports if you chose Yes in the previous question. For example, setting it to @/
allows you to import modules more easily.
You can choose the following options for this guide:
Navigate to Your Project Directory:
cd nextjs-backend
For this tutorial, we will be using Visual Studio Code (VS Code) as our integrated development environment (IDE). If you haven't installed it yet, you can easily download it from the official website . Once you have Visual Studio Code installed, follow these steps to open your Next.js project:
Open Your Project in Visual Studio Code:
code .
To run your Next.js application, execute the following command in your terminal:
npm run dev
By default, the Next.js server will run on localhost at port 3000. You can view your application by opening your web browser and entering the following address:
http://localhost:3000
However, if you need to use a custom port for any reason—such as conflicts with other applications or specific deployment requirements—Next.js provides several options to set a different port:
Modify the package.json
File: You can change the default port by updating the dev
script in your package.json
file. For example, to run your application on port 4000, you would modify it like this:
"scripts": {
"dev": "next dev -p 4000",
"build": "next build",
"start": "next start"
}
Use Command-Line Options: You can specify a custom port directly when starting your application by using the command:
npm run dev -- -p 4000
For this guide, we will use the default port 3000 to keep things simple and straightforward. This default page will load after the page has loaded
Folder Structure
Navigate to the app
folder. Below is how the folder structure looks like:
The app
directory is integral to Next.js as it allows developers to define routes based on the file structure. Each folder can contain a page.js
file, which serves as a page component for that specific route. This means that the structure of your folders directly corresponds to the routes in your application i.e the name of the folders inside the app
will be the name of your routes. For instance, if you create a folder named about
with a page.js
inside it, this will be accessible at the /about URL.
Now let us understand the contents of the app folder:
-
page.js
: This file defines the main component for the root route of your application. It is automatically mapped to the root URL (e.g., http://localhost:3000). When users visit this URL, the content of page.js is rendered. -
layout.js
: This file is used to define a layout for your application. It allows you to create a consistent structure across multiple pages, such as headers, footers, and sidebars. Any components or elements defined here will be included in every page that uses this layout. -
font/
Folder: The font folder is typically used to store custom fonts that you want to use in your application. You can include font files (like .woff, .woff2, or .ttf) here and reference them in your CSS files to ensure consistent typography across your site. -
global.css
: The global.css file is where you can define global styles for your application. This file is imported into your application to apply styles universally across all components and pages. -
favicon.ico
: The favicon.ico file is an icon that represents your website in browser tabs, bookmarks, and other places where the site is referenced. It’s a small image (typically 16x16 or 32x32 pixels) that helps users identify your site visually.
Update the Home Page Component
Now let us open the page.js
and replace the contents with:
export default function Home() {
return (
<h1>Hello Next-Js</h1>
);
}
Code Explanation
Exporting the Component: export default
: This syntax is used to export the Home
function as the default export of this module. This means that when this module is imported elsewhere, it will import this specific component by default. In Next.js, each page is typically defined as a React component, and exporting it allows Next.js to render this component when users navigate to the corresponding route.
Defining a Functional Component: function Home()
: This defines a functional component named Home
. In React (and by extension, Next.js), components are the building blocks of the user interface. A functional component is a JavaScript function that returns JSX (JavaScript XML), which describes what the UI should look like.
Returning JSX: return ( <h1>Hello Next-Js</h1> );
: The return
statement is where you define what the component will render. In this case, it returns an <h1>
element containing the text "Hello Next-Js". JSX: JSX is a syntax extension for JavaScript that looks similar to HTML. It allows you to write HTML-like code within JavaScript, which React then transforms into actual DOM elements.
Rendering in Next.js: When this Home
component is placed in the page.js
file within the app
directory of a Next.js project, it becomes the content displayed at the root URL (e.g., http://localhost:3000
). When users visit this URL, they will see "Hello Next-Js" displayed on their screen.
While this section introduces how to set up a basic page in Next.js, our primary focus in this tutorial will be on backend development. We will concentrate on setting up API routes, connecting to MongoDB
, and implementing CRUD operations. This version maintains a technical tone while clearly explaining what is necessary without excessive elaboration. Let move to the next section which is setting up MonngoDB
SET UP MONGODB
Before diving into the setup process, it's important to understand some key terms related to MongoDB
. Familiarising yourself with these concepts will make it easier to navigate the platform and comprehend how it works.
Key Terms in MongoDB
Database: A database in MongoDB
is a container for collections. It organises your data into manageable units, allowing you to store related information together.
Collection: A collection is akin to a table in relational databases. It is a grouping of documents that share similar characteristics. Each document within a collection can have different structures, but they typically represent similar types of data, such as user profiles or product listings.
Document: A document is the basic unit of data in MongoDB
, represented in a JSON-like format (BSON). It consists of key-value pairs and can include various data types, such as strings, numbers, arrays, and even nested documents.
Cluster: A cluster is a group of servers (nodes) that work together to store and manage your data in MongoDB
. Clusters provide redundancy and scalability, ensuring that your application can handle increased loads and remain available even if one server fails.
Node: A node is an individual server within a cluster. Each node can store data and process requests, contributing to the overall performance and reliability of the cluster.
BSON (Binary JSON): BSON stands for Binary JSON and is the binary representation of JSON-like documents used by MongoDB to store data. It allows for more efficient storage and retrieval compared to standard JSON by supporting additional data types (like dates) and being more compact in certain cases. For example, a simple -JSON(JavaScript Object Notation) object might look like this:
{
"name": "Alice",
"age": 30
}
URI (Uniform Resource Identifier): The URI is a connection string used to connect your application to the MongoDB database. It contains essential information such as the username, password, cluster address, and database name. This string is crucial for establishing a connection between your application and the database.
Index: An index is a data structure that improves the speed of data retrieval operations on a collection. By creating indexes on specific fields, you can significantly enhance query performance, making it easier to find documents quickly.
Aggregation: Aggregation refers to the process of processing multiple documents and returning computed results. It allows you to perform operations like filtering, grouping, and sorting on your data to extract meaningful insights.
After familiarizing yourself with these key terms related to MongoDB
, we can now move on to the setup process. Understanding these concepts will provide a solid foundation as we configure MongoDB for our application. Let’s get started with setting up your MongoDB cluster!
Set up
We will be using MongoDB Atlas for this project, leveraging its cloud access to take advantage of features like automatic scaling, easy management, and enhanced security. This approach allows us to focus on development without worrying about the complexities of server maintenance. Let's proceed with setting up our MongoDB cluster in the cloud!
Step 1: Go to the official MongoDB
website and create an atlas account. You can use either Google account or GitHub account.
Step 2: After signing in, you'll be redirected to the main page. If this is your first time creating a database in MongoDB
, click the drop-down button on project0
in the top left corner. If you’ve created projects before, the name will reflect your latest project.
Step 3: Next, input the name of your project. For this tutorial, we’ll use nextjs-backend
. After entering the name, click the Next button.
Step 4: On the following page, you can add members who will have access to the database. In this case, no additional members will be added, so simply click the Create Project button.
Step 5: Once the project is created, you’ll be taken to the cluster page where a cluster needs to be set up. Click on the Create button.
Step 6: After clicking Create, you’ll be redirected to the deployment options for your cluster. Choose the free option; feel free to change the name of the cluster, provider, and region as desired. For this guide, selecting the free option with default settings is sufficient. Click on the Create Deployment button at the bottom right.
Step 7: You’ll arrive at the connection page where you’ll see your username and password. Copy down the password and then click on Create Database User, followed by Choose Connection Method.
Step 8: Let us go back to the virtual studio and lets create a .env
file in the root folder and paste the password.
Step 9: You will be redirected to the page to choose the connection method. Choose the drivers options
Step 10: You will be redirected to the page where you will choose the driver. Choose the Node.js
option and let the version be by default. You can then copy the MongoDB URI
in option 3. Click Done button afterwards.
Step 11: Lets go to the Virtual Studio and paste the URI in the .env
file. Delete the last part of the URI &appName=Cluster0
since we will give the name of the table in the code while setting up the connection with the database.
Step 12: After clicking Done , you will redirected to the clusters page where you will see the cluster name(Cluster0).
You can also obtain the URI
by clicking on the Connect or Get Connection String button. This step is crucial if you decide to clone your project onto another machine, as the URI will not be visible. This is because we will place the .env
file in the .gitignore
file to keep sensitive information secure. If you've already copied the URI and placed it in the .env
file, you can skip this step
Here is the point where the password we copied becomes important because for this URI string will not have the password. Below is how my URI looks like
mongodb+srv://hezronmackenzie04:<db_password>@cluster0.tmgen.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0
You will replace <db_password
with the password.
Now let us see how our database looks like. There are two ways do this:
Method 1:
The first is by clicking the Browse Collections
on the Overview
page under the Clusters
section:
You will be redirected to the collections page which will have the collection given by default by MongoDB
. It is called sample_mflix
Method 2:
The other way is by clicking on the clusters on the left side bar under the Database
option:
You will redirected to this page. Choose the Browse Collections
option
You will be redirected to the collections page which will have the collection given by default by MongoDB
. It is called sample_mflix
There is one more step then we are done with the setup.
Step 13: Lets change the network access settings so that our database will be accessible from any place. By default, it will only be accessible through your IP address only. Let us go to the Network Access
option on the left side bar
Step 14 You will redirected to the page listing all the ip addresses which can access the database. Click the ADD IP ADDRESS
Step 15: Click on the ALLOW ACCESS FROM EVERYWHERE
button and click Confirm
Congratulations on successfully setting up your MongoDB cluster on Atlas! While the setup process may have felt a bit tedious, it’s an essential foundation for building powerful applications. With this crucial step behind you, it’s time to shift gears and dive into the exciting world of coding!
CONNECT NEXT.JS TO MONGODB
Now that your database is ready, you’re poised to start implementing routes and populating your database. This is where the real magic happens! You'll be able to create dynamic APIs that interact with your MongoDB database, allowing you to perform operations like creating, reading, updating, and deleting data (commonly known as CRUD)
We will start with writing code that connects the database with the backend
Set up Database Connection
Before we proceed with installing mongoose
, let's briefly discuss what it is and what it does. Mongoose is an Object Data Modelling (ODM) library for MongoDB and Node.js. It provides a straightforward way to model your application data, allowing you to define schemas for your data structures. With Mongoose, you can perform operations such as creating, reading, updating, and deleting documents in your MongoDB database using a more structured and organized approach. It also includes built-in validation, middleware support, and various other features that make working with MongoDB easier and more efficient. Now that we understand the role of Mongoose in our application, let’s install it.
Open the terminal and run the following command:
npm install mongoose
Let us create a lib
folder in the app
directory. Create a file mongodbConnection.js
file in the lib
folder.
Add this code to the file
import mongoose from "mongoose";
const MONGODB_URI = process.env.MONGODB_URI;
const connectMongoDB = async () => {
try {
await mongoose.connect(MONGODB_URI, {
dbName: "nextjs-backend",
});
console.log("Connected to MongoDB.");
} catch (error) {
console.log("MongoDB connection error:", error);
}
};
export default connectMongoDB;
Code explanation
import mongoose from "mongoose";
Importing Mongoose: The code begins by importing the mongoose
library, which provides a straightforward way to interact with MongoDB through schemas and models.
const MONGODB_URI = process.env.MONGODB_URI;
MongoDB URI: The MONGODB_URI
constant retrieves the connection string from an environment variable. This string contains the necessary information to connect to your MongoDB instance, including the username, password, cluster address, and options. Storing sensitive information like credentials in environment variables helps keep them secure and prevents hard-coding them into your source code.
const connectMongoDB = async () => {
Asynchronous Function Declaration: The connectMongoDB
function is declared as an asynchronous function using the async
keyword. This allows the use of await
within the function, enabling asynchronous operations to be handled more intuitively.
try {
await mongoose.connect(MONGODB_URI, {
dbName: "nextjs-backend",
});
Connecting to MongoDB: The try
block attempts to establish a connection to the MongoDB database using mongoose.connect()
. The first argument is the MONGODB_URI
, which specifies the database location. The second argument is an options object that includes: dbName: "nextjs-backend"
: This specifies the name of the database you want to connect to. In this case, it is set to nextjs-backend
. This allows you to work with this particular database within your MongoDB instance.
console.log("Connected to MongoDB.");
Success Message: If the connection is successful, a message "Connected to MongoDB." is logged to the console. This provides immediate feedback that the application has successfully connected to the database.
} catch (error) {
console.log("MongoDB connection error:", error);
}
};
Error Handling: If an error occurs during the connection attempt, it is caught by the catch
block. The error message is logged to the console with "MongoDB connection error:", followed by the actual error details.
export default connectMongoDB;
Exporting the Function: Finally, the connectMongoDB
function is exported as the default export of this module. This allows other parts of your application to import and use this function when establishing a connection to MongoDB.
Set up Models
In this section, we will set up the models for our application. First, let's clarify what models are and how they will be used in our project.
What Are Models?
In the context of a database, models are representations of the data structures that define how data is organized and interacted with in your application. They serve as blueprints for creating, reading, updating, and deleting (CRUD) data in the database. Each model corresponds to a collection in MongoDB and defines the schema for the documents within that collection. They define the fields and data types for each document, as well as any validation rules or methods associated with that data.
Models We Will Use
For this guide, we will create a model for a simple task manager application. The primary model we will be using is:
Task Model: This model will represent individual tasks in our application. Each task will have key attributes such as:
Title: A string representing the name of the task.
Description: A string providing details about the task.
Created At: A date indicating when the task was created.
Updated At: A date indicating when the task was last updated.
By defining this model, we can easily manage tasks within our application and perform CRUD operations on them.Now, let's proceed with creating the models
folder inside the lib
directory and add a file named models.js
to define our Task model.
Add the code below:
import mongoose, { Schema } from "mongoose";
const taskSchema = new Schema(
{
title: String,
description: String,
},
{
timestamps: true,
}
);
const Task = mongoose.models.Task || mongoose.model("Task", taskSchema);
export default Task;
Code explanation
import mongoose, { Schema } from "mongoose";
Importing Mongoose: The code begins by importing mongoose
and the Schema
class from the Mongoose library. mongoose
provides the functionality to interact with MongoDB, while Schema
is used to define the structure of documents within a collection.
const taskSchema = new Schema(
{
title: String,
description: String,
},
{
timestamps: true,
}
);
Defining the Schema: A new schema, taskSchema
, is created using the Schema
constructor. The first argument is an object that defines the fields of the schema: title
: A field of type String
that will store the title of the task. description
: A field of type String
that will store a more detailed description of the task. The second argument is an options object: timestamps: true
: This option automatically adds two properties to the schema: createdAt
and updatedAt
. These properties track when each document is created and last updated, respectively.
const Task = mongoose.models.Task || mongoose.model("Task", taskSchema);
Creating the Model: This line checks if a model named "Task" already exists in mongoose.models
. If it does, it uses that existing model to avoid creating multiple models with the same name. This is particularly useful in environments like Next.js where modules may be reloaded. If the model does not exist, it creates a new model called "Task" using mongoose.model()
, passing in the name of the model and the schema (taskSchema
). This model represents the "tasks" collection in your MongoDB database.
export default Task;
Exporting the Model: The Task
model is exported as the default export of this module. This allows other parts of your application to import and use this model to create, read, update, or delete tasks in your MongoDB database.
Build CRUD Endpoints
In Next.js 13, setting up API routes is a straightforward process that leverages the framework's file-based routing system. This approach allows developers to create server-side endpoints directly within their application structure, facilitating seamless integration between frontend and backend code.
CRUD operations Flowchart
Breakdown of the flowchart
- User Action: The flowchart begins with a User Action node, representing any interaction initiated by the user, such as creating, reading, updating, or deleting a task.
-
Create Task: When the user decides to create a task, this action triggers a POST request to the
/api/tasks
endpoint. The system then connects to the MongoDB database to perform the operation. - Connecting to MongoDB: The flow moves to establishing a connection with the MongoDB database. This connection is necessary for any database operations.
- Insert Task: After connecting, the system inserts the new task into the Task Collection within MongoDB. Once the task is successfully inserted, the system returns a success message back to the API route.
-
Read Tasks: If the user wants to read or retrieve tasks, they initiate a GET request to
/api/tasks
. Similar to the create operation, this also connects to MongoDB. - Fetch Tasks: After establishing the connection, the system fetches all tasks from the Task Collection. The retrieved list of tasks is then returned as a response to the client.
-
Update Task: For updating an existing task, the user triggers a PUT request to
/api/tasks/:id
, where:id
represents the specific task's identifier. The system connects to MongoDB and updates the corresponding task document in the collection. - Return Updated Task: After updating, it returns the updated task details back to the API route.
-
Delete Task: To delete a task, the user initiates a DELETE request to
/api/tasks/:id
. As with previous operations, it connects to MongoDB and deletes the specified task document from the collection. - Return Deletion Confirmation: Finally, after successfully deleting the task, it returns a confirmation message indicating that the deletion was successful.
Below, we will explore how API routes are set up in Next.js 13, the conventions surrounding file and folder naming.
File Structure for API Routes
API routes in Next.js are defined within the app
directory, which is a new feature introduced in Next.js 13. This directory structure enhances the way routes are organized compared to previous versions.
-
Location: API route files should be placed in the
app/api
directory. For example, a file namedhello/route.js
located atapp/api/hello/route.js
would be accessible via the URL/api/hello
. -
File Naming: The naming convention for API routes follows a clear pattern: Each API route is defined in a file named
route.js
within its respective folder. This indicates that the folder corresponds to an endpoint. For dynamic routes, you can use square brackets in the folder name. For instance,app/api/users/[id]/route.js
would handle requests like/api/users/1
. - Exporting Route Handlers: Each API route file should export a default asynchronous function that handles incoming requests.
Handling Different HTTP Methods
Next.js 13 allows you to define separate functions for handling different HTTP methods (GET, POST, PUT, DELETE) within the same route file
Create and Populate Collections with POST Requests
A POST request is primarily used to send data to the server. This data can be included in the body of the request, allowing for the transmission of various types of information, such as form submissions, file uploads, or API interactions. We will use the POST request to create the collection in out case, Tasks
and send info about the Tasks
i.e title
and description
.
Lets create a folder api
in the app
folder. In the api
folder create a folder tasks
. Go ahead and create a file route.js
in the tasks
folder. Add the code below to handle the POST requests.
import Task from "@/app/lib/models/models";
import connectMongoDB from "@/app/lib/mongodbConnection";
import { NextResponse } from "next/server";
export async function POST(request) {
try {
const { title, description } = await request.json();
await connectMongoDB();
await Task.create({ title, description });
return NextResponse.json({ message: "Task Created" }, { status: 201 });
} catch (error) {
return NextResponse.json({ error: "Failed to create task" }, { status: 500 });
}
}
Code explanation
This code snippet is designed to handle incoming POST requests to create a new task in the MongoDB
database. It utilises Mongoose
for database interactions and the Next.js API routing capabilities. Here’s a breakdown of each part of the code:
import Task from "@/app/lib/models/models";
import connectMongoDB from "@/app/lib/mongodbConnection";
import { NextResponse } from "next/server";
Imports:
Task: This imports the Mongoose model for the task, which defines the structure of task documents in the MongoDB collection. It allows you to interact with the tasks collection using Mongoose methods.
connectMongoDB: This imports a function that establishes a connection to the MongoDB database. It ensures that the application can communicate with the database before performing any operations.
NextResponse: Imported from next/server
, this utility is used to create responses for API requests, allowing you to return JSON responses easily.
export async function POST(request) {
POST(request): This line exports an asynchronous function named POST
, which handles incoming POST requests made to this API route. The function takes one parameter, request
, representing the HTTP request object.
try {
const { title, description } = await request.json();
request.json(): Inside the try
block, this method attempts to parse the JSON body of the incoming request. It reads the request body and converts it into a JavaScript object. The destructuring assignment extracts title
and description
, which are expected fields for creating a new task.
await connectMongoDB();
connectMongoDB(): This function is called to establish a connection to the MongoDB database. This step is crucial because any database operations (like creating a new task) require an active connection.
await Task.create({ title, description });
Task.create(): The code calls this method with an object containing title
and description
. This method creates a new document in the tasks collection of the MongoDB database using Mongoose. If successful, this operation adds a new task with the specified title and description.
return NextResponse.json({ message: "Task Created" }, { status: 201 });
Successful Response: If all operations are successful, this line returns a JSON response indicating that the task has been created. The response includes a status code of 201 (Created), signifying that a new resource has been successfully created on the server.
} catch (error) {
return NextResponse.json({ error: "Failed to create task" }, { status: 500 });
}
}
Error Handling: If any errors occur during parsing, connecting to MongoDB, or creating the task, they are caught by the catch
block. In case of an error, it returns a JSON response with an error message and a status code of 500 (Internal Server Error). This informs the client that something went wrong while processing their request.
We will proceed to test the API we have just created. We will use Postman for testing the API.
Set up Postman for API testing
Step 1: Download and Install Postman: Visit the Postman website and download the version suitable for your operating system (Windows, mac OS, or Linux). Follow the installation instructions to set up Postman on your local machine.
Step 2: Create a Postman Account: Open Postman and sign up for a free account if you don’t already have one. This allows you to save your requests and collections.
Step 3: Create a New Collection: Click the New button in the top left corner of the Postman window and select Collection. Use the + icon or the Create Collection button to create a new collection that will hold all the various requests we will make. We choose to create a collection instead of individual HTTP Requests because it allows us to organize multiple types of requests (POST, GET, PUT, DELETE) in one place. This approach streamlines our workflow, making it easier to manage related requests and maintain consistency across our API testing efforts.
Step 4: Add Requests to Your Collection: After creating your collection, click on the collection name, New Collection in the left sidebar then hover over the name to reveal the three-dot menu (...). Click in the three-dot menu (...) and this will open a drop-down and choose Add request.
Step 5: Enter Request Details: In the new request tab, enter the URL of your API endpoint http://localhost:3000/api/tasks
. Select the HTTP method (POST) from the drop-down menu next to the URL field.
Step 6: Set Up Request Body:
- Switch to the Body tab below the URL field.
- Select "raw" and choose JSON from the drop-down menu.
- Enter your JSON data in the body. For example:
{
"title": "Coding",
"description": "Write a program in Next JS that connects the backend to MongoDB."
}
Step 7: Send the Request: Click the "Send" button to submit your request. Review the response displayed in the lower section of Postman, which includes status codes, headers, and response body. You should get message in the repsonse section at the bottom and the status code should be 201:
{
"message": "Task Created"
}
Step 8: Change name of collection and request: Click on the collection name in the left sidebar, and then hover over the collection to reveal the three-dot menu (...). Click on the Select "Rename" from the drop-down menu, enter the name nextjs-backend, and press Enter to save the change. Next, locate the New Request within that collection, hover over it to access the three-dot menu again, select "Rename," enter POST
Step 9: Save Your Request: To save your request for future use, click on the "Save" button.
Step 10: Lets us create another task. Go back to step 6 and lets add another task with the details below:
{
"title": "Next Js Docs",
"description": "Read the official next js documentation."
}
View Entries in MongoDB Atlas
Let us verify if the task has been added to the database.
Step 1: Log into MongoDB Atlas: Go to the MongoDB Atlas website and log in to your account.
Step 2: Select Your Project: Once logged in, select the organization and project that contains your desired cluster from the navigation bar.
Step 3: Access Your Cluster: Click on the "Clusters" tab in the sidebar to view your clusters.
Step 4: Open the Collections Page: Click the "Browse Collections" button for your cluster. This action will take you to the Data Explorer, where you can view all databases and collections associated with your cluster.
Step 5: Select Your Database: In the left pane, find and click on the nextjs-backend that contains the collection you created earlier.
Step 6: View Your Collection: After selecting the database, you will see a list of collections within it. Click on the tasks which will then load the entry we just made like the one below. You will be able to view the two entries we made:
Retrieve All Entries from the Database with a GET Request
The GET request is a fundamental HTTP method used to retrieve data from a specified resource on a server. When you make a GET request, you are essentially asking the server to send back information, such as web pages, images, or data in formats like JSON or XML. This method is commonly used in web development for fetching resources without altering the server's state, making it safe . In our case, we will use the GET request to retrieve all entries from the database, allowing us to display the stored data and verify that our API is functioning correctly.
Let us go back to route.js
file in the tasks
folder in the app
folder. Add the code below :
export async function GET() {
try {
await connectMongoDB();
const tasks = await Task.find();
return NextResponse.json({ tasks });
} catch (error) {
console.error("Error retrieving tasks:", error);
return NextResponse.json({ error: "Failed to retrieve tasks" }, { status: 500 });
}
}
Code explanation
export async function GET() {
GET(): This line exports an asynchronous function named GET
, which will handle incoming GET requests to this API route. It does not take any parameters, as it is designed to fetch data without needing additional input.
try {
try: The code execution begins with a try
block, which allows for error handling. This block contains operations that may potentially throw errors, such as connecting to the database and querying for data
await connectMongoDB();
connectMongoDB(): This function is called to establish a connection to the MongoDB database. This step is crucial because any subsequent database operations require an active connection. The await
keyword ensures that the function waits for the connection to be established before proceeding.
const tasks = await Task.find();
Task.find(): The code then calls Task.find()
, which is a Mongoose method used to retrieve all documents from the Task
collection in the database. The result is stored in the tasks
variable. The await
keyword is used here to ensure that the function waits for the database query to complete before moving on.
return NextResponse.json({ tasks });
Returning JSON Response: If both the connection and data retrieval are successful, this line returns a JSON response containing the retrieved tasks. The NextResponse.json()
method formats the response as JSON and automatically sets the appropriate content type. This allows clients (such as front-end applications) to easily consume and display the data.
} catch (error) {
catch (error): If any errors occur during the execution of the code within the try
block, they will be caught by this catch
block.
console.error("Error retrieving tasks:", error);
Logging Errors: Inside the catch block, this line logs an error message to the console along with the error details. This is useful for debugging purposes, as it provides insight into what went wrong during execution
return NextResponse.json({ error: "Failed to retrieve tasks" }, { status: 500 });
}
}
Error Response: If an error occurs, a JSON response is returned indicating that there was a failure in retrieving topics. The response includes an error message and sets the HTTP status code to 500 (Internal Server Error), signalling to clients that something went wrong on the server side.
GET API Testing With Postman
Now it is time to test if our API is working.
Step 1: Open Postman: Launch the Postman application on your computer or open the Postman extension in Visual Studio Code.
Step 2: Navigate to Your Collection: In the left sidebar, find and click on the collection you previously created named nextjs-backend.
Step 3: Add a New Request: Click on the three dots ... on the right side of the collection name. It will open a drop-down ad then choose "Add request" option to create a new request.
Step 4: Set Up Your GET Request: In the new request tab, enter the URL for your API endpoint in the request URL field. Enter the URL: http://localhost:3000/api/tasks
. Select GET as the HTTP method from the drop-down menu next to the URL field.
Step 5: Send the Request: Click the "Send" button to execute your GET request. Wait for a moment as Postman communicates with your API.
Step 6: Review the Response: After sending the request, check the response section at the bottom of Postman. You should see a JSON response containing all entries retrieved from your database under the tasks
key. If there are any errors, they will be displayed here as well, allowing you to troubleshoot if needed.
Step 7: Change name of collection and request: Click on the New request name in the left sidebar, and then hover over the collection to reveal the three-dot menu (⋮). Select "Rename" from the drop-down menu, enter the name GET, and press Enter to save the change.
Step 8: Save Your Request: Click on the "Save" button.
Remove Entries from the Database with DELETE Request
The DELETE request is an HTTP method used to remove a specific resource from a server. When a client sends a DELETE request, it indicates the intention to permanently delete the resource identified by the specified URL. Unlike other methods such as GET or POST, which are used for retrieving or creating resources, the DELETE method is specifically designed for deletion. In our application, we will use the DELETE request to remove entries from our database, such as deleting a specific topic or record.
Let us go back to route.js
file in the tasks
folder in the app
folder. Add the code below :
export async function DELETE(request) {
try {
const id = request.nextUrl.searchParams.get("id");
await connectMongoDB();
await Task.findByIdAndDelete(id);
return NextResponse.json({ message: "Task deleted" }, { status: 200 });
} catch (error) {
console.error("Error deleting task:", error);
return NextResponse.json({ error: "Failed to delete task" }, { status: 500 });
}
}
Code explanation
This code snippet defines a DELETE request handler for removing a specific task from a MongoDB database in a Next.js API route. Below is a breakdown of each part of the code:
export async function DELETE(request) {
DELETE(request): This line exports an asynchronous function named DELETE
, which will handle incoming DELETE requests to this API route. It takes one parameter, request
, representing the HTTP request object.
try {
try: The code execution begins with a try
block, which allows for error handling. This block contains operations that may potentially throw errors, such as connecting to the database and deleting a task.
const id = request.nextUrl.searchParams.get("id");
request.nextUrl.searchParams.get("id"): This line extracts the ID of the task to be deleted from the query parameters of the request URL. This allows clients to specify which task they want to delete by including its ID in the request URL
await connectMongoDB();
connectMongoDB(): This function is called to establish a connection to the MongoDB database. This step is crucial because any subsequent database operations require an active connection. The await
keyword ensures that the function waits for the connection to be established before proceeding
const deletedTask = await Task.findByIdAndDelete(id);
Task.findByIdAndDelete(id): The code attempts to delete the task with the specified ID using this Mongoose method. It searches for a document by its ID and removes it from the database. If successful, it assigns the deleted task's details to deletedTask
.
return NextResponse.json({ message: "Task deleted" }, { status: 200 });
} catch (error) {
Successful Deletion Response: If the deletion was successful and a task was found, a JSON response is returned confirming that the task has been deleted, along with a status code of 200 (OK).
console.error("Error deleting task:", error);
catch (error): If any errors occur during execution (e.g., connection issues or unexpected errors), they are caught by this catch
block.
return NextResponse.json({ error: "Failed to delete task" }, { status: 500 });
}
}
Error Response: If an error occurs, a JSON response is returned indicating that there was a failure in deleting the task. The response includes an error message and sets the HTTP status code to 500 (Internal Server Error), signalling to clients that something went wrong on the server side.
DELETE API Testing With Postman
Now it is time to test if our API is working.
Step 1: Open Postman: Launch the Postman application on your computer.
Step 2: Retrieve All Entries: Navigate to the nextjs-backend collection. Click on the previously saved GET request that retrieves all entries from your database. Click the "Send" button to execute the GET request. Review the response section at the bottom of Postman to see all entries returned from the database. Copy the ID of the entry you wish to delete. We will copy the id of the first entry for this guide.
Step 3: Add a New Request: Click on the three dots ... on the right side of the collection name. It will open a drop-down ad then choose "Add request" option to create a new request.
Step 4: Set Up Your DELETE Request: In the new request tab, enter the URL for your API endpoint in the request URL field. Enter the URL: http://localhost:3000/api/tasks
. Select DELETE as the HTTP method from the drop-down menu next to the URL field.
Step 5: Add query parameters. There are two ways to do this:
Option 1: Using Query Parameters
-
Add Query Parameters: Click on the "Params" tab located below the URL field. In the key-value pairs section, enter a key (e.g.,
id
) and its corresponding value (e.g.,123
), where123
is the ID of the task you wish to delete. This will append?id=123
to your URL. In our case we will use key value of id and the value 6775062f0058e4dab982b94
Option 2: Using Path Parameters
-
Using Path Parameters: Alternatively, you can directly include the ID in the URL path. Modify your URL to look like this:
http://localhost:3000/api/tasks?id=123
, where 123 is the ID of the task you wish to delete. In this case, you do not need to add any parameters in the Params tab. In our case we will use the URLhttp://localhost:3000/api/tasks?id=6775062f0058e4dab982b94
Step 6: Send the Request: Click the "Send" button to execute your DELETE request. Wait for a moment as Postman communicates with your API.
Step 7: Review the Response: After sending the request, check the response section at the bottom of Postman.
If the task is successfully deleted, you should receive a JSON response similar to this:
{
"message": "Task deleted"
}
The HTTP status code will typically be 200 OK, indicating that the request was processed successfully.
If you attempt to delete a task that does not exist (e.g., using an ID that has already been deleted), you should receive a JSON response indicating that the task could not be found:
{
"error": "Task not found"
}
In this case, the HTTP status code will be 404 Not Found, signalling that the resource you attempted to delete does not exist.
Step 8: Change name of collection and request: Click on the New request name in the left sidebar, and then hover over the collection to reveal the three-dot menu (⋮). Select "Rename" from the drop-down menu, enter the name DELETE, and press Enter to save the change.
Step 9: Save Your Request: click on the "Save" button.
Viewing Entries in MongoDB Atlas
Let us verify if the task has been deleted to the database.
Step 1: Log into MongoDB Atlas: Go to the MongoDB Atlas website and log in to your account.
Step 2: Select Your Project: Once logged in, select the organization and project that contains your desired cluster from the navigation bar.
Step 3: Access Your Cluster: Click on the "Clusters" tab in the sidebar to view your clusters.
Step 4: Open the Collections Page: Click the "Browse Collections" button for your cluster. This action will take you to the Data Explorer, where you can view all databases and collections associated with your cluster.
Step 5: Select Your Database: In the left pane, find and click on the nextjs-backend that contains the collection you created earlier.
Step 6: View Your Collection: After selecting the database, you will see a list of collections within it. Click on the tasks which will then load the remaining entries after deletion. There should be one entry because we delete the first entry:
Update Entries in the Database with a PUT Request
The PUT request is an HTTP method used primarily to update an existing resource on the server. When you send a PUT request, you are instructing the server to completely replace the current representation of the resource with the new data provided in the request body. This means that all fields of the resource must be included in the request. In our application, we will use the PUT request to update the details of an entry in our database, such as modifying a task's title, description. By implementing dynamic routing, we can specify which entry to update by including its ID in the URL. For example, a PUT request might look like http://localhost:3000/api/tasks/123
, where 123
is the ID of the task we want to update. The request body will contain the new data in JSON format, and upon successful execution, the server will respond with a confirmation message indicating that the update was successful.
To implement the PUT request for updating a task in a Next.js API route, you should create a folder named [id]
within the /app/api/tasks/
directory. Inside this folder, create a file named route.js
. The naming of the folder as [id]
indicates that it will handle dynamic routing based on the task ID, allowing the API to capture which specific task is being updated through the URL. This structure enables you to define a clear and organised endpoint, such as http://localhost:3000/api/tasks/123
, where 123
represents the ID of the task to be updated. By using square brackets, Next.js recognizes that this folder is meant for dynamic segments, facilitating the handling of various tasks without needing separate files for each ID.
Below is how the folder structure should look like:
Inside the route.js
write the following code :
export async function PUT(request, { params }) {
try {
const { id } = params;
const { newTitle: title, newDescription: description } = await request.json();
await connectMongoDB();
await Task.findByIdAndUpdate(id, { title, description }, { new: true });
return NextResponse.json({ message: "Task updated successfully" }, { status: 200 });
} catch (error) {
console.error("Error updating task:", error);
return NextResponse.json({ error: "Failed to update task" }, { status: 500 });
}
}
Code explanation
This code snippet defines a PUT request handler that allows clients to update an existing task in the database. Below is a breakdown of each part of the code:
export async function PUT(request, { params }) {
PUT(request, { params }): This line exports an asynchronous function named PUT
, which will handle incoming PUT requests to this API route. It takes two parameters: request
, representing the HTTP request object, and params
, which contains dynamic route parameters (in this case, the task ID).
try {
try: The execution begins with a try
block that allows for error handling. This block contains operations that may potentially throw errors, such as connecting to the database and updating a task.
const { id } = params;
const { id } = params;: The ID of the task to be updated is extracted from the params
object. This ID is dynamically captured from the URL when the request is made, allowing the API to identify which task needs to be updated.
const { newTitle: title, newDescription: description } = await request.json();
await request.json(): The new title and description for the task are retrieved from the request body. The properties are renamed using destructuring assignment for clarity, where newTitle
becomes title
and newDescription
becomes description
. This makes it clear what data is being used in the update operation.
await connectMongoDB();
connectMongoDB(): This function is called to establish a connection to the MongoDB database. This step is essential for performing any database operations. The await
keyword ensures that the function waits for the connection to be established before proceeding.
await Task.findByIdAndUpdate(id, { title, description }, { new: true });
Task.findByIdAndUpdate(id, { title, description }, { new: true }): The code attempts to update the task using this Mongoose method. It searches for a document by its ID and updates it with the new title and description. The { new: true }
option ensures that the updated document is returned after the operation, allowing you to see what was changed.
return NextResponse.json({ message: "Task updated successfully" }, { status: 200 });
Successful Update Response: If the update operation is successful, a JSON response is returned confirming that the task has been updated successfully, along with a status code of 200 (OK).
} catch (error) {
Catch Block: If any errors occur during execution (e.g., connection issues or if no task is found with the provided ID), they are caught by this catch
block.
console.error("Error updating task:", error);
Logging Errors: Inside the catch block, an error message is logged to the console along with the error details. This is useful for debugging purposes, providing insight into what went wrong during execution.
return NextResponse.json({ error: "Failed to update task" }, { status: 500 });
}
}
Error Response: If an error occurs during the update process, a JSON response is returned indicating that there was a failure in updating the task. The response includes an error message and sets the HTTP status code to 500 (Internal Server Error), signalling to clients that something went wrong on the server side.
PUT API Testing With Postman
Now it is time to test if our API is working.
Step 1: Open Postman: Launch the Postman application on your computer.
Step 2: Retrieve All Entries: Navigate to the nextjs-backend collection. Click on the previously saved GET request that retrieves all entries from your database. Click the "Send" button to execute the GET request. Review the response section at the bottom of Postman to see all entries returned from the database. Copy the ID of the entry you wish to delete. We will copy the id of the first entry for this guide.
Step 3: Create a New PUT Request: Hover over the nextjs-backend collection name in the left sidebar until three dots appear next to it. Click on these three dots to open a drop-down menu, then choose the "Add request" option to create a new request.
Step 4: Enter the URL for Your PUT API Endpoint: In the new request tab, enter the URL for your PUT API endpoint. For example, if you want to update a task with a specific ID, your URL might look like this: http://localhost:3000/api/tasks/[id]
, where [id]
is the ID of the task you copied earlier. In our case it will be http://localhost:3000/api/tasks/677507210058e4dab982b943
Step 5: Select HTTP Method: Choose PUT from the drop-down menu next to the URL field.
Step 6: Set Up the Request Body: Click on the "Body" tab below the URL field, select raw, and choose JSON from the drop-down menu that appears next to it. Enter the JSON data you want to update, for example:
{
"newTitle": "Updated Next Js Docs ",
"newDescription":"Read the updated official next js documentation."
}
Step 7: Send the Request: Click the "Send" button to execute your PUT request and wait for a moment as Postman communicates with your API.
Step 8: Review the Response: After sending the request, check the response section at the bottom of Postman. You should see a JSON response indicating whether the task has been updated successfully, such as:
{
"message": "Task updated successfully"
}
If there was an issue (e.g., if no task was found with that ID), you might see an error message like:
{
"error": "Task not found"
}
Step 9: Change name of collection and request: Click on the New request name in the left sidebar, and then hover over the collection to reveal the three-dot menu (...). Select "Rename" from the drop-down menu, enter the name PUT, and press Enter to save the change.
Step 10: Save Your Request: Click on the "Save" button.
View Entries in MongoDB Atlas
Let us verify if the task details have been updated in the database.
Step 1: Log into MongoDB Atlas: Go to the MongoDB Atlas website and log in to your account.
Step 2: Select Your Project: Once logged in, select the organization and project that contains your desired cluster from the navigation bar.
Step 3: Access Your Cluster: Click on the "Clusters" tab in the sidebar to view your clusters.
Step 4: Open the Collections Page: Click the "Browse Collections" button for your cluster. This action will take you to the Data Explorer, where you can view all databases and collections associated with your cluster.
Step 5: Select Your Database: In the left pane, find and click on the nextjs-backend that contains the collection you created earlier.
Step 6: View Your Collection: After selecting the database, you will see a list of collections within it. Click on the tasks which will then load the remaining entries after deletion. There title and description should be updated to :
"title": "Updated Next Js Docs ",
"description":"Read the updated official next js documentation."
Conclusion and Next Steps
In conclusion, you have successfully set up a robust Next.js application integrated with MongoDB, enabling essential CRUD (Create, Read, Update, Delete) operations on your data. This foundational setup lays the groundwork for more complex applications. You've learned to create dynamic API routes that facilitate interaction with your database and effectively manage tasks. Utilizing Postman for testing has also provided valuable experience in verifying the functionality of your endpoints. As you continue your development journey, stay tuned for the next steps where I will demonstrate how to seamlessly combine the backend with the front end. We’ll enhance user experience and functionality while incorporating styles to create a polished application. This knowledge equips you with the skills necessary to build scalable web applications and explore advanced features in both Next.js and MongoDB.
I also want to share that my journey into JavaScript and frameworks like Next.js began at Zone01 Kisumu, where I am an apprentice software developer. The hands-on experience there has been invaluable as I continue to grow in this field. Exciting developments are ahead, so keep following along! Happy coding! This version retains the key messages while being more concise, making it easier for readers to digest
Top comments (0)