There is a lint error, that basically says that we cannot return Promises in places where a void is expected, the message is clear, but how to fix it without lying to the linter by using any or (as any), both functions [validateJWT, getUser] are async functions It looks pretty basic, but I do not know how to fix it in an easy way. thanks!
import { Router } from 'express';
import { getUser } from '../controllers/userController';
import { validateJWT } from '../middlewares/validateJWT';
const router = Router();
router.get('/user', validateJWT, getUser);
export default router;
const getUser = async (req: Request, res: Response, next:
NextFunction): Promise<any> => {
try {
const { id } = req.params;
if (!id) {
let response = formatErrorResponse({ error: true, statusCode: 400, errorMessage: 'Missing id in params' });
return res.status(400).json(response);
}
let user = await User.findById({_id: id});
let objRes = { error: false, statusCode: 200, message: 'User was found', data: user };
return res.status(200).json(objRes);
} catch (error) {
console.log('error in controller /user', error)
next(error);
}
}
export {
getUser
}
const validateJWT = async (req: Request, res: Response, next: NextFunction): Promise<any> => {
const token = req.header('x-token');
console.log(req)
if (!token) {
const err = formatErrorResponse({ error: true, statusCode: 401, errorMessage: 'Missing header x-token' });
return res.status(401).json(err);
}
try {
await verifyToken(token);
next();
} catch (error) {
const err = formatErrorResponse({error: true, statusCode: 401, errorMessage: 'Invalid token, athorization denied', errorContent: error});
return res.status(400).json(err);
}
}
You need to change your implementation of the function to work like this:
router.get('/user', (req, res) => {
validateJWT(req, res);
getUser(req, res);
});
Since express.Router instances expect the route as a string for the first parameter which you have as "/user". And a second parameter which is a callback.
Inside that callback you can call your functions.
Assuming you need validateJWT to finish running before getUser you could do something like this:
validateJWT(...).then((...) => getUser(...));
I suggest something like the below.
router.use(validateJWT)
router.get('/users/:id', (req, res, next) => {
getUser(req.params.id)
.then(res.json)
.catch(next);
});
In the docs, I could see an async middleware example http://expressjs.com/en/guide/writing-middleware.html#mw-fig.
I could not find an async handler example. I recall that back in the day, express didn't support async handler. Hence, I used .then inside the handler. You need to double-check if you can use them nowadays.
In the express docs is also an example like the below. It makes working with async handler more convenient in some regard. As you can wrap all your async handler with this wrapper.
const wrap = fn => (...args) => fn(...args).catch(args[2]);
router.get('/users/:id', wrap(getUser));
Related
I have been getting terribly confused with how to test my controller functions. I realize that I have to mock my dependencies, request, response, and the controller functions. Here's what I have so far:
OrdersController.js
const OrderService = require('../services/orderServices')
module.exports = class OrdersController {
static async apiGetOrders(req, res, next) {
try {
const orders = await OrderService.getOrders()
return res.status(200).json(orders)
} catch (error) {
return res.status(500).json({ error: 'Unable to get orders' }) // 500, Internal Service Error, generic
}
}
static async apiPostOrder(req, res, next) {
// All good, create an orderDocument
try {
const orderDocument = {
_id: null, // undefined at this point Mongo creates this _id for us
orderId: req.body.orderId,
cookies: req.body.cookies,
daySelected: req.body.daySelected,
timeSelected: req.body.timeSelected,
userInfo: req.body.userInfo,
createdAt: new Date(),
}
await OrderService.addOrder(orderDocument)
return res.status(201).send('success') // status OK, something was Created
} catch (error) {
return res.status(500).json({ error }) // 500, Internal Server Error
}
}
OrdersController.spec.js
import OrderService from '../services/orderServices'
import { mockOrder, mockOrders } from '../mocks/fixtures'
import OrdersController from '../controllers/ordersController'
jest.mock('../controllers/ordersController.js')
const mockRequest = () => {
return {}
}
const mockResponse = (mockOrders) => {
const res = {};
res.status = jest.fn().mockReturnValue(200);
res.json = jest.fn().mockReturnValue(mockOrders);
return res;
}
// #3 Test the OrdersControllers
// mock dependencies: req, res, and spyOn the controller functions
describe('Orders Controller', () => {
test('[Positive], should call OrderService.getOrders and receive status 200', async () => {
jest.spyOn(OrdersController, 'apiGetOrders')
const req = mockRequest()
const res = mockResponse(mockOrders)
await OrdersController.apiGetOrders(req, res)
expect(res.status).toHaveBeenCalledWith(200)
expect(res.json()).toEqual(mockOrders)
})
test('[Negative], error yields status 500', async () => {
jest.spyOn(OrdersController, 'apiGetOrders')
const req = mockRequest()
const res = mockResponse({status: 500, error: 'Unable to get orders'})
await OrdersController.apiGetOrders(req, res)
expect(res.status).toHaveBeenCalledWith(500)
expect(res.json()).toEqual(error)
})
})
I'm trying to test the happy path and the negative path on the get request. I followed this expample, https://codewithhugo.com/express-request-response-mocking/, and read all of the jest docs, https://jestjs.io/docs/mock-functions. The error that I receive is:
Questions:
Am I actually writing the tests correctly?
Am I also supposed to mock the OrderService?
Should I use Sinon or is Jest more than sufficient?
I am also new to Jest and am struggling with finding documentation that goes to enough detail to suggest to me what I'm doing wrong. But in your case, I think you might be spying on the wrong thing. The OrdersController is the subject of the test, so I don't believe that you should be mocking that. Rather you should spy on OrdersController's dependency, Orderservice and mock its methods.
Also, its not clear to me why you have next in
static async apiGetOrders(req, res, next)
You don't seem to use it in the body of the method anywhere, so hanging any testing off that value, probably won't work either.
If I deliberately try to fetch a wrong product ID using postman, the application crashes.
Reason
The error is NOT getting catched by my error-handler middleware.
Route
/* returns a single product */
router.get("/:id", async (req: Request, res: Response) => {
const product = await Product.findById(req.params.id);
if (!product) {
throw new NotFoundError();
}
res.send(product);
});
Not Found Error (custom)
import { CustomError } from "./custom-error";
export class NotFoundError extends CustomError {
statusCode = 404;
constructor() {
super("Page not found");
console.log("Not found error"); // <=== Not being consoled out
Object.setPrototypeOf(this, NotFoundError.prototype);
}
serialize() {
return [{ message: "Page not found" }];
}
}
Error handler:
export const errorHandler = (
err: Error,
req: Request,
res: Response,
next: NextFunction
) => {
if (err instanceof CustomError) {
console.log("Error while processing request"); // <=== Not being consoled out
return res.status(err.statusCode).send({ errors: err.serialize() });
}
// and if not
console.log(err);
res.status(400).send({
errors: [{ message: "Something went wrong" }],
});
};
I have no clue why the error is not being caught by the middleware in the app.ts file:
// routes
app.use("/api/product", productFetchRouter);
// error handler
app.use(errorHandler);
Repo
Try to declare your middleware before your routes:
// error handler
app.use(errorHandler);
// routes
app.use("/api/product", productFetchRouter);
app.use(userFetchRouter);
router.get("/:id", async (req: Request, res: Response, next: Next) => {
try{
const product = await Product.findById(req.params.id);
if (!product) {
throw new NotFoundError();
}
res.send(product);
}catch(e){
next(e);
}
});
router.use(errorHandler);
Note: I am not so good with Typescript but that how i do it normally catch the error and then pass it to the errorHandler Middleware by using next()
This question already has answers here:
Handling errors in express async middleware
(6 answers)
Closed 3 years ago.
I google for it and found some examples (like this).
I done everything like there, and all just fine!
But now every my router function contains try...catch block. Like that:
accounts = express.Router()
accounts.post('/following', async (req, res, next) => {
try {
***do some stuff***
if (smth_bad)
next(new ErrorHandler(413, 400, "Wrong data"));
} catch (e) {
next(e)
}
});
accounts.post('/followers', async (req, res, next) => {
try {
***do some stuff***
if (smth_bad)
next(new ErrorHandler(413, 400, "Wrong data"));
} catch (e) {
next(e)
}
});
accounts.post('/posts', async (req, res, next) => {
try {
***do some stuff***
if (smth_bad)
next(new ErrorHandler(413, 400, "Wrong data"));
} catch (e) {
next(e)
}
});
accounts.post('/pizza', async (req, res, next) => {
try {
***do some stuff***
if (smth_bad)
next(new ErrorHandler(413, 400, "Wrong data"));
} catch (e) {
next(e)
}
});
app.use('/api/v1/account', accounts);
app.use((err, req, res, next) => {
handleError(err, res);
});
I know, that i can use next() without try...catch, but I want to handle unexpected errors and tell about it to user. My handling looks like this:
class ErrorHandler extends Error {
constructor(statusCode, httpStatus, message) {
super();
this.statusCode = statusCode;
this.httpStatus = httpStatus;
this.message = message;
}
}
const handleError = (err, res) => {
if(err instanceof ErrorHandler){
const { statusCode, message, httpStatus } = err;
res.status(httpStatus).json({
status: "error",
statusCode,
message
});
} else {
console.error(err);
res.status(500).json({
status: "error",
statusCode: '510',
message: 'Server error',
});
}
};
Is there a way to simplify try...catch blocks in every router?
First off, try/catch will not catch errors asynchronously. What you're doing WILL work if all your code inside the try/catch is synchronous, but as you've already discovered, that's a really cumbersome way to approach the problem.
There are quite a few ways you could go about this, but I think maybe you should be asking a different question: "What does good modular code look like?" or "How do I implement a router inside my code?"
I would suggest you go looking for patterns on how to structure a router.
One possible approach is to write functions that you define outside your try/catch blocks (possibly in another module, for tidiness's sake) and use them inside the try/catch. Like this:
const getStuffFunction = function(req,callback){
//do stuff here
}
accounts.post('/pizza', async (req, res, next) => {
getStuffFunction(req,function(){
//do router stuff here after your business logic is done
})
Hope that helps.
im using nodejs 8. I've replaced promise structure code to use async and await.
I have an issue when I need to return an object but await sentence resolve undefined.
This is my controller method:
request.create = async (id, params) => {
try {
let request = await new Request(Object.assign(params, { property : id })).save()
if ('_id' in request) {
Property.findById(id).then( async (property) => {
property.requests.push(request._id)
await property.save()
let response = {
status: 200,
message: lang.__('general.success.created','Request')
}
return Promise.resolve(response)
})
}
}
catch (err) {
let response = {
status: 400,
message: lang.__('general.error.fatalError')
}
return Promise.reject(response)
}
}
In http request function:
exports.create = async (req, res) => {
try {
let response = await Request.create(req.params.id, req.body)
console.log(response)
res.send(response)
}
catch (err) {
res.status(err.status).send(err)
}
}
I tried returning Promise.resolve(response) and Promise.reject(response) with then and catch in the middleware function and is occurring the same.
What's wrong?
Thanks a lot, cheers
You don't necessarily need to interact with the promises at all inside an async function. Inside an async function, the regular throw syntax is the same as return Promise.reject() because an async function always returns a Promise. Another thing I noticed with your code is that you're rejecting promises inside a HTTP handler, which will definitely lead to unexpected behavior later on. You should instead handle all errors directly in the handler and act on them accordingly, instead of returning/throwing them.
Your code could be rewritten like so:
request.create = async (id, params) => {
let request = await new Request(Object.assign(params, { property : id })).save()
if ('_id' in request) {
let property = await Property.findById(id)
property.requests.push(request._id)
await property.save()
}
}
And your http handler:
exports.create = async (req, res) => {
try {
await Request.create(req.params.id, req.body)
res.send({
status: 200,
message: lang.__('general.success.created','Request')
})
} catch (err) {
switch (err.constructor) {
case DatabaseConnectionError: // Not connected to database
return res.sendStatus(500) // Internal server error
case UnauthorizedError:
return res.sendStatus(401) // Unauthorized
case default:
return res.status(400).send(err) // Generic error
}
}
}
Error classes:
class DatabaseConnectionError extends Error {}
class UnauthorizedError extends Error {}
Because you have that try/catch block inside your http handler method, anything that throws or rejects inside the Request.create method will be caught there. See https://repl.it/LtLo/3 for a more concise example of how errors thrown from async function or Promises doesn't need to be caught directly where they are first called from.
I'm using promises inside express middleware. I want to use the async/await methods.
app.get('/data1',async function(req,res) {
data = await getData1(); // This line throw an error,
res.send(data)
})
app.get('/data2',async function(req,res) {
data = await getData2(); // This line throw an error
res.send(data)
})
This makes the browser wait forever.
On the server I see
(node:251960) UnhandledPromiseRejectionWarning: Unhandled promise rejection
Now, to fix it for one middleware I'm doing:
app.get('/data1',async function (req,res){
return (async function(){
data = await getData1()
})().catch(() => {
res.send("You have an error")
}
})
app.get('/data2',async function (req,res){
return (async function(){
data = await getData2()
})().catch(() => {
res.send("You have an error")
}
})
I don't like this repetion. How can I set default error? I have tried for example:
app.use(function(error,req,res,next)){
res.send('You have an error')
}
But it didn't work.
In other words: How to set default function to be called when Express middlewares returning a rejected promise?
Now I found a way how to do it, I'm still keep the question open for more suggestions
app.get("/data1",
wrap_middleware(async (req, res) => {
data1=await getData1()
res.send(data1)
})
}
app.get("/data1",
wrap_middleware(async (req, res) => {
data2=await getData2()
})
}
function wrap_middleware(func) {
return async (req, res, next) => {
func(req, res, next).catch(err => {
console.log(err.message);
res.send("Error");
});
};
}
I don't understand the use of sending the same error for different function but I think the handling error code could be write in more readable way (just catch the error and do with them what you want the same way you catch errors in any route middleware):
function getData1(){
return new Promise( (resolve,reject) => {
setTimeout(() =>{
reject(new Error('error has occur!'));
},2000);
});
}
router.get('/data1', async (req,res, next) => {
try{
const data = await getData1();
res.send(data);
}
catch(ex){
res.send(ex.message);
// next(ex); => sending it to express to handle it
}
});
If you want a global error handling then its not any different from any code you want catch errors globally - you can set a function that take as param , the response object and the async code and create general catch for every async call comes from middleware (which has response object)