Build a quotes generator API with NodeJS,Express and HarperDB.

Build a quotes generator API with NodeJS,Express and HarperDB.

ยท

7 min read

This article is part of a series titled APIs with NodeJS and HarperDB , with focus on creating APIs with HarperDB.

In this article, you are going to learn how to build a quotes generator API using NodeJS and HarperDB.

I will assume you know what NodeJS is, and already have it installed on your machine, otherwise, if you don't know what NodeJS is, you should check out this article about NodeJS and how to install it on your machine.

With that being said, let's talk about harperDB.

What is HarperDB?

HarperDB is a SQL/NoSQL database, that is, you can store or access data using SQL queries or as JSON (NoSQL). if you don't have a HarperDB account yet, you can sign up and create a free HarperDB instance.

Also to learn more about harperDB, you can visit their get started page.

Now that you know what HarperDB is and have possibly created an account, let's dive into the project.

if you would love to see the finished project, you can find it on this Github repo.

Features for this API.

With this API you will be able to

  • add new quotes.
  • edit the quote.
  • delete the quote.
  • get quotes by category.
  • get quotes from the author.
  • get a random quote (suitable for displaying a QOTD [quote of the day]).

Routes for the API.

The API will include the following routes :

  • /quotes [method=get] route: To get all quotes.
  • /quotes/:id [method=put] route: To update a quote by id.
  • /quotes/:id [method=delete] route: To delete a quote by id.
  • /create [method=post] route: where we can add new quotes to the API.
  • /random [method=get] route: To get a random quote.
  • /author/:author [method=get] route: To get quotes from a specific author.
  • /category/:category [method=get] route: To get quotes from a specific category.

Tools for this project.

I will be using VS Code as the Code Editor and Postman for testing the API.

To start the project, open up the terminal and create a directory named quotes-generator-api by running the following command,

mkdir quotes-generator-api

and move into that directory

cd quotes-generator-api

initialize a package.json for the project.

npm init -y

We will be needing a couple of dependencies and devDependencies for this project.

if you don't know the difference between both of them, you should read this article about the difference between dependencies and devDependencies.

Let's install the necessary dependency modules we need for this project.

npm install express cors harpee dotenv morgan http-errors express-async-handler

those are the dependency modules needed, Let me explain what each module will be doing.

  • express: this module will be used to set up a server and routes that we need.
  • cors: this module will be used to make certain routes available so people on a different domain can use our API.
  • harpee: a modeling tool for harperDB (created by me) which will help simplify some processes for the usage of harperDB.
  • dotenv: will help us set up environment variables to use locally in our app.
  • morgan: to log information about each request to the console.
  • http-errors: to create and throw an error for unavailable routes.
  • express-async-handler: to catch errors when using async functions.

let's also install a devDependency module.

npm install nodemon --save-dev

what this module does is, it will restart our server whenever we make changes while working locally.

Now that we have the requirements for our project, let's create some file folders to stay organized,

config folder, models folder, routes folder, controllers folder,

the folder structure would be like this. screenshot of file folders in VS Code

In the models folder, let's create a quotes.model.js file to set up our model,

const {Schema, Model}=require('harpee');


const QuotesSchema=new Schema({
    name:'QuotesScehma',
    primaryKey:'id',
    fields:{
        author:String,
        category:String,
        content:String
    }});

const Quotes=new Model('quotes', QuotesSchema);

module.exports=Quotes;

In the config folder, let's create the following files,

  • db.config.js: this is where we can configure the database connection.
const {createConnection}= require('harpee');

const dbconfig={
    host:process.env.DB_HOST,
    username:process.env.DB_USER,
    password:process.env.DB_PASS
}
const connectDB=()=> createConnection(dbconfig);
module.exports={
    connectDB
}
  • db.init.js: this is where we initialize the model, so we can work with it.
// so we can use environment variables.
require('dotenv').config();

const { connectDB } = require("./db.config");
// connect to the database
connectDB();

const Quotes = require("../models/qoutes.model");


async function dbInit(){
// initialize Quotes model, the init() method creates the schema and table.

    await Quotes.init();
console.log('db initialized')
}
dbInit();
module.exports=dbInit;

Now let's create an entry file let's name it index.js with the following code.

// require dotenv module so we can use environment variables
require('dotenv').config();
const express=require('express');
const morgan = require('morgan');
const { connectDB } = require('./config/db.config');
const app=express();
const createError=require('http-errors');
// check if there's already a port on the environment, otherwise use port 3300;
const port=process.env.PORT ||3300;
// connect to the database
connectDB();

// set up middlewares

// accept JSON 
app.use(express.json())
// accept form
app.use(express.urlencoded({
    extended:false
}));

// log api info
app.use(morgan('dev'));

// set up routes
app.get('/',(req,res)=>{
    res.send('hello quotes api')

});


app.use((req, res, next)=>{
    return next(createError(404))
})
app.listen(port,()=>{
    console.log(`server listening on port ${port}`)
})

Let's update our package.json file with the following code.

"scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js",
    "db:init": "node ./config/db.init.js"
    }

Then we run the following commands,

npm run db:init
npm run start

The server should be up and running on port localhost:3300 and you should see this message.

Screenshot_20220425-090545_1.jpg Now under the controllers folder, let's create quotes.controller.js file, this is where we are going to write functions for each route that we are going to create.

const expressAsyncHandler = require("express-async-handler");
const Quotes = require("../models/qoutes.model");

// get all quotes
const getAllQuotes=expressAsyncHandler(async(req,res)=>{
    try{

        let {limit}=req.query;
        limit=parseInt(req.query) || 20;
        const quotes= await Quotes.find({getAttributes:['id','author','category','content'],limit});

        res.status(200).json({quotes});
    }
    catch(error){
res.status(500).json({
    error,message:'an error occurred '
})
    }
});

// get a single random quote
const getRandomQuotes=expressAsyncHandler(async(req,res)=>{
    try{


    const limit=1;

    // get the record count of quotes from the database
    const {record_count}=await Quotes.describeModel();

    // generate a random index to be used as offset
    const randomIndex=Math.floor((Math.random() * record_count) ) || 0;
    const offset=randomIndex;

        const quotes= await Quotes.find({getAttributes:['id','author','category','content'],limit,offset});
        // send only one quote
        res.status(200).json(quotes[0]);
    }
    catch(error){
res.status(500).json({
    error,message:'an error occurred '
})
    }
});

// create new quote
const createNewQuote=expressAsyncHandler(async(req,res)=>{
    try{
           const {author,content,category}=req.body;
           if(!req.body){
               return res.status(400).json({
                   message:'no quote to create,body is empty'
               })
           }
      const {inserted_hashes}=  await Quotes.create({author,content,category});
res.status(201).json({
    message:'successfully added new quote with id '+inserted_hashes[0]
});
    }
    catch(error){
        res.status(500).json({
            error,
            message:'an error occured, couldn\'t create quote'
        })
    }
});

// edit a quote
const editQuote=expressAsyncHandler(async(req,res)=>{
    try{

        const {quote_id}=req.params;
const quote=await Quotes.findOne({id:quote_id});
if(!quote) {
    return res.status(404).json({
        message:`quote with id '${quote_id}' was not found`
    })
}
const quoteToUpdate=req.body|| {};
quoteToUpdate['id']=quote_id;
await Quotes.update(quoteToUpdate);

res.status(200).json({
    message:'successfully updated quote with id '+quote_id
})
}
catch(error){
    res.status(500).json({
        error,message:'an error occurred, couldn\'t update quote '
    })
}
});

// delete a quote 
const deleteQuote=expressAsyncHandler(async(req,res)=>{
    try{

        const {quote_id}=req.params;
        if(!quote_id){
    return res.status(400).json({message:'please provide an id'});
}
const quote=await Quotes.findOne({id:quote_id});
if(!quote) {
    return res.status(404).josn({
        message:`quote with id '${quote_id}' was not found`
    })
}

await Quotes.findByIdAndRemove([quote_id]);

res.status(200).json({
    message:'successfully deleted quote with id '+quote_id
})

    }
    catch(error){
        res.status(500).json({
            message:'an error occured, couldn\'t delete quote'
        })
    }
})

// get quotes by author
const getQuotesByAuthor=expressAsyncHandler(async(req,res)=>{
    try{
const {author}=req.params;
        let {limit}=req.query;
        limit=parseInt(req.query) || 20;
        const quotes= await Quotes.find({getAttributes:['id','author','category','content'],limit,where:author ?`author='${author}'`:null});

        res.status(200).json({quotes});
    }
    catch(error){
res.status(500).json({
    error,message:'an error occurred '
})
    }
});

// get quotes by category
const getQuotesByCategory=expressAsyncHandler(async(req,res)=>{
    try{
const {category}=req.params;
        let {limit}=req.query;
        limit=parseInt(req.query) || 20;
        const quotes= await Quotes.find({getAttributes:['id','author','category','content'],limit,where:category ?`category='${category}'`:null});

        res.status(200).json({quotes});
    }
    catch(error){
res.status(500).json({
    error,message:'an error occurred '
})
    }
});



module.exports={
    getAllQuotes,
    createNewQuote,
    editQuote,
    deleteQuote,
    getQuotesByAuthor,
    getQuotesByCategory,
    getRandomQuotes
}

Now, under the routes folder let's create a quotes.route.js file, with the following code,

const express = require("express");
const { getAllQuotes, getQuotesByAuthor, getQuotesByCategory, createNewQuote, editQuote, deleteQuote, getRandomQuotes } = require("../controllers/quotes.controller");

const cors=require('cors');
const router=express.Router();


router.get('/',cors(),getAllQuotes);
router.get('/author/:author',cors(),getQuotesByAuthor);
router.get('/category/:category',cors(),getQuotesByCategory);
router.get('/random',cors(),getRandomQuotes);
router.post('/create',createNewQuote);
router.put('/:quote_id',editQuote);
router.delete('/:quote_id',deleteQuote);


module.exports=router;

Now, let's update the index.js file with the following code.

const quotesRouter=require('./routes/quotes.route');


app.use('/quotes',quotesRouter);

And that's it, our API is ready for use in frontend applications.

Note that in a real-world API, the quote adding route /create, the edit route, and the delete route would be protected (Authentication and Authorization), but that's beyond the scope of the article if you would want to learn how to implement such with this API, then check out this article.

Conclusion

In this article, I have demonstrated how you can create a quotes generator API with NodeJS, Express, and HarperDB.

If you find this article helpful, do give it a thumbs up. ๐Ÿ‘

ย