How to unit test an express route that only returns an array from mongo DB.
as you can see below this express route is only returning value as json, so how can I possibly add a unit test.
router.get('/products', async (req, res) => {
try {
const db = await DB.connect('products');
const products = await db.collection('product').find().toArray();
res.json(products);
} catch (err) {
res.status(500).json({ message : err.message });
}
});
I will just say first of all that I am aware of almost all the questions asked on this site under this title.
The solutions there were pretty obvious and already done by me (with no success) or only helped for those specific cases and didn’t really work in my case unfortunately.
Now, for the problem:
I'm trying to create a route that will handle a get request and a post request which are sent to the route 'ellipses'.
These requests should receive and send data from and to an SQL database.
The problem is that for some reason the router is not ready to get these functions and gives me the error in the title:
Router.use () requires middleware function but got an undefined
Here is my code:
This code is from the file dat.js. its porpose is just to access the SQL database.
import { Sequelize } from "sequelize";
export const sequelize = new Sequelize('TheDataBaseName', 'TheUser', 'ThePassword', {
host: 'localhost',
dialect: 'mssql'
});
This code is from the file: controller.js. its porpose is to manage the requests and load the data.
import { sequelize } from "../dat";
export const sendEllipses = async (req, res, next) => {
try {
const ellipses = await getEllipsesFromJson();
return res.send(ellipses);
} catch (e) {
console.log(e);
}
};
export const addNewEllipse = async (req, res, next) => {
const { body: obj } = req;
let newEllipse;
try {
if (Object.keys(obj) !== null) {
logger.info(obj);
newEllipse = await sequelize.query(
`INSERT INTO [armageddon].[dbo].[ellipses] (${Object.keys(
obj
).toString()})
values (${Object.values(obj).toString()})`
);
} else {
console.log("the values are null or are empty");
}
return res.send(newEllipse);
} catch (error) {
console.log(error);
}
};
This code is on the file: routers.js.
its porpose is to define the route
import Router from "express";
import { sendEllipses } from "../ellipses.controller";
import { addNewEllipse } from "../ellipses.controller";
const router = Router();
export default router.route("/ellipses").get(sendEllipses).post(addNewEllipse);
This code is from the file: app.js. This is where everything actually happens.
import { router } from "../routers";
import express from "express";
app.use('/api', router);
app.listen(5000, () => {
console.log("server is runing on port 5000")
});
You need to export the router
const router = Router();
router.route("/ellipses").get(sendEllipses).post(addNewEllipse)
export default router
Now import the router:
import routes from "../router.js";
app.use('/api', routes);
Its also mentioned in the docs: https://expressjs.com/de/guide/routing.html#express-router
I am creating an express app using mongoose with the intention of connecting this to React for the frontend.
I have listed some CRUD operations for a customer controller below but there are a few things I do not like about this approach.
When using Customer.findById with a valid ObjectID that is not found, it returns null with a 200 response code. I want this to return 404 if no customer was found. I realise I could change the catch response to a 404, but I want to have some generic error handling incase the server goes down during the request or an invalid ObjectId was provided, which brings me to my next item.
If I provide an invalid ObjectId I want to provide some meaningful message, is 500 the right response code?
Error handling: Am I returning errors the correct way? currently errors return a string with the error message. Should I return JSON instead? e.g. res.status(500).json({error: error.message). I am planning on connecting this to react (which I am still learning) and I assume the UI will need to display these messages to the user?
findById is repeated in getCustomerById, updateCustomer, and deleteCustomer. I feel this is bad practice and there must be a more streamlined approach?
I want to have one function that validates if the ObjectId is valid. I am aware that I can do this is the routes using router.params but I'm not sure if checking for a valid id should be in the routes file as it seems like something the controller should be handling? See routes example below from another project I did.
What are the best practices and suggested ways to improve my code, based on the above?
I have read the documentation from mongoose, mozilla, and stackoverflow Q&A but they don't seem to address these issues (at least I could not find it).
I am really after some guidance or validation that what I am doing is correct or wrong.
customer.controller.js
const Customer = require("../models/customer.model");
exports.getCustomers = async (req, res) => {
try {
const customers = await Customer.find();
res.status(200).json(customers);
} catch (error) {
res.status(500).send(error.message);
}
};
exports.getCustomerById = async (req, res) => {
try {
const customer = await Customer.findById(req.params.id);
res.status(200).json(customer);
} catch (error) {
res.status(500).send(error.message);
}
};
exports.addCustomer = async (req, res) => {
try {
const customer = new Customer(req.body);
await customer.save().then(res.status(201).json(customer));
} catch (error) {
res.status(500).send(error.message);
}
};
exports.updateCustomer = async (req, res) => {
try {
const customer = await Customer.findById(req.params.id);
Object.assign(customer, req.body);
customer.save();
res.status(200).json(customer);
} catch (error) {
res.status(500).send(error.message);
}
};
exports.deleteCustomer = async (req, res) => {
try {
const customer = await Customer.findById(req.params.id);
await customer.remove();
res.status(200).json(customer);
} catch (error) {
res.status(500).send(error.message);
}
};
Router.params example
This is a routes file (not related to my current app) and is provided as an example of how I have used router.params in the past.
const express = require("express");
const router = express.Router();
const mongoose = require("mongoose");
const Artist = require("../models/Artist");
const loginRequired = require("../middleware/loginRequired");
const {
getArtists,
addArtist,
getArtistById,
updateArtist,
deleteArtist,
} = require("../controllers/artistController");
router
.route("/")
.get(loginRequired, getArtists) // Get all artists
.post(loginRequired, addArtist); // Create a new artist
router
.route("/:id")
.get(loginRequired, getArtistById) // Get an artist by their id
.put(loginRequired, updateArtist) // Update an artist by their id
.delete(loginRequired, deleteArtist); // Delete an artist by their id
router.param("id", async (req, res, next, id) => {
// Check if the id is a valid Object Id
if (mongoose.isValidObjectId(id)) {
// Check to see if artist with valid id exists
const artist = await Artist.findOne({ _id: id });
if (!artist) res.status(400).json({ errors: "Artist not found" });
res.locals.artist = artist;
res.locals.artistId = id;
next();
} else {
res.status(400).json({ errors: "not a valid object Id" });
}
});
module.exports = router;
i personly like to make error handeling more global so i would write something like
constPrettyError = require('pretty-error')
const pe = new PrettyError()
const errorHandler = (err, req, res, next) => {
if (process.env.NODE_ENV !== 'test') {
console.log(pe.render(err))
}
return res
.status(err.status || 500)
.json({ error: { message: err.message || 'oops something went wrong' } })
}
module.exports = errorHandler
as a handler
the in your index / server file
app.use(errorHandler)
then in your handlers just
} catch (err) {
next(err);
}
as an example
if (!artist) next({ message: "Artist not found" ,status:404 });
also, note that you can customize this error handler to switch case (or object) a custom error per status as well if you want
const errorHandler = (err, req, res, next) => {
if (process.env.NODE_ENV !== 'test') {
console.log(pe.render(err))
}
const messagePerStatus = {
404: 'not found',
401: 'no authorization'
}
const message = messagePerStatus[err.status]
return res
.status(err.status || 500)
.json({
error: { message: message || err.message || 'oops something went wrong' }
})
}
then just
if (!artist) next({status:404 });
I also agree with answer by Asaf Strilitz but still need to show what i do in my projects
Create a custom error class
AppError.js
class AppError extends Error {
constructor(statusCode, message) {
super();
// super(message);
this.statusCode = statusCode || 500 ;
this.message = message || "Error Something went wrong";
}
}
module.exports = AppError;
Create an error handling middleware
errors.js
const AppError = require("../helpers/appError");
const errors = (err, req, res, next) => {
// console.log(err);
let error = { ...err };
error.statusCode = error.statusCode;
error.message = error.message;
res.status(error.statusCode).json({
statusCode: err.statusCode,
message: err.message,
});
};
exports.errors = errors;
Create a middleware to validate object id
validateObjectId.js
const mongoose = require("mongoose");
const AppError = require("appError");
module.exports = function (req, res, next) {
const { _id } = req.params;
if (_id && !mongoose.Types.ObjectId.isValid(_id)) {
throw new AppError(422, "Invalid ID field in params");
}
next();
};
In your app.js
const { errors } = require("errors");
// At the end of all middlewares
// Error Handler Middleware
app.use(errors);
In your routes file
const express = require("express");
const router = express.Router();
const mongoose = require("mongoose");
const Artist = require("../models/Artist");
const loginRequired = require("../middleware/loginRequired");
const validateId = require("validateObjectId");
const {
getArtists,
addArtist,
getArtistById,
updateArtist,
deleteArtist,
} = require("../controllers/artistController");
// Your routes
router
.route("/:id")
.get(validateId, loginRequired, getArtistById) // Get an artist by their id
.put(validateId, loginRequired, updateArtist) // Update an artist by their id
.delete(validateId, loginRequired, deleteArtist); // Delete an artist by their id
module.exports = router;
Now regarding findById method being repeated i dont see anything bad in that as it is specific to database call still you can introduce a staic method on model itself or create a single method on cntroller but still need to check if it returns the found object or not and handle the error on that.
I am creating an endpoint that serves a file generated dynamically. I have written a controller which generates the file after certain operation based on request param fileId.
I am throwing some errors if anything goes wrong while file generation or it is an invalid request. I have used Promise.reject() for throwing error and on successful file generation returning the response {fileName, filePath} as Promise from the controller.
import express from 'express'
import downloadFile from '../controller/file.controller'
const router = express.Router()
router.post('/file/download/:fileId', (req, res) => {
downloadFile(req.fileId).then((fileResp) => {
res.download(fileResp.filePath, fileResp.fileName, function (error) {
if (error) {
console.log('Downloading error')
} else {
console.log('Downloading success')
}
})
}).catch((error) => {
res.status(error.status).json({message: error.message})
})
})
I have observed that file is being served on requesting endpoint but it will be empty of size zero bytes.
I have tried the same thing without Promise which works well. In this approach, I have changed my errors from Promise.reject() to throw error and response from Promise to an object
import express from 'express'
import downloadFile from '../controller/file.controller'
const router = express.Router()
router.post('/file/download/:fileId', (req, res) => {
const fileResp = downloadFile(req.fileId)
res.download(fileResp.filePath, fileResp.fileName, function (error) {
if (error) {
console.log('Downloading error')
} else {
console.log('Downloading success')
}
})
})
I am unable to find the issue in the 1st approach. Is it Promise which is causing the issue or I am doing something wrong?
I am trying to develop my first simple app using Koa, which just recieves some data and puts it in a Mongo DB. However, I found it difficult even to connect to the database, as the response I get is {"error": "this.db_client.connect is not a function"}. Here is the app code:
import Koa from "koa";
import bodyParser from "koa-bodyparser";
import {DBHandler} from "./db"
import {error} from "./middlewares/error";
const app = new Koa();
app.use(bodyParser());
app.use(error);
app.use(async ctx => {
const db = new DBHandler();
db.writeEntity(ctx.request.body);
});
app.listen(3000);
The DBHandler:
export class DBHandler {
constructor() {
this.db_url = "mongodb://localhost:27017";
this.db_client = new MongoClient(this.db_url, {useNewUrlParser: true, useUnifiedTopology: true});
}
writeEntity = (entity) => {
console.log(this.db_client);
this.db_client.connect((err, client) => {
if (err) throw new Error("Connection Error");
const db = client.db("database");
const collection = db.collection("users");
collection.insertOne(entity, (err, res) => {
if (err) throw new Error("Insertion Error");
console.log(res.ops);
client.close;
});
});
};
}
By the way, the console.log(this.db_client) prints Promise { <pending> }, which means, my MongoClient object is a promise!
Any ideas, what is happening and how to make it work?
Since you confirmed via the comments that you are importing the MogoClient like this:
import MongoClient from "mongodb";
I can confirm that is where the problem is, MongoClient is not a direct export of the mongodb module, instead, it's a sub export. You are supposed to import it like this:
import mongodb from "mongodb";
const MongoClient = mongodb.MongoClient;
// Or using require
const MongoClient = require("mongodb").MongoClient;
This should fix the problem.
You can read more on connecting to MongoDB with the MongoClient class here.