A basic CRUD MVC app with Node, Express JS, Mongo DB and EJS

Peter posted this on June 20, 2022

Following up on this post, I’ve been using a Google Sheet to calculate the number of drugs I’d need to buy every month for my mum.

I’m a bit free now though & I’ve decided to make it into a web app I can access responsively from anywhere.

Though we use PHP (LAMP stack) more of the time  in building software projects, I’d be building this myself so I’d be using the Javascript environment (love stretching my brain) and putting every step down here (so I can refer to it too :D)


Before writing any code, what has to be done needs to be understood first of all as that would determine the database structure as well as any routes and code that would be needed.

Information that would be handled are: Name of Drug (text), pills in a card, pills in a pack and how many are taken each day (all numbers).

Operations required would be adding drugs to the database, editing this drugs, viewing the drugs and deleting them from the database when no longer needed.


This assumes you’ve Node JS installed (I have v16.14.0) and are conversant with Git, HTML, CSS and Javascript.

I’d add a link to the Git Repo so you can fork it if you want to.


First of all, create your working Folder

From Terminal,

md drug-monitor
cd drug-monitor
touch server.js
git init

This creates the folder I’d be working with, creates an empty server.js file  and initialises the Git environment (for version control)

Next I’d create the package.json file using npm which would hold app information so it can be installed with the same dependencies anywhere it is required.

npm init

After filling all app details, a package.json file would be created and populated with the information.

Next, all dependencies would be installed using npm.

npm install axios body-parser dotenv ejs express mongoose morgan nodemon

I’d comment what each does in the code as we go along

Separation of Concerns/Software Architecture

In order to make it easy to manage and keeping to best practice,  code in production software is usually split with scripts performing the same function bundled together.

The MVC (Model View Controller) method would be used for this to separate the logic and data from presentation/UI.

The folder structure we would create for this is below:

Creating the HTTP Server

First thing to do would be creating the HTTP server using Express.

Copy the following code into your empty server.js and save

  1. const express = require('express');//we installed express using npm previously and we are indicating that it would be used here
  2. const app = express(); //this assigns express to the variable "app" - anything else can be used.
  3. const PORT = process.env.PORT || 3100; //uses either what's in our env or 3100 as our port (you can use any unused port)
  6. app.get('/', function(req, res) {//this listens for a get request for "/" the homepage
  7.   res.send('Hello World'); //and sends this response to the console once the request is received
  8. })
  11. app.listen(process.env.PORT || PORT, function() {//specifies port to listen on
  12. 	console.log('listening on '+ PORT);
  13. 	console.log('Welcome to the Drug Monitor App at http://localhost:${PORT}');
  14. })

Return to your terminal and run

npm start

You would see the console logs in the app.listen printed out on your terminal

After this, go to your browser and enter localhost:3100 (or any port you specified)

You should see the response in your app.get printed on the screen.

This is a quick test that everything is working and we can go on to the main building.

Next thing to do would be to create a .env file

touch .env

This file (enabled by dotenv installed earlier) can be used to save your sensitive information (so I can comfortably push the code publicly)

add a PORT variable to the .env file

PORT = 3000

Add the following to the beginning of server.js so it knows it’s going to be using the .env, body-parser and morgan (comments explain what they do)

const bodyParser = require('body-parser');//body-parser makes it easier to deal with request content by making it easier to use
const dotenv = require('dotenv').config();//indicates we would be using .env
const morgan = require('morgan');//this logs requests so you can easily troubleshoot

Add the use statements for these in your server.js (it should be like below once you are done).

  1. const express = require('express');//we installed express using npm previously and we are indicating that it would be used here
  2. const app = express(); //this assigns express to the variable "app" - anything else can be used.
  3. const bodyParser = require('body-parser');//body-parser makes it easier to deal with request content by making it easier to use
  4. const dotenv = require('dotenv').config();//indicates we would be using .env
  5. const morgan = require('morgan');//this logs requests so you can easily troubleshoot
  6. const PORT = process.env.PORT || 3100; //uses either what's in our env or 3100 as our port (you can use any unused port)
  8. app.set('view engine', 'ejs');//Put before app.use, etc. Lets us use EJS for views
  9. //use body-parser to parse requests
  10. app.use(bodyParser.urlencoded({extended:true}));
  11. //indicates which is the folder where static files are served from
  12. app.use(express.static('assets'));
  13. //use morgan to log http requests
  14. app.use(morgan('tiny'));
  16. app.get('/', function(req, res) {//this listens for a get request for "/" the homepage
  17.   res.send('Hello World'); //and sends this response to the console once the request is received
  18. })
  20. app.listen(PORT, function() {//specifies port to listen on
  21. 	console.log('listening on '+ PORT);
  22. 	console.log(`Welcome to the Drug Monitor App at http://localhost:${PORT}`);
  23. })


Creating Views (the V in MVC)

Views are the what the end user sees and interacts with.

For this app, the views are going to be rendered using EJS (Embedded Javascript) – you can use pug or any other templating engine of your choice.

Open the ‘views’ folder and create a new file: index.ejs

This can be formatted as a simple HTML file as below:


To get the server to render this ejs file rather than the text from before, we need to return to server.js and replace

res.send('Hello World');



Open your chosen port in your browser and confirm that your index.ejs file is being served.

We would now build a simple home page to for the app which would include a button to add new drugs, a table displaying the drugs in the database as well as buttons to edit and delete drugs.

The index.ejs file would be as follows (please note I used fontawesome for the icons and it is referenced in the <head>


If everything is working up to this point, let us split the index.ejs file to make the code more manageable (especially by a team)

in the includes folder (under views), create two files _header.ejs and _footer.ejs

split the code into both files and include them on the index.ejs meaning your files would be as follows:







Reload your index to confirm everything still works. Styling would be done later.

Now we can create other pages.

First is a form to add new users.

Create a new file, add_drug.ejs in the views folder and copy everything from index.ejs into it.


Create a new file, update_drug.ejs in the views folder and copy everything from add_drug.ejs into it then edit as below:


Now we need to add the path to these new forms to the server.js
Add the following after the first app.get in your server.js

app.get('/add-drug', function(req, res) {//this listens for a get request for "/add-drug" from any hyperlink
  res.render('add_drug'); //tells server to respond with add_drug.ejs (.ejs is optional)
app.get('/update-drug', function(req, res) {

Visit both pages in your browser to see they load:


If all goes well up to this, we can now move to separating the routes from the main server.js file.

In the routes folder under the server folder, create a new file: routes.js

Add this to the beginning;

const express = require('express');// As in the server.js
const route = express.Router(); //Allows us use express router in this file

Copy the three app.get statements in server.js and replace the app.get with route.get (route we just declared in line 2 of routes.js

route.get('/', function(req, res) {//this listens for a get request for "/" the homepage
  res.render('index.ejs'); //tells server to respond with index.ejs (.ejs is optional)
route.get('/add-drug', function(req, res) {//this listens for a get request for "/add-drug" from any hyperlink
  res.render('add_drug'); //tells server to respond with add_drug.ejs (.ejs is optional)
route.get('/update-drug', function(req, res) {

And at the bottom of the routes.js file, add the following to make it usable elsewhere

module.exports = route;//exports this so it can always be used elsewhere

In server.js, delete the 3 app.get() statements and replace with the following

//load the routes
app.use('/',require('./server/routes/routes'));//Pulls the routes file whenever this is loaded

Check that all routes still work as intended.

Next, we would separate the call back functions into a separate file.
In the services folder under server, create a new file: render.js
In the routes.js file, require this new render file using:

const services = require('../services/render');

Copy the function from the first route.get and add to the render.js as follows:

exports.homeRoutes= function(req, res) {//this listens for a get request for "/" the homepage

This assigns it to the variable, homeRoutes and exports that variable.

Now add this exported variable to the first route.get as follows:

route.get('/', services.homeRoutes);

Since services is required to run this file, the above would simply go into the file indicated and pick out homeRoutes
We can now do the same for the remaining routes
In render.js

exports.addDrug =  function(req, res) {//this listens for a get request for "/add-drug" from any hyperlink
  res.render('add_drug'); //tells server to respond with add_drug.ejs (.ejs is optional)
exports.updateDrug =  function(req, res) {

In routes.js

route.get('/add-drug', services.addDrug)
route.get('/update-drug', services.updateDrug)

Check if everything still works.

Connecting a Database to the App

We’d be using Mongo DB here as our Database as this would be a simple application.
I’d also be using the Mongo Db Atlas service as I said previously, I’d like the app accessible from anywhere.
You can always create an account at https://mongodb.com and follow their guide to setup if you don’t have an account already https://www.mongodb.com/docs/atlas/getting-started/
Create a Project & Cluster for your App then copy the connection string (for node JS)

Open your .env file
Save the following in it (replace YOUR_CONNECTION_STRING with your actual connection string from Atlas):


Create a new file connect.js in the database folder under server and add the following code

const mongoose = require('mongoose');//mongoose is a JS library that works with MongoDB
const connectDb = async () =&gt; {//an async function to prevent blocking
        const conn = await mongoose.connect(process.env.MONGO_STR, {//connect using connection string
        	//outline features that would be used
            useNewUrlParser: true,
            useUnifiedTopology: true,
        //Display on console if connection is successful
        console.log(`Database successfully connected at ${conn.connection.host}`);
    }catch(err){//catch errors
        process.exit(1);//exit from the process on error
module.exports = connectDb; //exports the connection function for use anywhere

Require this file in server.js

const connectMongo = require('./server/database/connect');//requires connect.js file

Then call the file (so it executes the connection function) in the body of server.js

//connect to Database

If all is well up to this point, after Nodemon restarts your server, you should be able to see the following in your terminal:

Database successfully connected at clusterx-xxxxx-00-01.xxxxx.mongodb.net

Creating our Model (the M in MVC)

in the model folder under server, create a new file, model.js
In this file we would write a schema for database entries into the Mongo Database.

Paste the code below in model.js

const mongoose = require('mongoose');
let schema = new mongoose.Schema({
    name : {
        type : String,// name would be a string
        required: true,// name is a required property
        unique: true // the value of name must be unique
    card : {
        type: Number, // card would be a number
        required: true
    pack : {
        type: Number,
        required: true
    perDay : {
        type: Number,
        required: true,
        unique: true
const DrugDB = mongoose.model('all-drugs', schema);
module.exports = DrugDB;

Creating our Controllers

In the controller folder under server, create a new file: controller.js
This file would contain the database operations, Create, Read, Update and Delete on our Database.

Add this code to the controller.js file

let Drugdb = require('../model/model');
// creates and saves a new user
exports.create = (req,res)=&gt;{
// can either retrieve all users from the database or retrieve a single user
exports.find = (req,res)=&gt;{
// edits a user selected using their user ID
exports.update = (req,res)=&gt;{
// deletes a user using their user ID
exports.delete = (req,res)=&gt;{

Before the exports statement at the end of routes.js, add the following:

// API for CRUD operations
route.post('/api/drugs', controller.create);
route.get('/api/drugs', controller.find);
route.put('/api/drugs/:id', controller.update);
route.delete('/api/drugs/:id', controller.delete);

Coding the CRUD operations

Let’s create the CRUD operations next


inside the curly braces on exports.create in the routes.js, add the following code which would ensure an empty request is not being sent and send the data to Mongo if there is information in it.

    // validate incoming request
    if(!req.body){// if content of request (form data) is empty
        res.status(400).send({ message : "Content cannot be emtpy!"});// respond with this
    //create new user
    const drug = new Drugdb({
        name : req.body.name,//take values from form and assign to schema
        card : req.body.card,
        pack: req.body.pack,
        perDay : req.body.perDay
    //save created user to database
        .save(drug)//use the save operation on drug
        .then(data =&gt; {
        .catch(err =&gt;{
            res.status(500).send({//catch error
                message : err.message || "There was an error while adding the drug"


Here there would be two operations, one to pick a drug using it’s ID and another to return all drugs in the database
Add the following code in your exports.find method

    if(req.query.id){//if we are searching for user using their ID
        const id = req.query.id;
            .then(data =&gt;{
                    res.status(404).send({ message : "Can't find drug with id: "+ id})
            .catch(err =&gt;{
                res.status(500).send({ message: "Error retrieving drug with id: " + id})
            .then(drug =&gt; {
            .catch(err =&gt; {
                res.status(500).send({ message : err.message || "An error occurred while retriving user information" })

You can test bot methods with POSTMAN to ensure they are working properly before proceeding.
Please ensure your database was connected to successfully.


        return res
            .send({ message : "Cannot update an empty drug"})
    const id = req.params.id;
   	const drugName = req.params.name;
    Drugdb.findByIdAndUpdate(id, req.body, { useFindAndModify: false})
        .then(data =&gt; {
                res.status(404).send({ message : `Drug with id: ${id} cannot be updated`})
                //message : `${drugName} was updated successfully!`
        .catch(err =&gt;{
            res.status(500).send({ message : "Error in updating drug information"})


    const id = req.params.id;
        .then(data =&gt; {
                res.status(404).send({ message : `Cannot Delete drug with id: ${id}. Pls check id`})
                    message : `${id} was deleted successfully!`
        .catch(err =&gt;{
                message: "Could not delete Drug with id=" + id

Now all CRUD operations are working, we can return to making our pages dynamic.

Configuring CRUD operations in the Front End

First of all, we would make the home page display objects from the database when loaded.
Open the render.js file under services
We’d use the Axios HTTP client installed earlier to query our API after which the data received would be parsed and displayed on the front end
require axios in the render.js using:

const axios = require('axios');

In your .env file, add a BASE_URI value


Note this would be changed when the site is live

change your exports.homeRoutes in render.js to the following:

exports.homeRoutes= function(req, res) {
    // Make a get request to /api/users
    axios.get(`${process.env.BASE_URI}:${process.env.PORT}/api/drugs`)//get request to pull drugs
            res.render('index', { drugs : response.data });// response from API request stored as drugs
        .catch(err =&gt;{

We would now create a loop in the index.ejs to replace the placeholders with actual dynamic data.
Edit your index.ejs as follows:


In the main.js file add the following:

$("#add_drug").submit(function(event){//on a submit event on the element with id add_drug
    alert($("#name").val() + " sent successfully!");//alert this in the browser

Updating the update form
The update form would need to be modified so when it’s called, existing values are already filled in.
Open the render.js and edit exports.updateDrug as follows:

exports.updateDrug =  function(req, res) {
    axios.get(`${BASE_URI}:${PORT}/api/drugs`, { params : { id : req.query.id }})//request a drug from the database using the id
            res.render("update_drug", { drug : response.data})//add drug data when rendering update form
        .catch(err =&gt;{

Open the update_user.ejs file and here, the form values would be updated with EJS variables so whenever the form is loaded, it shows the information for the current drug.


Since we didn’t add an action to the update form, we would use JQuery to specify a custom one in our main.js as below:

$("#update_drug").submit(function(event){// on clicking submit
event.preventDefault();//prevent default submit behaviour
//var unindexed_array = $("#update_drug");
var unindexed_array = $(this).serializeArray();//grab data from form
var data = {}
$.map(unindexed_array, function(n, i){//assign keys and values from form data
data[n['name']] = n['value']
var request = {//use a put API request to use data from above to replace what's on database
"url" : `http://${url}/api/drugs/${data.id}`,
"method" : "PUT",
"data" : data
alert(data.name + " Updated Successfully!");
window.location.href = "/";//redirects to index after alert is closed

Creating, Reading & Updating are now working perfectly in the front end.

What’s left is making the delete buttons work and also styling our app (it looks horrible I know!)

Making the Delete Work

We would also do this with JQuery by calling the delete operation on the API we built

in the main.js, add the following:

if(window.location.pathname == "/"){//since items are listed on homepage
$ondelete = $("table tbody td a.delete"); //select the anchor with class delete
$ondelete.click(function(){//add click event listener
let id = $(this).attr("data-id") // pick the value from the data-id
let request = {//save API request in variable
"url" : `http://${url}/api/drugs/${id}`,
"method" : "DELETE"
if(confirm("Do you really want to delete this drug?")){// bring out confirm box
$.ajax(request).done(function(response){// if confirmed, send API request
alert("Drug deleted Successfully!");//show an alert that it's done
location.reload();//reload the page

This completes the operations


The Basic operations are complete and can be expanded on in future

Meaning I can see the list of drugs, add to the list and edit and delete drugs if required.

Now I’d need to make it look a little attractive. with CSS.

I’d usually leave design for the designers but since this is being built by me, I’d just make a basic layout that would be usable on mobile and desktop.

First, I’m using this plugin: https://github.com/stazna01/jQuery-rt-Responsive-Tables to ensure the table remains responsive

Then using Bootstrap to add the remaining styles for the form

The current repo can be accessed at:


Free alternatives to Heroku for hosting your open source projects

Post A Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.