Build a CRUD API Using NodeJS, Express, and Mongoose

Build a CRUD API Using NodeJS, Express, and Mongoose

Introduction

CRUD stands for Create(Insert data to database), Read (Retrieve data from database), Update (Edit data from database), and Delete (delete data from database)

It is a popular design through which Web APIs interact with databases.

This tutorial shows you how to:

  • Set up an express server
  • Set up routes with express
  • Connect to MongoDB using Mongoose
  • Write mongoose models
  • Use Mongoose to make database queries

Prerequisites

  • At least node 8.10 and npm >= 5.6
  • Working knowledge of nodeJS
  • JavaScript concepts like arrow functions, object destructuring, and async/await.

I am using Ubuntu 18.04 and a VS Code editor

Create a new folder

Open your terminal and create a new folder and move into it.

mkdir crud-api-nodejs && cd crud-api-nodejs

Initialize npm. You can change the options or you can accept the default options.

npm init -y

Create an Express Server

Install Express and Mongoose

npm i express mongoose

Create server.js and open VS Code

touch server.js
code .

In ./server.js, add the following code

// Require express
const express = require('express')
// Initialize express
const app = express()

// parse json objects
app.use(express.json()) 

// parse url encoded objects- data sent through the url
app.use(urlencoded({ extended: true})) 

// create a server
const PORT = 8080
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT  }`)
}

In the above code, we have,

  • Added express to our app
  • Initialized express
  • Added middleware to parse JSON and URL encoded data.
  • Started a server

Connect to database

In ./db.js

const mongoose = require('mongoose')
const dbURI = 'mongodb://localhost:27017/bookDb'
mongoose.connect(dbURI, {useNewUrlParser:true})
// connect to database
const db = mongoose.connection
// if error
db.on("error", (err) => {
  console.error(`err: ${err}`)
})// if connected
db.on('connected', (err, res) => {
  console.log('Connected to database')
})

Import ./db.js to ./server.js

   const express= require('express')const app = express()
   const db = require('./db')
   ...

Create Book Schema Model in ./bookSchema.js

const mongoose = require('mongoose')const bookSchema = 
mongoose.Schema({
  title: {
  type: String,
  required: true
  },
  author: {
    type: String,
    required: true
  },
  publisher: {
    type: String,
    required: true
  },
  read: {
    type: Boolean,
    required: true
  }
})
// Export model
module.exports = mongoose.model('Book', 
  bookSchema)

Create

Here is what happens

  • A client will send book info to our server through the request body.
  • Check whether the data was actually sent, if not, send a bad request response
  • Create a new record in the database
  • If successful, send a 201 created response back

Let's first create a helper function for creating a database record.

In ./dbHelpers.js add the following:

const Book = require('./bookSchema')
   exports.create = async (data) => {
    try{
    const newBook = new Book(data)
    const savedBook = newBook.save()
    if(!savedBook) throw new Error('Book could not be saved')
    return {error: null}
    } catch (error) {
      return {error: error.message}
    }
}

Remember to import the helper function in ./server.js

    const { create } = require('./dbHelpers')

Now in ./server.js, add the following,

app.post('/create', async (req, res) ⇒ {
  //check if req.body is empty
  if (!Object.keys(req.body).length) {
    res.status(400).json({
    message: 'Request body cannot be empty'
  })
  }
  const {title, author, publisher, read} = (req.body)
  // create a record in db
  const book = await create({title, author, publisher, read})
  if (book.error) {
    res.status(500).json({
      message: book.error
    })
  }
  res.status(201).json({
    message: ‘New book record created’
  })
})

READ

Read all

To read all book records in the database, create a query that matches all the documents.

In ./dbHelpers.js, add the following

exports.readAll = async () => {
  try{
    const books = await Book.find({})
    if (!books) throw new Error('Book not found')
    return {error: null, data: books}
  }catch(error) {
      return {error: error.message, data: null}
  }
}

Add route in ./server.js

. . .
const {create, readAll } = require('./dbHelpers')
. . .
app.get('/read-all', async (req, res) => {
  const books = await readAll()
  if (books.error) {
    res.status(500).json({
      message: error.message,
      books: books.data
    })
  }
  res.status(200).json({
      message: 'success',
      books: books.data
    }) 
})

Read One

To retrieve one record, use findById and pass the Id in the URL as a parameter.

Add a helper function in ./dbHelpers.js

exports.readOne = async (id) => {
  try{
    const book = await Book.findByIdAndUpdate(id)
    if(!book) throw new Error('Could not retrieve book')
    return {error: null, data:book}
   } catch (error) {
     return {error: error.message, data:null}
  }
}

Add route in ./server.js

. . .
const {create, readAll, readOne } = require('./dbHelpers')
. . .
app.get('/read-one/:bookID', async (req, res) ⇒ {
  const book = await readOne(req.params.bookID)
  if (book.error) {
    res.status(500).json({
      message: book.error,
      books: book.data
    })
  }
  res.status(200).json({
      message: 'success',
      book: book.data
    }) 
})

UPDATE

Pass in the id of the document you want to update via the URL and the data to update via the request body.

In ./dbHelpers.js

exports.update = async (id, data) ⇒ {
  try{
    const updatedBook = await Book.findByIdAndUpdate(id, data,{new: true})
    if(!updatedBook) throw new Error('Failed to update book')
    return {error: null, data: updatedBook}
   } catch (error) {
     return {error: error.message, data: null}
  }
}

In ./server.js

. . .
const {create, readAll, readOne, update } = require('./dbHelpers')
. . .
app.put('/update/:bookID', async (req, res) => {
   if (!Object.keys(req.body).length) {
       res.status(400).json({
       message: 'Request body cannot be empty',
       book: null
     })
  }

  const book = await update(req.params.bookID, req.body)
  if (book.error) {
    res.status(500).json({
      message: book.error,
      book: book.data
    })
  }
  res.status(200).json({
      message: 'success',
      book: book.data
    }) 
})

DELETE

Delete One

Pass the Id of the document to delete via the URL. In ./dbHelpers.js

exports.deleteOne = async (id) => {
  try{
    const isDeleted = await Book.findByIdAndDelete(id)
    if (!isDeleted) throw new Error('Failed to delete book')
    return { error: null}
  }catch (error) {
  return { error: error.message}
  }
}

In ./server.js

. . .
const {create, readAll, readOne, update, deleteOne } = require('./dbHelpers')
. . .
app.delete('/delete/:bookID', async (req, res) => {
  const isDeleted = await deleteOne(req.params.bookID)
  if (isDeleted.error) {
    res.status(500).json({
      message: isDeleted.error,
    })
  }
  res.status(200).json({
      message: 'Deleted Successfully'
    }) 
})

Delete All In ./dbHelpers.js

exports.deleteAll = async () => {
  try{
    const isDeleted = await Book.deleteMany({})
    if (!isDeleted) throw new Error('Failed to delete books')
    return {error: null}
  }catch (error) {
  return { error: error.message }
  }
}

In ./server.js

. . .
const {create, readAll, readOne, update, deleteOne, deleteAll } = require('./dbHelpers')
. . .
app.delete('/delete-all', async (req, res) ⇒ {
  const isDeleted = await deleteAll(req)
  if (isDeleted.error) {
    res.status(500).json({
      message: isDeleted.error,
    })
  }
  res.status(200).json({
      message: 'Deleted Successfully'
    }) 
})

And that's it a simple CRUD API using mongoose, express, and nodeJS.

I learned a lot when writing this post and I hope you learn something too.

If there is anything in this post that is not clear please let me know in the comments.

Thanks for reading and Happy coding :)