L
O
A
D
I
N
G

Ahmet Zeybek

Full Stack Developer
95%
Backend
70%
Frontend
90%
Design

Menu

Build NodeJS & Express REST API with MongoDB and Swagger

In this tutorial we will create a Movie Catchphrase API that allows you to Create, Read, Update and Delete Catchphrases, or in short perform CRUD operations.

We are going to use Node.js and Express with Mongoose in order to interact with the MongoDB instance. We will use Swagger to document the API we created.

MongoDB Setup


For this project I assume you already have set-up a MongoDB cluster (or a local MongoDB installation) and have the connection URI. If not you can refer to these links for a installation guide: MongoDB cluster or MongoDB local

Project Setup


First thing we need to do is set up the project by initializing with npm and installing the packages we are going to use. Run the following commands to setup the project:

npm init -y
npm install --save express mongoose
npm install --save-dev dotenv nodemon

dotenv will allow us to pull in environment variables from a .env file. Create a .env file in the root of the project and add the following:

.env
MONGO_URI=Your_MongoDB_URI_comes_here

Next let's create a .gitignore file in the root of the project and add the following:

.gitignore
.env
node_modules

Change the package.json scripts with the following:

package.json
"scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
},

Note: Nodemon allows you to keep the application running while making changes.

Start Building the API


Let's create a server.js file in the root of the project. This will contain a basic server setup with a basic route. Add the following to the file:

server.js
const express = require('express')

const app = express()

app.use(express.json())

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(process.env.PORT || 5000, () => console.log('Up and running 馃殌'))

In order to start the application, run the following command:

npm run dev

Navigate to localhost:5000 in the browser to view the application.

Database Configuration & Connection


Always keep all the configurations for the app in a separate folder. Let鈥檚 create a new folder config in the root folder of our application for keeping all the configurations.

Create a new file db.js inside the config folder with the following contents:

./config/db.js
const mongoose = require('mongoose')
require('dotenv').config()

const connectDB = async () => {
  try {
    const conn = await mongoose.connect(process.env.MONGO_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      useFindAndModify: false,
    })

    console.log(`MongoDB Connected: ${conn.connection.host}`)
  } catch (err) {
    console.error(err)
    process.exit(1)
  }
}

module.exports = connectDB

We are going to import the above database configuration in server.js and call the connectDB function to connect to our MongoDB database. Update the server.js accordingly:

server.js
const express = require('express')
const connectDb = require('./config/db')

const app = express()
connectDb()

app.use(express.json())

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(process.env.PORT || 5000, () => console.log('Up and running 馃殌'))

Creating the Catchphrase Model


Let鈥檚 create a new folder models in the root folder of our application for keeping all the models.

Create a new file catchphrase.js inside the models folder with the following contents:

models/catchphrase.js
const express = require('express')
const router = express.Router()
const Catchphrase = require('../models/catchphrase')

router.get('/', async (req, res) => {
  try {
    const catchphrases = await Catchphrase.find({})
    res.json(catchphrases)
  } catch (err) {
    res.status(500).json({ message: err.message })
  }
})

router.get('/:id', getCatchphrase, (req, res) => {
  res.json(res.catchphrase)
})

router.post('/', async (req, res) => {
  const catchphrase = new Catchphrase({
    movieName: req.body.movieName,
    catchphrase: req.body.catchphrase,
    movieContext: req.body.movieContext,
  })

  try {
    const newCatchphrase = await catchphrase.save()
    res.status(201).json(newCatchphrase)
  } catch (err) {
    res.status(400).json({ message: err.message })
  }
})

router.patch('/:id', getCatchphrase, async (req, res) => {
  if (req.body.movieName != null) {
    res.catchphrase.movieName = req.body.movieName
  }
  if (req.body.catchphrase != null) {
    res.catchphrase.catchphrase = req.body.catchphrase
  }
  if (req.body.movieContext != null) {
    res.catchphrase.movieContext = req.body.movieContext
  }

  try {
    const updatedCatchphrase = await res.catchphrase.save()
    res.json(updatedCatchphrase)
  } catch (err) {
    rs.status(400).json({ message: err.message })
  }
})

router.delete('/:id', getCatchphrase, async (req, res) => {
  try {
    await res.catchphrase.remove()
    res.json({ message: 'Deleted Catchphrase' })
  } catch (err) {
    res.status(500).json({ message: err.message })
  }
})

async function getCatchphrase(req, res, next) {
  let catchphrase
  try {
    catchphrase = await Catchphrase.findById(req.params.id)
    if (catchphrase == null) {
      return res.status(404).json({ message: 'Cannot find catchphrase' })
    }
  } catch (err) {
    return res.status(500).json({ message: err.message })
  }

  res.catchphrase = catchphrase
  next()
}

module.exports = router

Create a new file index.js inside the routes folder with the following contents:

routes/index.js
const catchphrases = require('./catchphrases')

module.exports = {
  catchphrases,
}

In this file we will import all of the routes we create. This will allow us to import this file in our server.js to define our routes.

Modify the server.js file as follows:

server.js
const express = require('express')
const connectDb = require('./config/db')
const { catchphrases } = require('./routes/index')

const app = express()
connectDb()

app.use(express.json())

app.use('/catchphrases', catchphrases)

app.listen(process.env.PORT || 5000, () => console.log('Up and running 馃殌'))

After running the application you should be able to navigate to the following route localhost:5000/catchphrases to see all the catchphrases in your database.

Adding Swagger Documentation


Swagger allows us to auto document our API. Let's start by installing the following packages:

npm install --save swagger-ui-express [email protected]

Next change the server.js file accordingly:

server.js
const express = require('express')
const connectDb = require('./config/db')
const { catchphrases } = require('./routes/index')
const swaggerJsDoc = require('swagger-jsdoc')
const swaggerUi = require('swagger-ui-express')

const app = express()
connectDb()

app.use(express.json())

const swaggerOptions = {
  swaggerDefinition: {
    info: {
      title: 'Catchphrases REST API',
      description:
        'A REST API built with Express and MongoDB. This API provides movie catchphrases and the context of the catchphrase in the movie.',
    },
  },
  apis: ['./routes/catchphrases.js'],
}

app.use('/catchphrases', catchphrases)

const swaggerDocs = swaggerJsDoc(swaggerOptions)
app.use('/', swaggerUi.serve, swaggerUi.setup(swaggerDocs))

app.listen(process.env.PORT || 5000, () => console.log('Up and running 馃殌'))

Next we need to describe our routes. Change the catchphrases.js file located in the routes folder accordingly:

routes/catchphrases.js
const express = require('express')
const router = express.Router()
const Catchphrase = require('../models/catchphrase')

/**
 * @swagger
 * /catchphrases:
 *   get:
 *     description: All catchphrases
 *     responses:
 *       200:
 *         description: Returns all the catachphrases
 */
router.get('/', async (req, res) => {
  try {
    const catchphrases = await Catchphrase.find({})
    res.json(catchphrases)
  } catch (err) {
    res.status(500).json({ message: err.message })
  }
})

/**
 * @swagger
 * /catchphrases/{id}:
 *   get:
 *     parameters:
 *      - in: path
 *        name: id
 *        required: true
 *        type: string
 *        description: The catchphrase ID.
 *     description: Get a catchphrase by id
 *     responses:
 *       200:
 *         description: Returns the requested catachphrase
 */
router.get('/:id', getCatchphrase, (req, res) => {
  res.json(res.catchphrase)
})

/**
 * @swagger
 * /catchphrases:
 *   post:
 *     parameters:
 *      - in: body
 *        name: catchphrase
 *        description: New catchphrase
 *        schema:
 *          type: object
 *          properties:
 *            movieName:
 *              type: string
 *            catchphrase:
 *              type: string
 *            movieContext:
 *              type: string
 *     responses:
 *       201:
 *         description: Created
 */
router.post('/', async (req, res) => {
  const catchphrase = new Catchphrase({
    movieName: req.body.movieName,
    catchphrase: req.body.catchphrase,
    movieContext: req.body.movieContext,
  })

  try {
    const newCatchphrase = await catchphrase.save()
    res.status(201).json(newCatchphrase)
  } catch (err) {
    res.status(400).json({ message: err.message })
  }
})

/**
 * @swagger
 * /catchphrases/{id}:
 *   patch:
 *     parameters:
 *      - in: path
 *        name: id
 *        required: true
 *        type: string
 *        description: The catchphrase ID.
 *      - in: body
 *        name: catchphrase
 *        description: Update catchphrase
 *        schema:
 *          type: object
 *          properties:
 *            movieName:
 *              type: string
 *            catchphrase:
 *              type: string
 *            movieContext:
 *              type: string
 *     responses:
 *       201:
 *         description: Created
 */
router.patch('/:id', getCatchphrase, async (req, res) => {
  if (req.body.movieName != null) {
    res.catchphrase.movieName = req.body.movieName
  }
  if (req.body.catchphrase != null) {
    res.catchphrase.catchphrase = req.body.catchphrase
  }
  if (req.body.movieContext != null) {
    res.catchphrase.movieContext = req.body.movieContext
  }

  try {
    const updatedCatchphrase = await res.catchphrase.save()
    res.json(updatedCatchphrase)
  } catch (err) {
    rs.status(400).json({ message: err.message })
  }
})

/**
 * @swagger
 * /catchphrases/{id}:
 *   delete:
 *     parameters:
 *      - in: path
 *        name: id
 *        required: true
 *        type: string
 *        description: The catchphrase ID.
 *     description: Delete a catchphrase by id
 *     responses:
 *       200:
 *         description: Returns the requested catachphrase
 */
router.delete('/:id', getCatchphrase, async (req, res) => {
  try {
    await res.catchphrase.remove()
    res.json({ message: 'Deleted Catchphrase' })
  } catch (err) {
    res.status(500).json({ message: err.message })
  }
})

async function getCatchphrase(req, res, next) {
  let catchphrase
  try {
    catchphrase = await Catchphrase.findById(req.params.id)
    if (catchphrase == null) {
      return res.status(404).json({ message: 'Cannot find catchphrase' })
    }
  } catch (err) {
    return res.status(500).json({ message: err.message })
  }

  res.catchphrase = catchphrase
  next()
}

module.exports = router

After running the application you should be able to navigate to the following route localhost:5000 to see the documentation generated by Swagger.

Hosting on Heroku


Heroku allows you to host your application free of charge, but with limited resources. To setup the project use the following webpage from the official Heroku documentation.

Note: You might need to add the following config vars in order to run the application:

MONGO_URI = <Your mongo uri>
NODE_ENV = production
NPM_CONFIG_PRODUCTION = false