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));
}
Related
i have a strange problem. i detected where the problem is, but i dont know how to solve it. i use cookie-parser, express-mysql-session, express-session, connect-flash, passport and etc in my project. to store sessions in mySQL database, i use express-mysql-session module. but this module makes a problem for connect-flash. this is where i config first middlewares:
const setFirstMiddleWares = (server: Express) => {
if (devMode) server.use(morgan("dev"));
server.use(bodyParser.urlencoded({ extended: false }));
server.use(cookieParser());
server.use(
expressSession({
secret: "secret",
saveUninitialized: false,
resave: false,
store: MySQLSessionStore, //the problem is exactly this line
})
);
server.use(flash());
server.use(passport.initialize());
server.use(passport.session());
server.use(setRenderConfig);
};
the problem that express-mysql-session make for connect-flash is that when i set a message with connect-flash in middlewares and then i redirect user to for example login page, at the first time no message will be displayed in view. but when i refresh the login page, the message will be displayed! but when i remove the store property of express-session config object, everything will be fine and the flash message will be displayed in login view immediately after user redirected to the login page and the login page doesnt need to be refreshed to display the error or success message! it is really strange behavior for flash-connect. you can see the important parts of my codes:
DB.ts:
mport mysql from "mysql2/promise";
import MySQLStoreSessionsStore from "express-mysql-session";
import * as expressSession from "express-session";
const dbConfig = {
host: process.env.DBHOST,
user: process.env.DBUSER,
password: process.env.DBPASSWORD,
port: Number(process.env.DBPORT),
database: process.env.DATABASENAME,
};
const connection = mysql.createPool(dbConfig);
const MySQLSessionStoreClass = MySQLStoreSessionsStore(expressSession);
const MySQLSessionStore = new MySQLSessionStoreClass({}, connection);
export { connection as mysql, MySQLSessionStore };
Users.ts:
import { NextFunction, Request, Response } from "express";
import passport from "passport";
import { signUpUser } from "../models/Users";
const signUpUserController = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
await signUpUser(req.body);
req.flash("sign-in-successes", [
"your account has been created successfully! please login to your account.",
]);
res.redirect("/login");
} catch (errors) {
if (Array.isArray(errors)) {
req.flash("sign-up-errors", <string[]>errors);
res.redirect("/sign-up");
} else next({ error: errors, code: "500" });
}
};
const signInUserController = (
req: Request,
res: Response,
next: NextFunction
) => {
passport.authenticate("local", {
failureRedirect: "/login",
failureFlash: true,
})(req, res, next);
};
const remmemberUserController = (req: Request, res: Response) => {
console.log("line 5:", req.body);
if (req.body["remmember-user"]) {
req.session.cookie.originalMaxAge = 253402300000000;
} else {
req.session.cookie.expires = undefined;
}
res.redirect("/account/dashboard");
};
const logOutController = (req: Request, res: Response, next: NextFunction) => {
req.logOut({ keepSessionInfo: false }, (err) => {
console.log(err);
req.flash("sign-in-successes", ["logged out successfuly!"]);
res.redirect("/login");
});
};
export {
signUpUserController,
signInUserController,
logOutController,
remmemberUserController,
};
passport.ts:
import passport from "passport";
import localStrategy from "passport-local";
import bcrypt from "bcrypt";
import { getUsersInfos } from "../models/Users";
passport.use(
new localStrategy.Strategy(
{
usernameField: "user-email-signin",
passwordField: "user-password-signin",
},
async (email, password, done) => {
try {
const user: any = await getUsersInfos(email);
if (Array.isArray(user)) {
const length: 2 | number = user.length;
if (length === 0) {
return done(null, false, {
message: "user with your entered email not found!",
});
}
const isMatch = await bcrypt.compare(password, user[0].password);
if (isMatch) {
return done(null, user);
} else {
return done(null, false, {
message: "email or password are incorrect!",
});
}
} else {
return done(null, false, {
message: "something went wrong. please try again!",
});
}
} catch (error) {
console.log(error);
}
}
)
);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser(async (user, done) => {
if (Array.isArray(user)) {
done(null, user[0].id);
}
});
LoginController.ts:
import { Request } from "express";
import { NewExpressResponse } from "../types/Types";
const loginController = (req: Request, res: NewExpressResponse) => {
res.render("login", {
title: `login`,
scripts: [...res.locals.scripts, "/js/LoginScripts.js"],
successes: req.flash("sign-in-successes"),
error: req.flash("error"),
});
};
export { loginController };
i searched a lot for this problem in google, but the only thing that i found is that i should use req.session.save(() => { res.redirect("/login");}) in my middlewares to display the flash message in the view immediately after user redirected to the route.
but there are some problems with this way:
i should use this code for every redirection if i want to set and use the flash message at the redirect route and i actually dont like this.
i cant do something like this in signInUserController because passport set the errors in flash by itself.
so do you have any idea to fix this strange behavior of connect-flash when im using express-mysql-session? thanks for help :)
I found that this is a problem(actually not a problem, a feature that can be added) in connect-flash. It doesnt save the session by itself. Because of it, i created the async version of this package with more features. You can use promise base or callback base functions to save and get your flash messages. So you can use async-connect-flash instead of using connect-flash. I hope you like it :)
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' });
};
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);
});
I have my Express + Passport + Firebase project where I handle authentication with a local stratetegy. Since I found that Passport would take care of the authentication process, so I also found that it would accept flash messages as third parameter for the done() function (in the strategy). But I am not sure how to read them:
I guess the flow I made to set and read flash messages were:
Install connect-flash with NPM.
Set the Express middleware after importing it:
import * as flash from 'connect-flash';
...
const app = express();
...
app.use(flash());
Configure Passport Authentication in the Express route according to the documentation:
// POST - /api/v1/admin/oauth/login
router.post(
'/login',
async (req: Request, res: Response) => { /* middleware function to validate input */ },
passport.authenticate('local', {
failureRedirect: '/api/v1/admin/oauth/login',
failureFlash: true
}),
async (req: Request, res: Response) => { /* function after success login */
);
Include the flash messages in the done() method, according to Passport configuration documentation:
import { Strategy as LocalStrategy } from 'passport-local';
import db from '../../config/database';
import * as bcrypt from 'bcryptjs';
export default new LocalStrategy({ usernameField: 'email' }, async (email, password, done) => {
const ref = db.collection('users').doc(email);
try {
const doc = await ref.get();
if (!doc.exists) {
return done(null, false, { error: 'Wrong email' });
}
const user = doc.data();
const match: boolean = await bcrypt.compare(password, user.password);
if (!match) {
return done(null, false, { error: 'Wrong password' });
}
user.id = doc.id;
delete user.password;
return done(null, user);
} catch(error) {
return done(error);
}
});
Read the flash messages using req.flash('error'):
// GET - /api/v1/admin/oauth/login
router.get('/login', (req: any, res: Response) => {
const result: IResult = {
message: '',
data: null,
ok: false
};
if (req.flash('error')) {
resultado.message = req.flash('error');
console.log(req.flash('error'));
}
return res.status(400).json(result);
});
I thought it was theroically working in my mind, until step 5, where req.flash('error') has an empty array in it. What I am doing wrong?
You're passing the flash message wrong!
The 3rd argument of done() should be an object with the fields type and message:
return done(null, false, { message: 'Wrong email' });
The type defaults to error.
This API doesn't seem to be documented explicitly, but is shown in the 3rd example of the Verify Callback section in the Configure chapter of the Passport.js documentation.
I've created a repo with a minimally reproducible working example.
I keep searching and I found a solution but it works in the second login attempt.
Steps from my question I modified to make it work:
Install connect-flash with NPM.
Set the Express middleware after importing it:
import * as flash from 'connect-flash';
...
const app = express();
...
app.use(flash());
Configure Passport Authentication in the Express route according to the documentation:
// POST - /api/v1/admin/oauth/login
router.post(
'/login',
async (req: Request, res: Response) => { /* middleware function to validate input */ },
passport.authenticate('local', {
failureFlash: true,
failureRedirect: '/api/v1/admin/oauth/login'
}),
async (req: Request, res: Response) => { /* function after success login */
);
Create another route so it can display the flash message, thanks to #Codebling:
// GET - /api/v1/admin/oauth/login
router.get('/login', (req: Request, res: Response) => {
const result: IResult = {
message: 'Auth ok',
data: null,
ok: true
};
let status: number = 200;
const flashMessage: any = req.flash('error');
if (flashMessage.length) {
resultado.message = flashMessage[0];
resultado.ok = false;
status = 400;
}
return res.status(status).json(result);
});
Include the flash messages in the done() method, according to Passport configuration documentation:
import { Strategy as LocalStrategy } from 'passport-local';
import db from '../../config/database';
import * as bcrypt from 'bcryptjs';
export default new LocalStrategy({ usernameField: 'email' }, async (email, password, done) => {
const ref = db.collection('users').doc(email);
try {
const doc = await ref.get();
if (!doc.exists) {
return done(null, false, { message: 'Wrong email' });
}
const user = doc.data();
const match: boolean = await bcrypt.compare(password, user.password);
if (!match) {
return done(null, false, { message: 'Wrong password' });
}
user.id = doc.id;
delete user.password;
return done(null, user);
} catch(error) {
return done(error);
}
});
The project is a Node/Express API server, generated by swagger-node-codegen. Its POST API needs a validator to check the syntax of the request body.
Here are two examples of the issue:
If POST using an empty request body,
If POST using a request body including more attributes than required,
the server will receive the request body and will still process as normal, which is sending values to query functions.
What is the appropriate way to check the syntax of a POST request body and response the error?
I use middlewares to check POST body values: https://expressjs.com/en/guide/using-middleware.html
router.post('/create', validateEmpty, (req, res) => {
res.status(200).send({result: "OK"})
})
const validateEmpty = (req, res, next) => {
let value = req.body.value;
if(value.length === 0) {
return res.status(500).send({
error: "empty value"
});
}
// if string value is longer than 0, continue with next function in route
next();
}
This is a very good module for validating the request body syntax and values. Here is an example.
You can create a helper module requestValidator.js for validating all the routes request. considering a signup API.
const Joi = require('joi');
module.exports = {
// auth
validateSignUp: (input) => {
const schema = Joi.object().keys({
firstName: Joi.string().required(),
lastName: Joi.string().required(),
email: Joi.string().email({ minDomainAtoms: 2 }).required(),
password: Joi.string().min(6).max(20).required(),
confirmPassword: Joi.string().valid(Joi.ref('password')).required().options({
language: {
any: {
allowOnly: 'must match password'
}
}
}),
gender: Joi.string().required(),
address: addressSchema.required(),
});
return Joi.validate(input, schema);
},
}
And in your authController.js
const { validateSignUp } = require('../helpers/requestValidation');
module.exports = {
signUp: async (req, res, next) => {
const body = req.body;
// validation
const { error } = validateSignUp(body);
if(error) return res.status(400).json({
success: false,
message: error
});
const user = await User.findOne({ email: email.toLowerCase() });
if(user) {
return respondFailure(res, 'the email address you have entered is already registered');
}
const newUser = new User(body);
await newUser.save();
return res.status(400).json({
success: true,
message: 'user registered successfully',
data: newUser
});
},
}
you can add your entire app's request validation in requestValidator.js and use it in your controllers.
Above solutions works, but their exists a express library for validating either body, query or params.
You can follow following steps.
1). Firstly install the library by this command.
npm install --save express-validator
2). Now where your routes are defined, import this
const { body, query, param } = require("express-validator");
3). Now in your route you can use either of above objects example..
router.post('/upload',
[
query('id')
.not()
.isEmpty()
.withMessage('id field can not be empty')
.matches(/^[0-9]+$/)
.withMessage('id must be integer only'),
body('fullName')
.trim()
.not()
.isEmpty()
.withMessage('fullName can not be empty')
.matches(/^[A-Za-z]+$/)
.withMessage('fullName must be Alpha only')
],
userController.fetchProperty
);
their are number of methods are defined in the package as i used some of them in above example like isEmpty(), matches(), isInt() etc.
4). Now in your controller where your logic is defined, you first need to import this
const { validationResult } = require("express-validator");
5). Now in your logic you just need to call this function by passing req, res object to it
const checkInputError = (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(422).json({
message: 'failure',
status: 422,
errors: errors.array()
});
}
};
Now you will get whole validation error as response either applied in body, query or param.
Hope this will help you or somebody else!
You can refer here for more query.
https://express-validator.github.io/docs/