How to unit test an express route? - javascript

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 });
}
});

Related

AxiosError: Request failed with status code 404 shows when trying to get data from MongoDB database

I am trying to use the Get method from the code below. I can use the Post method to post new instances to the database but my Get method is not working. When I tried to use the Get method I encountered the "AxiosError: Request failed with status code 404" error.
This is my code that contains the Get and Post methods:
const express = require('express');
const mongoose = require('mongoose');
const { ObjectId } = require('mongodb');
const { connectToDb, getDb, URI } = require('./db');
const Root = require('../models/Root');
const port = process.env.PORT || 7000;
const URL = 'http://localhost:7000'
const axios = require('axios');
// init & middleware
const app = express();
const router = express.Router();
app.use(express.json());
mongoose.set('strictQuery', false);
mongoose.set('bufferCommands', false);
let db
connectToDb((err) => {
if (!err) {
app.listen(port, () => {
console.log(`Listening on port ${port}`);
});
}
});
mongoose.connect(URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
// POST
app.post('/roots', async (req, res) => {
const { root_id, node_id, name } = req.body;
if (!root_id || !node_id || !name) {
return res
.status(400).send({ error: 'Please provide all required fields' });
}
const root = new Root({ root_id, node_id, name });
try {
const savedRoot = await root.save();
res.send(root);
} catch (err) {
//console.error('Error saving root:', err);
res.status(400).send(err);
}
});
// GET
app.get('/roots/:root_id', async (req, res) => {
try {
const response = await axios.get(
`${URL}/roots?filter={"where":{"root_id":${req.params.root_id}}}`
);
res.status(200).json(response.data);
} catch (err) {
console.error('Error getting root:', err);
res.status(400).send(err);
// res.status(500).json({ error: 'Could not fetch the root' });
}
});
// DELETE
app.delete('/roots/:root_id', async (req, res) => {
try {
await axios.delete(`${URL}/roots/${req.params.root_id}`);
res.status(200).json({ message: 'Root deleted successfully' });
} catch (err) {
console.error('Error getting root:', err);
res.status(400).send(err);
// res.status(500).json({ error: 'Could not delete the root' });
}
// Call to a method to delete all children nodes of the tree in the Node tables
});
// PATCH
app.patch('/roots/:root_id', async (req, res) => {
try {
const response = await axios.patch(
`${URL}/roots/${req.params.root_id}`,
req.body
);
res.status(200).json(response.data);
} catch (err) {
res.status(500).json({ error: 'Could not update the root' });
}
});
I use this code to connect to the database:
// Use this file to connect to database - easy to switch between local and cloud for testing
const{MongoClient} = require('mongodb')
let dbConnection
// Connect to local database
let URI = 'mongodb://127.0.0.1:27017/PM_AI'
module.exports = {
connectToDb: (cb) => {
MongoClient.connect(URI)
// MongoClient.connect(cloudURI)
.then((client) => {
dbConnection = client.db()
return cb()
})
.catch(err => {
console.log(err)
return cb(err)
})
},
getDb: () => dbConnection,
URI
}
ERROR LOG for the error that I encounter:
{
"message": "Request failed with status code 404",
"name": "AxiosError",
"stack": "AxiosError: Request failed with status code 404\n at settle (D:\\CSDS_395_Project\\AI-PM\\node_modules\\axios\\dist\\node\\axios.cjs:1900:12)\n at IncomingMessage.handleStreamEnd (D:\\CSDS_395_Project\\AI-PM\\node_modules\\axios\\dist\\node\\axios.cjs:2944:11)\n at IncomingMessage.emit (node:events:525:35)\n at endReadableNT (node:internal/streams/readable:1359:12)\n at process.processTicksAndRejections (node:internal/process/task_queues:82:21)",
"config": {
"transitional": {
"silentJSONParsing": true,
"forcedJSONParsing": true,
"clarifyTimeoutError": false
},
"adapter": [
"xhr",
"http"
],
"transformRequest": [
null
],
"transformResponse": [
null
],
"timeout": 0,
"xsrfCookieName": "XSRF-TOKEN",
"xsrfHeaderName": "X-XSRF-TOKEN",
"maxContentLength": -1,
"maxBodyLength": -1,
"env": {},
"headers": {
"Accept": "application/json, text/plain, */*",
"User-Agent": "axios/1.3.3",
"Accept-Encoding": "gzip, compress, deflate, br"
},
"method": "get",
"url": "http://localhost:7000/roots?filter={\"where\":{\"root_id\":1}}"
},
"code": "ERR_BAD_REQUEST",
"status": 404
}
The URL that I use to test my method in Postman is http://localhost:7000/roots/1.
Please let me know what am I doing wrong with my code here.
Thank you very much!
In your expressjs server file, the url you are using in mongoose.connect() refers to the expressjs server itself instead of localhost mongodb instance
So in your server.js/app.js or whatever is your main expressjs server file,
const MONGO_URL = 'mongodb://127.0.0.1:27017/PM_AI'
I can also see that you are using both mongo client and mongoose which I don't understand why... You only need one of these libaries to connect to mongodb from your backend
Also your code is pretty messed up so I've made the following changes
No need to use mongoose strict query and other configurations, simply using mongoose.connect() in latest mongoose version is enough. As mongodb connection establishes, you can launch your server
In terminal, write npm install dotenv. It is a package that is used to access variables in .env file, without it your server won't work properly
I've removed mongo client as it is not needed, simply using mongoose is enough
I don't know why you are making axios requests to your own server. This axios thing is what is causing 404 error. You should use axios only when you need to make api calls from frontend, or make api calls from your backend to some other backend server. For your own server, you should always prefer using a controller function for every route otherwise you will get 404 error. By controller function, I mean instead of axios.get, you need to execute mongoModel.delete() instead of axios.delete() or return mongoModel.findById() instead of axios.get()
For mongodb connection, use MONGO_URL and for connecting your own server, use URL
So the final version of your code should look like:
const express = require('express');
const mongoose = require('mongoose');
const { ObjectId } = require('mongodb');
const Root = require('../models/Root');
const MONGO_URL = 'mongodb://127.0.0.1:27017/PM_AI'
const axios = require('axios');
// For environmental variables in .env file
const dotenv = require("dotenv")
dotenv.config()
// init & middleware
const app = express();
const router = express.Router();
app.use(express.json());
const port = process.env.PORT || 7000
const URL = `http://localhost:${port}`
mongoose.connect(MONGO_URL).then(() => {
console.log("Mongodb connected")
app.listen(port,() =>
{console.log("Server started") }
});
// POST
app.post('/roots', async (req, res) => {
const { root_id, node_id, name } = req.body;
if (!root_id || !node_id || !name) {
return res
.status(400).send({ error: 'Please provide all required fields' });
}
const root = new Root({ root_id, node_id, name });
try {
const savedRoot = await root.save();
res.send(root);
} catch (err) {
//console.error('Error saving root:', err);
res.status(400).send(err);
}
});
// GET
app.get('/roots/:root_id', async (req, res) => {
try {
const response = await axios.get(
`${URL}/roots?filter={"where":{"root_id":${req.params.root_id}}}`
);
res.status(200).json(response.data);
} catch (err) {
console.error('Error getting root:', err);
res.status(400).send(err);
// res.status(500).json({ error: 'Could not fetch the root' });
}
});
// DELETE
app.delete('/roots/:root_id', async (req, res) => {
try {
await axios.delete(`${URL}/roots/${req.params.root_id}`);
res.status(200).json({ message: 'Root deleted successfully' });
} catch (err) {
console.error('Error getting root:', err);
res.status(400).send(err);
// res.status(500).json({ error: 'Could not delete the root' });
}
// Call to a method to delete all children nodes of the tree in the Node tables
});
// PATCH
app.patch('/roots/:root_id', async (req, res) => {
try {
const response = await axios.patch(
`${URL}/roots/${req.params.root_id}`,
req.body
);
res.status(200).json(response.data);
} catch (err) {
res.status(500).json({ error: 'Could not update the root' });
}
});

CRUD operations using mongoose and express

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.

My variable is always undefined, function returning before awaiting the query

const express = require('express')
const usersJs = require('./endpoints/users')
var cors = require('cors')
const app = express()
app.use(cors())
app.use(express.json());
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
next();
});
app.post('/addUser', (req, res) => {
result = usersJs.doAddUser().then(result => console.log(result))
res.json(result)
})
This is my server which is doing the call of doAdduser() from another file as you see bellow...
let stuff_i_want
module.exports.doAddUser = () => {
return addUser(async function (result) {
stuff_i_want = result;
return stuff_i_want
});
}
addUser = async (callback) => {
const mysql = require('mysql')
const connection = mysql.createConnection({
host: "localhost",
port: "",
user: "",
password: "",
database: ""
})
connection.connect(async function (err) {
if (err) throw err;
connection.query("SELECT * FROM USERS", async function (err, result) {
if (err) throw err;
stuff_i_want = result
});
});
return callback(result)
}
but the problem that result is always undefined in when im trying to res.json() it to the client, or when im trying console.log(result), its happening before addUser() which will make it undefined, tried with delay, with async await, but still result undefined in client and server but in doAdduser i can see the result from the database
I wanna be honest with you, this code looks super messi. However i have an solution for you.
You try to get the result outside the promise chain, that dont work. I would use the modern way async / await
const express = require('express')
const { addUser } = require('./endpoints/users')
var cors = require('cors')
const app = express()
app.use(cors())
app.use(express.json());
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
next();
});
app.post('/addUser', async (req, res) => {
try {
let result = await addUser();
return res.status(200).json(result);
} catch (err) {
console.log(err);
return res.status(500).json({ message: "Something went wrong" })
}
})
Also wrap it with an try / catch
Next to your mysql connection:
const mysql = rquire("mysql");
module.exports.addUser = () => {
return new Promise((resolve, reject) => {
const connection = mysql.createConnection({
host: "localhost",
port: "",
user: "",
password: "",
database: ""
});
connection.connect(err => {
if (err) reject(err);
connection.query("SELECT * FROM USERS", (err, result) => {
if (err) return reject(err);
return resolve(result);
});
});
});
};
import it on the top not somehwere in the middle of the code this is just confusing.
export a named module called addUser witch is an function.
That function returns an promise.
Then reject on errors and resolve if you got an result from your DB.
The try / catch block will catch the error.
Also add an status code to your response. I am not sure here i might be wrong but no status code means your request was successful even if something went wrong on the serverside
Its also good to have an router folder where you add all your routes and import the route handlers in there that looks something like this then:
const { addUserHandler } = require("./user");
router.post("/addUser", addUserHandler)
So routes and the logic are seperated and it doesnt look messi

Can not make mongoose find().then() to work

Faced an issue with Mongoose.
import express from 'express';
import Countries from '../models/countries.mjs';
const router = new express.Router();
router.get('/countries-data', async (req, res) => {
try {
let countries =
await Countries.find({})
.select(
'-_id specimenDate dailyLabConfirmedCases changeInDailyCases')
.sort('specimenDate');
if (!countries) return res.status(500).send();
res.json(countries);
} catch (err) {
res.status(500).send();
}
});
This code works as expected but I decided to remove the async/await and use find().then() instead:
import express from 'express';
import Countries from '../models/countries.mjs';
const router = new express.Router();
router.get('/countries-data', (_, res) => {
Countries.find({})
.select('-_id specimenDate dailyLabConfirmedCases changeInDailyCases')
.sort('specimenDate')
.then((countries) => {
if (!countries) throw Error('no data');
res.json(countries);
})
.catch(res.status(500).send());
});
This one rise an exception while trying to send the json data:
UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I really don't know what I'm doing wrong. And why the catch isn't getting the promise exception? Any suggestion?
I think the problem arises from the response you sent in the catch block since the catch takes in a callback function which returns an error if there is any.
Try this:
import express from 'express';
import Countries from '../models/countries.mjs';
const router = new express.Router();
router.get('/countries-data', (_, res) => {
Countries.find({})
.select('-_id specimenDate dailyLabConfirmedCases changeInDailyCases')
.sort('specimenDate')
.then((countries) => {
if (!countries) throw Error('no data');
res.json(countries);
})
// refactor your code like this
.catch((err)=>{
res.status(500).send(err)
});
});

How to connect Node.js to existing Postgres? error: Unhandled promise rejection

I have an existing postgresql database with Rails, now I'm making a Node.js app which is using the same database. I already have users in my db and now I would like to list them all.
I successfully created an express app and then I did as follows:
✗ npm install --save sequelize pg pg-hstore
✗ sequelize init
index.js
const express = require('express');
const logger = require('morgan');
const bodyParser = require('body-parser');
const pg = require('pg');
var conString = 'postgres://localhost:5432/db_name';
var client = new pg.Client(conString);
const app = express();
client.connect(err => {
if (err) {
console.error('connection error', err.stack);
} else {
console.log('connected');
}
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.get('/', (req, res) => {
res.send(models.User.findAll);
});
const PORT = process.env.PORT || 5000;
app.listen(PORT);
In my config.json I have:
"development": {
"username": "my_username",
"password": null,
"database": "database_name",
"host": "127.0.0.1",
"dialect": "postgres"
}
I get this error: UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()
I'm probably missing a big step but I don't know what it is, I have never done this before.
Example Query
const query = {
text: 'CREATE TABLE IF NOT EXISTS coverages ('+
'vaccine VARCHAR(64),' +
'country VARCHAR(255),' +
'region VARCHAR(255),' +
'year VARCHAR(4),' +
'value VARCHAR(12),' +
'PRIMARY KEY(vaccine, country, region, year, value))'
};
client.query(query)
.then(function(res) {
console.log(res);
})
.catch(function(err) {
console.log('\nError executing query', err.stack);
});
Here are some example queries using async / await (which requires Node 8+, I believe, so make sure your version supports this):
var express = require('express');
var pg = require('pg');
var router = express.Router();
let conString = 'postgres://localhost:5432/db_name';
var postgrespool = new pg.Pool({
connectionString: conString
});
router.get('/checkdbconnection', function(req, res, next) {
(async () => {
// Here is the query!
// alter it to query a table in your db
// this example just confirms a connection
var { rows } = await postgrespool.query(`
SELECT
'Hello from Postgres' AS pg_val;`);
if (rows.length) {
return res.send(rows);
} else {
res.status(404);
return res.send('No response from database.');
}
})().catch(e =>
setImmediate(() => {
res.status(500);
console.log(e);
return res.send('Error: ' + e.message);
})
);
});
router.get('/checkdbconnection/:name', function(req, res, next) {
let param_name = req.params.name;
(async () => {
// this example demonstrates how to pass parameters to your query with $1, $2, etc.
// usually, the cast of "::text" won't be necessary after the "$1"
var { rows } = await postgrespool.query(`
SELECT
'Hello from Postgres' AS pg_val,
$1::text AS parameter;`, [param_name]);
if (rows.length) {
return res.send(rows);
} else {
res.status(404);
return res.send('No response from database.');
}
})().catch(e =>
setImmediate(() => {
res.status(500);
console.log(e);
return res.send('Error: ' + e.message);
})
);
});
module.exports = router;
If you visit http://localhost:5000/checkdbconnection , you'll get this response:
[
{
"pg_val": "Hello from Postgres"
}
]
And if you visit, say, http://localhost:5000/checkdbconnection/Al-josh , you'll get this:
[
{
"pg_val": "Hello from Postgres",
"parameter": "Al-josh"
}
]
Hopefully my comments in the code have made it clear how the queries work, so you can alter them to your purpose. If not, provide some more detail about your tables and I can amend this answer.
Note also that I am using pg.Pool here to connect to Postgres. This is totally secondary to your question, but the documentation is worth reading.

Categories

Resources