Building CRUD apps with Next.js
Next.js is a widely used and open-source framework in web development. It's a go-to framework in terms of both server-side and client-side rendering. Next.js provides configurations for React applications and adds its own features on top of the structure as well.
Note: Next.js offers vast features and can be learned through building applications here.
The keyword CRUD
CRUD is a highly popular term amongst developers.
It is an acronym for the following words:
Create
Read
Update
Delete
CRUD applications
The term CRUD is mainly used to define the functionalities of applications. CRUD applications refer to software applications that allow users to manage data by creating, reading, updating, and deleting records, usually through user interfaces.
A great way to start learning Next.js is to be able to build a fully functional CRUD application in it. Without further ado, let's get started!
Setting up a Next.js project
Before we move on to the code, let's learn how to get our project up and running first. The procedure for a Next.js project is quite similar to that of React in terms of both commands and syntax.
First, we're going to open up a directory and initialize our project in the same directory.
npx create-next-app .
Then, the terminal will prompt us with a few settings that we'll say either "Yes" or "No" to according to our project scope and requirements.
The project structure should be similar to the given structure.
We can now run our application using this command:
npm run dev
A full stack notes app
In today's Answer, we will be creating a notes app that allows users to see the listed notes, create new ones, and update or delete already existing ones.
Technologies used
Next.js will be used for the front end.
Express and some middleware libraries will be used for the back-end.
Next.js front-end
First and foremost, let's set up the front end for our code.
"use client";
We mention
"use clientat the top of the code to show that this code represents the client code.
const [notes, setNotes ] = useState([]);const [inputValue, setInputValue] = useState("");const [editIndex, setEditIndex] = useState(null);
We define the states needed for our interface. This includes the
notesthemselves, the inputinputValueof the user for the new note and aneditIndexwhich is used for conditional rendering. This means that if theeditIndexholds an index and an editable field opens up for updating the note's content.
const fetchNotes = async () => {try{const response = await fetch("http://localhost:4000/api/notes");const data = await response.json();setNotes(data);}catch(error){console.log("Error encountered: ", error);}}
A
useEffecthook can make use of the functionfetchNotesto retrieve the existing notes from the server side and display them to the user.
const addNote = async () => {if(inputValue !== ""){try{const response = await fetch("http://localhost:4000/api/notes", {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ content: inputValue })})const data = await response.json();setNotes([...notes, data]);setInputValue("");}catch(error){console.log("Error encountered: ", error);}}}
To add a note, we use the
addNotefunction that gets triggered once the user presses the button. If theinputValuei.e. the note content is not empty, a new note is added both through thesetNotes([...notes, data])call and on the backend.
const updateNote = async (index, updatedValue) => {const noteToUpdate = notes[index];try {await fetch(`http://localhost:4000/api/notes/${index}`, {method: 'PUT',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ content: updatedValue })});noteToUpdate.content = updatedValue;setNotes([...notes]);} catch(error){console.log("Error encountered: ", error);}}
Next, we create the function
updateNotewhich takes theindexand new content i.e.updatedValueof the note. It updates the server and then uses the index of the note to access it usingnoteToUpdate = notes[index]and update its content usingnoteToUpdate.content = updatedValue;.
const deleteNote = async (index) => {const yesOrNo = window.confirm("Are you sure you want to delete this note?");if(yesOrNo){try {await fetch(`http://localhost:4000/api/notes/${index}`, {method: 'DELETE'})setNotes(notes.filter((_, i) => i !== index));} catch(error){console.log("Error encountered: ", error);}}};
Finally, we make the
deleteNotefunction. It simply takes theindexand filters out the note to be deleted through thefiltermethod. We also update the server. Prior to the deletion, it takes confirmation from the user.
const editNote = (index) => {const updatedNotes = [...notes];updatedNotes[index].editable = true;setNotes(updatedNotes);setEditIndex(index);};const saveNote = (index) => {const updatedNotes = [...notes];updatedNotes[index].editable = false;setNotes(updatedNotes);setEditIndex(null);};
The
editNotefunction allows us to set a specific note as editable by changing itseditableproperty totrue, while thesaveNotefunction saves the changes and sets itseditableproperty tofalse. This allows toggling of buttons.
Running the front-end
Express back-end
Having set up our front-end, let's now see where the server fetch requests actually go and how they're implemented.
const express = require('express');const bodyParser = require('body-parser');const cors = require('cors');const app = express();app.use(cors());app.use(bodyParser.json());const port = 4000;
We import
expressfor setting up our server,bodyParserto help extract the data sent in the request whilecorshelps enable cross-origin requests (different ports). We make our server listen on theport4000.
Note: Make sure to replace the port and endpoints when running it on your machine.
const notes = [];app.get('/api/notes', (req, res) => {res.json(notes);});
We keep an initially empty array
notesto keep track of the CRUD operations. The endpoint/api/notesis used as a GET request to retrieve the notes at any time.
app.post('/api/notes', (req, res) => {const { content } = req.body;const newNote = { content, editable: false };notes.push(newNote);res.json(newNote);});
To create new notes, we use the endpoint
/api/notesas a POST request and thecontentis sent in the body of the request.
app.put('/api/notes/:index', (req, res) => {const { index } = req.params;const { content } = req.body;if (index >= 0 && index < notes.length) {notes[index].content = content;res.sendStatus(200);} else {res.sendStatus(404);}});
To update a specific note, the
indexis sent in the URL of the request. We use thisindexto update ournotesarray by setting the newcontentsent in a PUT request's body.
app.delete('/api/notes/:index', (req, res) => {const { index } = req.params;if (index >= 0 && index < notes.length) {notes.splice(index, 1);res.sendStatus(200);} else {res.sendStatus(404);}});
We also send the
indexin the request URL for note deletion and use a DELETE request for this purpose. Using thisindexwe can usespliceto remove that note from thenotesarray accordingly.
Running the server
Complete code
Congratulations! We've set up the whole application in a few steps, and it can now be run and experimented with by simply clicking the "Run" button.