Generic Validator Middleware in express.js - node.js

So I wrote middleware in express (works just fine) for REST API endpoints like:
DELETE "/:id", PUT "/:id", GET "/:id". I connect via mongoose with MongoDB database. If no todo document by such id were found in database I throw error, terminate further actions.
Question: In code review it was said that I should use Generic middleware instead - what does it mean? Maybe any one can explain or post a link to some helpful article.
import { Request, Response, NextFunction } from 'express';
import Todo from '../models/Todo';
export const isExistMiddleWare = async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
const isExist = await Todo.findById(id);
if (!isExist) {
throw new Error('no todo with such id');
}
next();
} catch (err) {
res.status(404).json({ error: err.message });
}
};
I do have errorHandling middleWare already in my code (also works fine)
import type { ErrorRequestHandler } from 'express';
export const errorHandler: ErrorRequestHandler = (Error, req, res, next) => {
res.status(Error.status || 500).json({ error: Error.message || 'Something went wrong' });
};

Related

Im trying to update a user in MongoDB but it's giving me an error "You are not authenticated" why is that?

Im creating a video sharing app that allows users to sign in and update their profiles. i've created an error handler to identify errors in the backend and when im submitting my put request to update a user its going off.
here are my routes that im using
import express from 'express';
import { update} from '../controllers/user.js'
import { verifyToken } from '../verifyToken.js';
const router = express.Router();
router.put("/:id", verifyToken, update);
here is the controller for updating the user
import { createError } from "../error.js";
import User from '../models/User.js'
export const update = async (req, res, next) => {
if(req.params.id === req.user.id) {
try {
const updatedUser = await User.findByIdAndUpdate(req.params.id, {
$set: req.body
}, {new: true})
res.status(200).json(updatedUser)
} catch (error) {
next(error)
}
} else {
return next(createError(403, 'You can only update this account'));
}
}
here is the custom error handling im using which is in the index.js below and error.js
app.use((err, req, res, next) => {
const status = err.status || 500
const message = err.message || "something went wrong"
return res.status(status).json({
success: false,
status,
message
})
})
export const createError = (status, message) => {
const err = new Error()
err.status=status
err.message=message
return err
}
here is the middleware for the token
import jwt from 'jsonwebtoken'
import { createError } from './error.js'
export const verifyToken = (req, res, next) => {
const token = req.cookies.acess_token
if(!token) return next(createError(401, 'You are not authenticated'))
jwt.verify(token, process.env.JWT, (err, user)=>{
if(err) return next(createError(403, "Token invalid"))
req.user = user
next()
})
}

[ExpressJs]: Custom Error handler do not catch exceptions

I am creating a router with ExpressJs (using TypeScript) and thrown exceptions are not caught by my custom error handler, here is the code:
In index.ts file (which is the main):
import express, { Express } from 'express';
import cors from 'cors';
import { config } from '~/utils/config';
import { NotFoundRoute } from '~/middlewares/NotFound';
import { ExceptionHandler } from '~/middlewares/ExceptionHandler';
import { usersRouter } from '~/controllers/users';
const app: Express = express();
app.use(express.json());
app.use(cors());
app.use('/users', usersRouter);
app.all('*', NotFoundRoute);
app.use(ExceptionHandler);
app.listen(config.port, () => console.log("API is running on port " + config.port));
The custom exception handler:
import { NextFunction, Request, Response } from 'express';
export const ExceptionHandler = (err: any, req: Request, res: Response, next: NextFunction) => {
if (res.headersSent)
return next(err);
if (err.status && err.message) {
res.status(err.status).send({ error: err.message });
} else
return res.status(500).send({ message: "Internal Server Error" });
}
The router that is problematic (where InternalServerError is not caught by the above):
import { Request, Response, Router } from 'express';
import { User } from '~/schemas/User';
import { InternalServerError } from '~/errors/InternalServerError';
const usersRouter = Router();
usersRouter.get('/', async (req: Request, res: Response) => {
try {
const users = await User.find({});
res.status(200).send(users);
} catch (err) {
res.status(500).send({ message: "Failed to fetch users"});
}
})
usersRouter.post('/register', async (req: Request, res: Response) => {
try {
const { email, password } = req.body;
if (!email || !password)
res.status(403).send({ message: "Bad Request"});
const newUser = User.createUser({ email, password });
await newUser.save();
res.status(201).send(newUser);
} catch (err) {
throw new InternalServerError();
}
})
export { usersRouter };
All my exception are not caught which means that it is not exception-related.
In the index.ts file, the NotFoundRoute throws an exception that is caught, so I guess it works on the file's context. How it is not working ? I suppose the router has a thrown exception that would be caught but it is not.
To remind the context, I am trying to force errors to happen to see if the error handling is correct. So in this case I forced
newUser.save()
to fail.
Express cannot handle thrown exceptions or rejected Promises. To tell express that there is an error you need to call the next() function instead of using throw or rejecting a Promise:
usersRouter.post('/register', async (req: Request, res: Response, next: Function) => {
try {
const { email, password } = req.body;
if (!email || !password)
res.status(403).send({ message: "Bad Request"});
const newUser = User.createUser({ email, password });
await newUser.save();
res.status(201).send(newUser);
} catch (err) {
next(new InternalServerError()); // This is how you "throw" errors
// in Express
}
})
Note that express only expect you to call the function it passes as the third argument. The name of the argument is up to you. You can use words other than next:
usersRouter.post('/register', async (req: Request, res: Response, error: Function) => {
try {
/* some logic */
} catch (err) {
error(new InternalServerError());
}
})
Though traditionally the name of the third argument is next and most javascript developers expect it to be that.
Adding to slebetman's answer: You can throw errors that will be handled by the exception handler, but only in synchronous middleware. You can try it out:
app.use("/sync", function(req, res) {
throw "sync";
})
.use("/async", async function(req, res) {
throw "async";
})
.use(function(err, req, res, next) {
res.end("Caught " + err);
});

express-validator checkSchema not raise errors

I'm using checkSchema as middleware to validate some query input. It's working but it is not throwing errors.
Here's part of my code:
Router
import express from "express";
import * as controller from "../controllers/productsController";
import schema from "../validations/apiSchema";
const router = express.Router();
router
.route("/")
.get(schema, controller.getAllProducts)
.post(controller.createProduct);
export default router;
Controller
export function getAllProducts(req: express.Request, res: express.Response) {
productsModel
.find(req.query)
.then((products) => res.json(products))
.catch((err) => res.status(500).json(err));
}
Schema
const apiSchema = validator.checkSchema({
company: {
in: ["query"],
isInt: true,
exists: true,
// force an error
custom: {
options: (value) => {
console.log(value)
throw new Error("name is required");
},
}
}
});
The query parameter was a random string. It "works", console.log is called at every request, but it doesn't raise any errors.
I took a look at documentation and I saw that:
app.post(
'/user',
body('username').isEmail(),
body('password').isLength({ min: 5 }),
(req, res) => {
// you must get the errors by yourself
// even using checkSchema
const errors = validationResult(req); // <------
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
User.create({
username: req.body.username,
password: req.body.password,
}).then(user => res.json(user));
},
);
**These comments above are not part of the documentation
checkSchemas doesn't throw an error on its own. You must handle this yourself in the next middleware. In that case, I'll just return the error at the end, but you can do whatever you want.
So, that's it. Is it not complex but it is something easy to forget. For now, I'm using this small line in the script, that is enough to fix the whole thing:
export function getAllProducts(req: express.Request, res: express.Response) {
// now it throws errors
validator.validationResult(req).throw();
productsModel
.find(req.query)
.then((products) => res.json(products.length))
.catch((err) => res.status(500).json(err));
}

Handle exceptions on every Express route

I would like some basic error handling on every route, so if there is ever an exception, the API at least responds with 500.
According to this pattern, you still need to include a try/catch block in every route:
app.post('/post', async (req, res, next) => {
const { title, author } = req.body;
try {
if (!title || !author) {
throw new BadRequest('Missing required fields: title or author');
}
const post = await db.post.insert({ title, author });
res.json(post);
} catch (err) {
next(err) // passed to the error-handling middleware
}
});
That seems a little repetitive. Is there a higher-level way where exceptions are automatically caught everywhere and passed to the middleware?
I mean, it would obviously be possible for me to just define my own appGet():
function appGet(route, cb) {
app.get(route, async (req, res, next) => {
try {
await cb(req, res, next);
} catch (e) {
next(e);
}
});
}
Is there some built in version of this?
You can use express-promise-router package.
A simple wrapper for Express 4's Router that allows middleware to return promises. This package makes it simpler to write route handlers for Express when dealing with promises by reducing duplicate code.
E.g.
app.ts:
import express from 'express';
import Router from 'express-promise-router';
import bodyParser from 'body-parser';
const router = Router();
const app = express();
const port = 3000;
app.use(bodyParser.json());
app.use(router);
router.post('/post', async (req, res) => {
const { title, author } = req.body;
if (!title || !author) {
throw new Error('Missing required fields: title or author');
}
const post = { title, author };
res.json(post);
});
router.use((err, req, res, next) => {
res.status(500).send(err.message);
});
app.listen(port, () => console.log(`Server started at http://localhost:${port}`));
You don't need try/catch statement block anymore.
Test result:
I think the better approach would be to divide the services and the controllers which is demonstrated below.
Add post service:
async function addPostService (title, author) => {
if (!title || !author)
throw new BadRequest('Missing required fields: title or author');
return await db.post.insert({ title, author });
};
Add post controller:
function addPost(req, res, next){
const { title, author }= req.body;
addPostService
.then((post) => {
res.json(post);
})
.catch(next) // will go through global error handler middleware
}
Now, we can make a global error handler middleware which will catch the error thrown by any controller throughout the app.
function globalErrorHandler(err, req, res, next){
switch(true){
case typeof err === 'string':
// works for any errors thrown directly
// eg: throw 'Some error occured!';
return res.status(404).json({ message: 'Error: Not found!'});
// our custom error
case err.name = 'BadRequest':
return res.status(400).json({ message: 'Missing required fields: title or author!'})
default:
return res.status(500).json({ message: err.message });
}
}
And, don't forget to use the error handler middleware right before starting the server.
// ....
app.use(globalErrorHandler);
app.listen(port, () => { console.log('Server started...')});

express-validator not producing validation errors

I am facing issues while trying express-validator v4.3.0.
There is one basic example of login with post request. There are two parameters email and password. Below is my code.
routes.js file:
var express = require('express');
var router = express.Router();
var validator = require('./validator');
router.post('/login', [sanitize('email').trim(),
validator.publicRouteValidate('login')], (req, res, next) => {
console.log(req);
}
validator.js file:
'use strict';
const { check, validationResult } = require('express-validator/check');
const { matchedData, sanitize } = require('express-validator/filter');
module.exports = {
publicRouteValidate: function (method) {
return (req, res, next) => {
switch (method) {
case 'login':
check('email').isEmail().withMessage('email must be an email')
.isLength({min: 109}).withMessage('minimum length should be 100 characters')
break;
}
var errors = validationResult(req)
console.log(errors.mapped())
if (!errors.isEmpty()) {
res.status(422).json({ errors: errors.array()[0].msg })
} else {
res.send('login')
}
}}}
Now when I am doing POST request with only email and value of email is abc. So I should get the error like email must be an email. But I did not get any response. So What is the issue I do not know?
Your publicRouteValidate function is only creating a validator, but it is never being called.
This happens because, as documented, check returns an express middleware. Such middleware must be given to an express route in order for it to do its work.
I recommend you to break that function in two: one that creates your validators, and another that checks the request for validation errors, returning earlier before touching your route.
router.post(
'/login',
createValidationFor('login'),
checkValidationResult,
(req, res, next) => {
res.json({ allGood: true });
}
);
function createValidationFor(route) {
switch (route) {
case 'login':
return [
check('email').isEmail().withMessage('must be an email'),
check('password').not().isEmpty().withMessage('some message')
];
default:
return [];
}
}
function checkValidationResult(req, res, next) {
const result = validationResult(req);
if (result.isEmpty()) {
return next();
}
res.status(422).json({ errors: result.array() });
}

Resources