The Next JS is a javascript framework that makes the d development of the React-based application easier. It also boosts the speed of static websites. In this article, we will learn how to create a simple Next JS and MongoDB CRUD application.
We will create a simple To-Do application using the Next JS and persist the To-Do items into the MongoDB NoSQL database.
We will also use Tailwind CSS for application styling.
Table of Contents
- Creating the Next JS MongoDB CRUD app
- Creating the navigation bar for our app
- Adding the Create To Do functionality
- Updating the add To Do page
- Creating the To Do creation API
- Testing the add To Do functionality
- Displaying the To Dos from MongoDB
- Implementing the delete functionality
- Adding the functionality for To Do status update
- Testing the Next JS MongoDB CRUD application
- Conclusion
Creating the Next JS MongoDB CRUD app
We need to install Node JS in our system before proceeding with the creation of our Next JS app.
Create a new Next JS application with the tailwind CSS support. Run the below command in the command prompt/ terminal.
npx create-next-app -e with-tailwindcss to-do-app
The above command will create the basic folder structure of our application. It also installs the Tailwind CSS library into the application.
Also, install the MongoDB node package.
npm install mongodb
Below is the Next JS app folder structure.

Few of the important files and folders here are:
- public: This directory holds all static files of our application.
- pages: Holds the route based files.
- package.json: contains all the installed package details and scripts.
- pages/index.tsx: This is the home page of our application.
- pages/_app.tsx: This is the global app component that renders our application’s home page.
To run the application locally, use the below command.
npm run dev
Now the default server starts, and we can find this page below.

Update the content from the pages/index.tsx file as shown below.
import type { NextPage } from 'next' const Home: NextPage = () => { return ( <h2>Hello world!!</h2> ) } export default Home
Now we get our classic Next JS “hello world” app.

Creating the navigation bar for our app
Create a components folder, and inside that folder, create a navigation folder.
Create a navigation.tsx file as shown below.
import Link from "next/link" import { Fragment } from "react" const Navigation = () => { return ( <Fragment> <ul className="flex bg-red-300"> <li className="mr-6 px-4 my-5 font-bold hover:text-white"> <Link href="/">To Do APP</Link> </li> <li className="mr-6 px-4 my-5 font-bold hover:text-white"> <Link href="/add-todo">Add To Do</Link> </li> </ul> </Fragment> ) } export default Navigation
We have Created a simple navigation bar with two links. The To-Do APP link is for the home page and the second link opens the Add To-Do page.
Update the pages/_app.tsx file as shown below.
We are adding the Navigation component before rendering any other component.
Also, To enable the tailwind CSS globally, we can import the tailwind CSS file inside the pages/_app.tsx file.
import '../styles/globals.css' import 'tailwindcss/tailwind.css' import type { AppProps } from 'next/app' import { Fragment } from 'react' import Navigation from '../components/navigation/navigation' function MyApp({ Component, pageProps }: AppProps) { return ( <Fragment> <Navigation/> <Component {...pageProps} /> </Fragment> ) } export default MyApp
Create a add to do page by creating a add-todo folder under the pages folder and create a index.tsx file.
const AddTodo = () => { return ( <h2>Add to do page!</h2> ) } export default AddTodo
Run the application using the command “npm run dev“
Now our navigation bar is ready with two items.


Adding the Create To Do functionality
Let us add the create To-Do functionality to our application. After the completion of this section, we will be able to create new To-Do Items using add to do page. The item gets saved to the MongoDB database.
Creating a To Do form
Inside the components folder, create a todoForm folder, and add a todoForm.tsx file inside it. We will use this component inside our add To Do page.
Add the below content.
import { useRef } from 'react' function TodoForm(props: any) { const {addTodoHandler} = props const headingRef = useRef() const descriptionRef = useRef() const formSubmitHandler = (e: any) => { e.preventDefault() const formData = { heading : headingRef.current.value, description: descriptionRef.current.value } addTodoHandler(formData) } return ( <form className="max-w-lg w-full mx-auto" onSubmit={formSubmitHandler}> <div className="flex flex-wrap mb-6 -mx-3"> <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"> To Do Item </label> <input className="appearance-none block w-full bg-gray-200 text-gray-700 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white" type='text' placeholder="heading" ref={headingRef} /> </div> <div className="flex flex-wrap mb-6 -mx-3"> <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"> Description </label> <input className="appearance-none block w-full bg-gray-200 text-gray-700 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white" type='text' placeholder="description" ref={descriptionRef} /> </div> <button className="px-4 py-2 my-1 font-semibold text-red-700 bg-transparent border border-red-500 rounded hover:bg-red-500 hover:text-white hover:border-transparent" type="submit"> Add </button> </form> ) } export default TodoForm
We are creating a form that expects two input data from the user. The title is the title of our To-Do item, and the description field is for a short description of the item.
We are using React’s userRef to extract the user input values. Once the user submits the form, the handler function is invoked, which we will create in the next step.
Our user form component is ready now. We can render this component inside out add To Do page.
Updating the add To Do page
Update the index.tsx file under the pages/add-todo with the below content.
import { useRouter } from "next/router" import { Fragment } from "react" import TodoForm from "../../components/todoForm/TodoForm" const AddTodo = () => { const router = useRouter() const addTodoHandler = async (data: any) => { console.log("sending data::"+data) const response = await fetch("/api/new-todo", { method: "POST", body: JSON.stringify(data), headers: { "content-Type" : "application/json" } }) const res = await response.json() router.push("/") } return ( <Fragment> <TodoForm addTodoHandler={addTodoHandler} /> </Fragment> ) } export default AddTodo
We render the ToDoForm component whenever the user visits the page /add-todo.
We have created a function with the name addTodoHandler. The function is invoked from the ToDoForm component that we created earlier.
Once the function is invoked, we use the Javascript fetch API to invoke the URI: /api/new-todo. Finally, we redirect the user to the home URL(/).
Creating the To Do creation API
Create a file called new-todo.tsx under the api folder and add the below content.
This file will contain the handler function that handles the To-Do Item create request.
import { MongoClient } from "mongodb" async function handler(req: any, resp: any){ if(req.method !== 'POST') return const {heading, description} = req.body const done = "false" if(!heading || !description) return const client = await MongoClient.connect("{mongo connection string}") const db = client.db() const collection = db.collection("todos") const result = await collection.insertOne({heading, description, done}) client.close() resp.status(201).json({ todo: result, message: "To do created" }) } export default handler
We are validating the request and processing only the http POST request.
We are also validating the data before persisting it into MongoDB.
Also, we are using the MongoClient component from the node mongodb package to connect to the database and persist the data.
Testing the add To Do functionality
Now, if we run the application and navigate to the /add-to page, we get the below page.

Once we enter the To-Do item title and description and click on the Add button, data gets persisted to the MongoDB collection.
Displaying the To Dos from MongoDB
Now, let’s display the saved To-Do items on our application’s home page. Let’s create a new component that displays the To-Do items in a tabular format.
Creating the To Do Item component
Create a new folder under the component folder with the name todoItem and create a file todoItem.tsx
import { Fragment } from "react" function TodoItem(props: any) { const { id, heading, description, done } = props return ( <Fragment> <td className="py-5 font-bold text-blue-600">{heading}</td> <td className="py-5">{description}</td> <td className="py-5"> { (done === "true") ? (<img width="25" src="../../check.png" />) : (<img width="25" src="../../uncheck.jpeg" />)} </td> <td className="px-4 py-2 my-1 font-semibold text-red-700 bg-transparent border border-red-500 rounded hover:bg-red-500 hover:text-white hover:border-transparent"> <button className="id">Delete</button> </td> </Fragment> ) } export default TodoItem
We have added some table data that displays the To-Do item details along with its status on the home page.
The component expects the id, heading, and description and done as component parameters.
We will implement the delete and update functionalities in the following section.
Using the component to display the To Do items on home page
Update the page/inex.html page with the below content.
We will use the TodoItem component to display the records fetched from the back-end MongoDB collection.
import { MongoClient } from 'mongodb' import type { NextPage } from 'next' import Head from 'next/head' import { Fragment } from 'react' import TodoItem from '../components/todoItem/todoItem' const Home: NextPage = (props: any) => { return ( <Fragment> <Head> <link href="https://fonts.googleapis.com/css2?family=Montserrat&display=swap" rel="stylesheet" /> </Head> <div className="container mx-auto px-4 sm:px-8"> <table className="min-w-full leading-normal"> <thead> <tr> <th className="px-5 py-5 border-b-2 border-blue-200 bg-blue-100 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">To Do</th> <th className="px-5 py-5 border-b-2 border-blue-200 bg-blue-100 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">Desc.</th> <th className="px-5 py-5 border-b-2 border-blue-200 bg-blue-100 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">Done</th> <th className="px-5 py-5 border-b-2 border-blue-200 bg-blue-100 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider"></th> </tr> </thead> <tbody> { props.todos.map((todo: any) => ( <tr key={todo.id} className="px-5 py-5 border-b-2 border-black-200"> <TodoItem id = {todo.id} heading={todo.heading} description={todo.description} done={todo.done} /> </tr> ) ) } </tbody> </table> </div> </Fragment> ) } export async function getStaticProps(context: any){ const client = await MongoClient.connect("{mongo connection string}") const todoCollection = client.db().collection("todos") const todoArray = await todoCollection.find().toArray() client.close() return { props:{ todos : todoArray.map(todo => ({ heading: todo.heading, description: todo.description, done: todo.done, id: todo._id.toString() })) }, revalidate: 60 } } export default Home
We are using the getStaticProps function of the Next JS to pre-render the To-Do items from MongoDB during the build time.
Next JS uses the revalidate property to re-generate the page by rendering the data every 60 seconds.
Testing the retrieve to do functionality
Run the application, and now we can see the below page.

Implementing the delete functionality
Let’s add delete functionality to our To-Do application.
Creating the Delete API
Create a folder delete under api folder and create a file with the name [todoId].tsx
import { MongoClient, ObjectId } from "mongodb" async function handler(req: any, resp: any){ const {todoId} = req.query if(req.method !== 'DELETE') return const client = await MongoClient.connect("{mongo connection string}") const db = client.db() const collection = db.collection("todos") const result = await (await collection.deleteOne({_id: new ObjectId(todoId)})).deletedCount; client.close() console.log("deleted count::::"+result) return resp.json({ todo: result, message: "To do deleted" }) } export default handler
Here, the api/delete/{todoId} API is invoked by passing the To-Do item id. The API deletes the document by passing the given id.
Calling the delete function from To Do item component
We need to add the delete functionality by adding a new function to our TodoItem component. The function is invoked whenever the user clicks on the delete button of a To-Do item.
Modify the todoItem.tsx file with below content.
import { Fragment } from "react" import { useRouter } from 'next/router' function TodoItem(props: any) { const { id, heading, description, done } = props const router = useRouter() //delete todo: const deleteTodo = async (todoId: any) =>{ const resp = await fetch(`/api/delete/${todoId}`, { method: 'DELETE' }) .then(res => console.log("SUCCESS:: "+ res.json())) .catch(e => console.log("ERROR:" + e)) router.push("/") } return ( <Fragment> <td className="py-5 font-bold text-blue-600">{heading}</td> <td className="py-5">{description}</td> <td className="py-5"> { (done === "true") ? (<img width="25" src="../../check.png" />) : (<img width="25" src="../../uncheck.jpeg" />)} </td> <td className="px-4 py-2 my-1 font-semibold text-red-700 bg-transparent border border-red-500 rounded hover:bg-red-500 hover:text-white hover:border-transparent"> <button className="id" onClick={() => deleteTodo(id)} >Delete</button> </td> </Fragment> ) } export default TodoItem
Adding the functionality for To Do status update
We also have to provide the user an option to mark the To-Do item as done. Let’s implement this functionality now.
Creating the update to do API
Create a folder under api folder and a file […param].tsx
This API supports multiple HTTP path/request parameters.
import { MongoClient, ObjectId } from "mongodb" async function handler(req: any, resp: any){ if(req.method !== 'GET') return var query = { _id: new ObjectId(req.query.param[0].toString())}; const options = {upsert: true } const updateTodo = { $set: { done: req.query.param[1] } }; const client = await MongoClient.connect("{mongo connection string}") const db = client.db() const collection = db.collection("todos") const result = await collection.updateOne(query, updateTodo, options); client.close() console.log("updated record::::"+result) return resp.json({ todo: result, message: "To do updated!" }) } export default handler
We are using the HTTP GET API to implement our update functionality, as we have to update only one field on our To-Do item.
We are using the upsert functionality to update the MongoDB record.
Calling the update to do API
To update the To-Do item status, we need to create a function to handle the user click events on the done table column.
Modify todoItem.tsx with below.
import { Fragment } from "react" import { useRouter } from 'next/router' function TodoItem(props: any) { const { id, heading, description, done } = props const router = useRouter() //delete todo: const deleteTodo = async (todoId: any) =>{ const resp = await fetch(`/api/delete/${todoId}`, { method: 'DELETE' }) .then(res => console.log("SUCCESS:: "+ res.json())) .catch(e => console.log("ERROR:" + e)) router.push("/") } //togle todo: const togleDone = async (todoId: any, done: any) =>{ const resp = await fetch(`/api/toggle/${todoId}/${done}`, { method: 'GET' }) .then(res => console.log("SUCCESS:: "+ res.json())) .catch(e => console.log("ERROR:" + e)) router.push("/") } return ( <Fragment> <td className="py-5 font-bold text-blue-600">{heading}</td> <td className="py-5">{description}</td> <td className="py-5"> { (done === "true") ? (<img width="25" src="../../check.png" onClick={() => togleDone(id, "false")} />) : (<img width="25" src="../../uncheck.jpeg" onClick={() => togleDone(id, "true")} />)} </td> <td className="px-4 py-2 my-1 font-semibold text-red-700 bg-transparent border border-red-500 rounded hover:bg-red-500 hover:text-white hover:border-transparent"> <button className="id" onClick={() => deleteTodo(id)} >Delete</button> </td> </Fragment> ) } export default TodoItem
If the user clicks on the to-do item, the click event invokes the togleDone function. We are also passing the record’s id and the done status field so that we can update the To-Do item in MongoDB.
Testing the Next JS MongoDB CRUD application
Save the files and run the application(If it’s not running already).
We can now perform all the create/update/delete operations on our application, as shown below.

Conclusion
In this article, we learned how easy it is to create a Next JS and MongoDB CRUD application.
We also learned how Next JS makes it easier to create different application routes and supports static site generation to improve the application loading speed.
The code is available on Github.