- Published on
CRUD REST API Node.js Using Express.js and MongoDB
Regarding my previous post, Automated Deployment Node.js Using Github Actions in this post, I want to create a tutorial for a CRUD API for a to-do list app using Node.js with the Web Framework Express.js and MongoDB as the database.
Preparation
The list of tools I used while creating this tutorial are:
node.js v15.0.1
npm v7.0.3
yarn v1.22.5
mongodb v3.6.3
Alright... let’s go ahead and create a new directory and initialize the package.json for the project
mkdir nodejs-todo-app
cd nodejs-todo-app
yarn init -y
Then install the required packages
yarn add express dotenv cors mongoose
Before moving on to the steps, let’s create the .env file in the root directory first.
PORT=1337
MONGODB=mongodb://localhost:27017/nama_database
We’ll proceed step by step, starting with creating the model, handler / controller (for Create, Read, Update, Delete processes), then route, and the main file, index.js.
Create Model
First, we create the model for the project. Here, we use the mongoose library for the communication with MongoDB.
Create a models folder in the root directory
mkdir models
Then create the file models/todo.js
// models/todo.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const model = new Schema({
// This field contains the activities to be done
text: {
type: String,
required: true
},
// This field indicates whether the activity has been completed
checked: {
type: Boolean,
required: true
}
}, {
// This option is optional
// It will automatically creates the creation date and update date every time a document is created or updated
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
}
});
module.exports = mongoose.model('todos', model);
Create Handler
Next, we create the handler to process create, read, update, and delete data
Create a handlers folder in the root directory
mkdir handlers
Then create the file handlers/todo.js, and import the model we created earlier.
// handlers/todo.js
const modelTodo = require('../models/todo');
// Create Process
// Read All Process
// Read By ID Process
// Update Process
// Delete Process
Create
Add the create function by adding the following code:
// Create Process
exports.create = async (req, res) => {
// Retrieve data from the text and checked fields
const { text, checked } = req.body;
try {
// Send an error response if the text or checked field is not found
if (text === undefined || checked === undefined) {
return res.status(422).json({ message: 'Parameter yang dimasukkan tidak lengkap.' });
}
// Create a new document
const todo = await modelTodo.create({ text, checked });
// Send a response with the payload of the newly created document
return res.status(201).json(todo);
}
catch (error) {
console.log(error);
return res.status(500).json(error);
}
}
Read All
Add the function to read all or retrieve all existing data by adding the following code:
// Read All Process
exports.getAll = async (req, res) => {
try {
// Retrieve all data
const todos = await modelTodo.find();
// If we don't add .status(xxx), the response code will be 200
return res.json(todos);
}
catch (error) {
console.log(error);
return res.status(500).json(error);
}
}
Read By ID
Add the function to read by ID or retrieve data based on the ID by adding the following code:
// Read By ID Process
exports.getOne = async (req, res) => {
// Retrieve the id parameter from the URL
const { id } = req.params;
try {
// If the id parameter is not present, send an error response
if (id === undefined) {
return res.status(422).json({ message: 'Parameter id tidak ditemukan.' });
}
// Retrieve data based on id
const todo = await modelTodo.findOne({ _id: id });
// If data is not found, send a 404 status response
if ( ! todo) {
return res.status(404).json({ message: 'Data tidak ditemukan.' });
}
return res.json(todo);
}
catch (error) {
console.log(error);
return res.status(500).json(error);
}
}
Update
The update process is quite similar to the create process, except that we need to obtain the ID parameter to locate the document that we want to modify
// Update Process
exports.update = async (req, res) => {
try {
// Retrieve the id parameter
const { id } = req.params;
// Retrieve data from the fields text and checked
const { text, checked } = req.body;
// Send an error response if the fields text or checked are not found
if (text === undefined || checked === undefined) {
return res.status(422).json({ message: 'Parameter yang dimasukkan tidak lengkap.' });
}
// Update the data
const todo = await modelTodo.updateOne(
{
_id: id
},
{
text,
checked
});
// If no changes are made to a document, it means the document was not found
// Send a 404 status response
if (todo.nModified === 0) {
return res.status(404).json({ message: 'Data tidak ditemukan.' });
}
return res.json({ message: 'Sukses.' });
}
catch (error) {
console.log(error);
return res.status(500).json(error);
}
}
Delete
Lastly, the process is delete
// Delete Process
exports.delete = async (req, res) => {
// Retrieve the id parameter
const { id } = req.params;
try {
// delete the data
const todo = await modelTodo.deleteOne({ _id: id });
// If no deleted document, it means the document was not found
// Send a 404 status response
if (todo.deletedCount === 0) {
return res.status(404).json({ message: 'Data tidak ditemukan.' })
}
return res.json({ message: 'Sukses.' });
}
catch (error) {
console.log(error);
return res.status(500).json(error);
}
}
Create Route
The next step is to set up the routing by creating a routes directory and then creating the index.js and todo.js files.
mkdir routes
Create the file routes/todo.js:
// routes/todo.js
const express = require('express');
const router = express.Router();
// Import the handler we created earlier
const todoHandler = require('../handlers/todo');
// This route maps to http://localhost:1337/todos
router.route('/')
// Route for the Read All process
.get(todoHandler.getAll)
// Route for the Create process
.post(todoHandler.create);
// This route maps to http://localhost:1337/todos/:id
router.route('/:id')
// Route for the Read By ID process
.get(todoHandler.getOne)
// Route for the Update process
.put(todoHandler.update)
// Route for the Delete process
.delete(todoHandler.delete);
module.exports = router;
Create the file routes/index.js:
// routes/index.js
const express = require('express');
const router = express.Router();
// mport the todo routes we created earlier
const todoRoutes = require('./todo');
// This route maps to http://localhost:1337/
router.get('/', async (req, res) => {
res.json({ message: 'Halo!' });
});
// This route maps to http://localhost:1337/todos
router.use('/todos', todoRoutes);
module.exports = router;
Create the Main File
Finally, let's create the main file to run the server. Create a file named index.js in the root directory of the project:
// ./index.js
// to load the .env file
require('dotenv').config()
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
const port = process.env.PORT || 1337;
const dbUrl = process.env.MONGODB;
// Import the index.js route file we created earlier
// to import an index.js file inside a folder, you only need to reference the folder name
const routes = require('./routes');
const app = express();
// open the connection to MongoDB
const mongooseOptions = {
useNewUrlParser: true,
useUnifiedTopology: true
};
mongoose.connect(dbUrl, mongooseOptions);
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
// MongoDB is successfully connected
console.log('MongoDB connected.');
});
// finished opening the connection
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(routes);
app.listen(port, () => {
console.log('The magic happen on port :' + port);
});
Run the Server
Finally, try running the server using node, pm2, or nodemon:
$ node index.js
The magic happen on port :1337
MongoDB connected.
If you see this message, the server is running successfully.
Testing
You can test the API using postman, curl, or httpie.
I have created a Postman collection to make testing easier; you can download it here.
You can check the this project repository on my GitHub here.
That's it for now, see you~