I am trying to validate some input using express validator but i have a different setup than one in the documentation.
Im validating if the body.payload is not null
this.validator.document
public document = async (req: Request, res: Response, next: NextFunction) => {
check("payload").exists({ checkNull: true });
try {
validationResult(req).throw();
next();
} catch (err) {
res.status(422).json({ errors: err.mapped() });
}
}
this.controller.document
public document = async (req: Request, res: Response): Promise<any> => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
}
documentRoute
this.router.post("/:id/document",
this.jwtGuard,
this.validator.document,
this.controller.document);
im aware check is a middleware in itself, so how do i handle this inside my existing validator function that may have some other validation before it.
At the moment this does not work, even tho payload is set to null. It should catch the error and return a 422 response but it is not.
in validator.document:
public document = () => check("payload").exists({ checkNull: true });
in documentRoute:
this.router.post("/:id/document",
this.jwtGuard,
this.validator.document(), // notice the parentheses
this.controller.document);
Update: If you want to handle the errors in validator.document, you need to call check middleware before it when declaring the route:
this.router.post("/:id/document",
this.jwtGuard,
check("payload").exists({ checkNull: true }),
this.validator.document,
this.controller.document);
And in validator.document:
public document = async (req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
}
Update 2: If you have multiple check operations and does not want to bloat your route definition, I suggest you use schema validation.
Related
This question already has answers here:
Extend Express Request object using Typescript
(29 answers)
Closed 6 months ago.
const app = express();
This block is working when I use type any for response object
app.use((_req: Request, response: any, next: NextFunction) => {
response.success = function ({ result = {}, code = 200, message = "" }) {
return response.json({
result, code, message
})
}
next();
})
This block is not working when I use Response type from express because the property success does not exist.
app.use((_req: Request, response: Response, next: NextFunction) => {
response.success = function ({ result = {}, code = 200, message = "" }) {
return response.json({
result, code, message
})
}
next();
})
So I create a new type CustomResponse
interface BaseParams {
code?: number,
message?: string,
result?: any
}
interface CustomResponse extends Response {
success: (params: BaseParams) => Response;
};
app.use((_req: Request, response: CustomResponse, next: NextFunction) => {
response.success = function ({ result = {}, code = 200, message = "" }) {
return response.json({
result, code, message
})
}
next();
})
I get this new error
No overload matches this call.
The last overload gave the following error.
Argument of type '(_req: Request, response: CustomResponse, next: NextFunction) => void' is not assignable to parameter of type 'PathParams'
So I wonder how can I create this kind of global methods using typescript in the right way to avoid this PathParams error type
I created the types/express/index.d.ts file
interface BaseParams {
code?: number,
data?: any,
errors?: any,
message?: string,
}
declare namespace Express {
interface Request {
user?: any;
}
interface Response {
onSuccess: (params: BaseParams) => Response;
onError: (params: BaseParams) => Response;
}
}
Then I edited my tsconfig.json with
"compilersOptions": {
"typeRoots": ["./types"],
}
So I can add extra fields that I needed on Response or Request types. On my routes file
const router = Router();
router.use((_req, response: Response, next: NextFunction) => {
response.onSuccess = ({ data = {}, code = HTTPStatus.OK, message = "" }) => {
return response.status(code).json({
statusCode: code,
message,
data
})
}
response.onError = ({ errors = {}, code = HTTPStatus.BAD_REQUEST, message = "" }) => {
return response.status(code).json({
statusCode: code,
message,
errors
})
}
next();
});
How should I import loginMember in Controller? I am developing a REST API and now I need to use code in a different file location. I am having an error in the controller. When I am calling loginMember. (Cannot find name 'loginMember'.ts(2304))
SERVICE
import MembersModel from '../models/MembersModel';
import BaseService from './BaseService';
import { createPasswordToHash } from '../scripts/utils/auth';
class MembersService extends BaseService {
constructor() {
super(MembersModel);
}
// loginMember
loginMember = async (email: any, password: any) => {
return new Promise(async (resolve, reject) => {
try {
let data = await this.BaseModel.findOne({
email: email,
password: createPasswordToHash(password),
});
return resolve(data);
} catch (error) {
return reject(error);
}
});
};
}
export default MembersService;
CONTROLLER
import BaseController from './BaseController';
import MembersService from '../services/MembersService';
import ApiError from '../errors/ApiError';
import { NextFunction, Request, Response } from 'express';
import { createPasswordToHash, generateAccessToken } from '../scripts/utils/auth';
import httpStatus from 'http-status';
class MembersController extends BaseController {
constructor(membersService: MembersService) {
super(membersService);
}
login = (req: Request, res: Response, next: NextFunction) => {
MembersService.loginMember(req.body)
.then((response: any) => {
if (response) {
const member = {
...response.toObject(),
accessToken: generateAccessToken(response.toObject()),
};
delete member.password;
delete member.createdAt;
delete member.updatedAt;
return res.status(httpStatus.OK).send(member);
}
return res.status(httpStatus.UNAUTHORIZED).send({ error: 'Invalid email or password' });
})
.catch((err: { message: string }) => {
return next(
new ApiError(err.message, httpStatus.UNAUTHORIZED, 'login', req.headers['user-agent']?.toString() || 'Unknown')
);
});
};
}
export default new MembersController(new MembersService());
Now I am gettig a new error: "Property 'loginMember' does not exist on type 'typeof MembersService'.ts(2339)"
You're trying to call loginMember as a static method, but it's not defined as one. You'll have to use an instance of MembersService to use the method. Since your MembersController is already being initialized with a MembersService instance, you may just want to have a membersService property on the MembersController. Also, the loginMember method takes an email and a password, so you'll have to pass those arguments explicitly instead of just passing the request body. (I'm not sure where the email and password are in the request body though, so I can't help you there.) So with those changes, it would look like:
class MembersController extends BaseController {
private membersService: MembersService;
constructor(membersService: MembersService) {
super(membersService);
this.membersService = membersService;
}
login = (req: Request, res: Response, next: NextFunction) => {
this.membersService.loginMember(email, password) // <- Get these from the request
.then((response: any) => {
if (response) {
const member = {
...response.toObject(),
accessToken: generateAccessToken(response.toObject()),
};
delete member.password;
delete member.createdAt;
delete member.updatedAt;
return res.status(httpStatus.OK).send(member);
}
return res.status(httpStatus.UNAUTHORIZED).send({ error: 'Invalid email or password' });
})
.catch((err: { message: string }) => {
return next(
new ApiError(err.message, httpStatus.UNAUTHORIZED, 'login', req.headers['user-agent']?.toString() || 'Unknown')
);
});
};
One other code style suggestion would be to use async await instead of .then in the login method. Also, the Promise wrapping in the loginMember method looks unnecessary, and using an async function as the argument is an antipattern. The following should get the job done while avoiding those pitfalls:
loginMember = (email: any, password: any): Promise<Response> => {
return this.BaseModel.findOne({
email: email,
password: createPasswordToHash(password),
});
};
Im using Router classes to manage all my Routes:
const router = express.Router();
/**
* User Sign up Route at /api/auth/register
*/
router.post(
"/register",
checkBodyParameters(['username', 'email', 'password']),
verifyRegister.ensurePasswordStrength,
verifyRegister.checkUsernameAndEmail,
AuthController.register
);
export = router;
I want to check the x-www-form-urlencoded body parameters. To see if either the key is not what it should be, or the value is empty.
I wrote a middleware function to check that:
import { Request, Response } from "express";
export default function checkBodyParameters(
bodyParams: Array<string>,
req: Request,
res: Response,
next
) {
let requestBodyParams: Array<string> = [];
requestBodyParams.push(req.body.username, req.body.email, req.body.password);
requestBodyParams.forEach((requestBodyParam) => {
if (bodyParams.includes(requestBodyParam)) {
if (requestBodyParam !== "") {
next();
} else {
res.status(400).json({
message: "Paremeter cant be empty",
value: requestBodyParam,
});
}
} else {
res
.status(400)
.json({ message: "Paremeter not specified", value: requestBodyParam });
}
});
}
But it seems like it doesnt like me passing Arguments to the middleware function in
checkBodyParameters(['username', 'email', 'password'])
My Question is how do i create a middleware function which acceppts more values than req, res and next? And how to use this function correctly with the router instance.
Any Feedback is appreciated
You are calling the function instead of returning a function as a middleware.
Instead, use:
const checkBodyParameters = (
bodyParams: Array<string>
) => (
req: Request,
res: Response,
next
) => {
let requestBodyParams: Array<string> = [];
requestBodyParams.push(req.body.username, req.body.email, req.body.password);
requestBodyParams.forEach((requestBodyParam) => {
if (bodyParams.includes(requestBodyParam)) {
if (requestBodyParam !== "") {
next();
} else {
res.status(400).json({
message: "Paremeter cant be empty",
value: requestBodyParam,
});
}
} else {
res
.status(400)
.json({ message: "Paremeter not specified", value: requestBodyParam });
}
});
}
export default checkBodyParameters
I have been trying to use class-validator as middleware to validate some of my data.
I would love to get some advice as to
how can I also validate updates and what's a good validation
Here is the current class validator to validate the req.body sent when trying to register.
export default async (req: Request, res: Response, next: NextFunction) => {
let user = new User();
user.username = req.body.username;
user.email = req.body.email;
user.password = req.body.password;
let fieldErrors = await validate(user);
if (fieldErrors.length > 0) {
let errors = ValidatorErrToFieldErr(fieldErrors);
next(new HttpExeception({ statusCode: httpCode.BAD_REQUEST, errors }));
} else {
next();
}
};
What is a good validation pattern? I controller that handles some of the logic which in turn calls the service to mutate the database.
AuthController.ts
public static Register = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
req.body.password = await argon2.hash(req.body.password);
let modelUser = await service.addUser(req.body);
let user: IUserMe = {
//reasign user fields
};
req.session.user = {
id: user.id,
username: user.username,
isAdmin: user.isAdmin,
};
res.json({ user });
} catch (error) {
if (error.code === "23505") {
next(
new HttpExeception({
statusCode: httpCode.BAD_REQUEST,
errors: duplicationErrToFieldError(error.detail),
})
);
} else next(new HttpExeception({ statusCode: httpCode.SERVER_ERROR }));
}
};
UserService.ts
async addUser(input: IRegisterInput): Promise<User> {
return await getRepository(User).save(input);
}
So most middleware it’s called inside the routes themselves. I’ll use an example with Express, Passport, and TypeScript since it’s what I know best.
Say i don’t want users to access my “/home” page without signing in. So I write a middleware function:
export default ( req: Request, res: Response, next: NextFunction): void => {
if(req.user != undefined){
next();
}
else{
res.status(401);
}
}
This would be analogous to your class-validator function. Now, we need to make sure that this function runs before any API calls are made to “/home”.
Thus, we write the api route as
import * as express from “express”;
import {Request, Response} from “express”;
import isAuthenticated from “isAuthenticated.ts”;
class HomeRouter{
public path = “/”;
public router = App.router();
constructor(){
this.initRoutes();
}
public initRoutes(){
this.router.get(“/home”, isAuthenticated, (req: Request, res: Response) => {
res.send(“/index.html”);
}
}
}
This will force isAuthenticated to run before any of the logic in the rest of route is executed. If you would like the middleware to apply to every call to the server, just put express.use(isAuthenticated); in your server.ts file. If you’re using a technology different from Express that I’ve failed to identify, I’m sure the premise is the same, and the how will be in the documentation.
I am using Typescript in Node.js. When you use Express middleware, you often transform the Request object. With Typescript, however, we could not track how the Request object was transformed. If you know the middleware that passed before, is there a way to find out the type of the request from it? If not possible in express, I would like to find another framework where it is possible. Is it possible in Nest (https://github.com/kamilmysliwiec/nest)?
Example Code
import { Request, Response, NextFunction } from 'express';
function userMiddleware(req: Request & User, res: Response, next: NextFunction) {
req.user = {
id: 'user_id',
};
next();
}
interface User {
user: {
id: string;
}
}
interface Middleware {
<T>(req: Request & T, res: Response, next: NextFunction): void;
}
class Controller {
middleware = [userMiddleware];
get = new GetMethod(this.middleware);
post = (req: Request /* I don't know exact req type */, res: Response, next: NextFunction) => {
console.log(req.user) // Error!
}
}
class GetMethod {
constructor(middleware: Middleware[]) {
// How to deduce type of req from Middleware array?
}
}
const controller = new Controller();
express.use('/', controller.middleware, controller.post);
I want to extract type information from Middleware list in Controller class.
First I think the right interface is
interface User {
id: string;
}
Because they're callbacks they'll receive default Request that don't have user in its signature.
Therefore you have 2 options, do a type assertion, or to write a custom declaration. Both a fine if you do them properly.
Type assertion:
interface User {
id: string;
}
const isObject = (value: unknown): value is {[key: string]: unknown} => {
return value && typeof value === 'object';
};
const isReqWithUser = (req: Request): req is Request & {user: User} => {
return isObject(req) && !!req.user;
}
class Controller {
post = (req: Request, res: Response, next: NextFunction) => {
if (isReqWithUser(req)) {
console.log(req.user) // now it works
}
next();
}
}
Custom declaration:
but we need to understand that user not always exist on the request and we should mark it optional.
interface User {
id: string;
}
declare module 'express' {
export interface Request {
user?: User; // adding our custom declaration.
}
}
class Controller {
post = (req: Request, res: Response, next: NextFunction) => {
console.log(req.user) // now it works
next();
}
}