As demonstrated on https://strongloop.github.io/strongloop.com/strongblog/async-error-handling-expressjs-es7-promises-generators/#using-es7-asyncawait, I wanted to use a wrapper for all my async Express handlers to catch any errors happening there, but in a typed version for TypeScript.
I came up with this: (wrap.ts)
import { NextFunction, RequestHandler, Response, Request } from 'express';
type AsyncRequestHandler = (req: Request, res: Response, next: NextFunction) => Promise<any>;
/**
* Catches errors and passes them to the next callback
* #param handler Async express request handler/middleware potentially throwing errors
* #returns Async express request handler with error handling
*/
export default (handler: AsyncRequestHandler): RequestHandler => {
return (req, res, next) => {
return handler(req, res, next).catch(next);
};
};
I want to build a REST API with some endpoints like PUT /users/:userId and DELETE /users/:userId. For convenience, I don't want to query the specific user with the ID userId from the database in every handler and instead store it in req using a middleware. That means I have to use a modified Request interface for the handler definition adding a user property, e.g. UserRequest.
import express, { Request } from 'express';
import wrap from './wrap';
const app = express();
app.use('/users/:userId', wrap(async (req, res, next) => {
// set req.user ...
}));
export interface UserRequest extends Request {
user: User;
}
app.put('/users/:userId', wrap(async (req: UserRequest, res) => {
// do something with req.user ...
}));
// ...
This would be possible when not using wrap, but not with this type definition of wrap. The TypeScript compiler produces the following error:
Argument of type '(req: UserRequest, res: Response) => Promise<void>' is not assignable to parameter of type 'AsyncRequestHandler'.
Types of parameters 'req' and 'req' are incompatible.
Type 'Request' is not assignable to type 'UserRequest'.
Property 'user' is missing in type 'Request'.
What is the "TypeScript way" to accomplish this?
I somehow didn't realize that the issue appears without wrap as well (as long as the strict compiler option is enabled). My solution was to extend the express.Request interface.
Related
I am reading a code that has two files like below:
first file that uses the currentuser middleware:
const router = express.Router();
router.get("/api/users/currentuser", currentUser, (req, res) => {
res.send({ currentUser: req.currentUser || null });
});
export { router as currentUserRouter };
Second file that defines the middleware:
interface UserPayload {
id: string;
email: string;
}
declare global {
namespace Express {
interface Request {
currentUser?: UserPayload;
}
}
}
export const currentUser = (
req: Request,
res: Response,
next: NextFunction
) => {
if (!req.session?.jwt) {
return next();
}
try {
const payload = jwt.verify(
req.session.jwt,
process.env.JWT_KEY!
) as UserPayload;
req.currentUser = payload;
} catch (err) {}
next();
};
I understand that if there is a verified jwt token, the middleware will take the the payload out of it and add it to the req object. But what if it fails and it can't add the payload/current user to the req? What would happen for the following request and what will the res object look like?
router.get("/api/users/currentuser", currentUser, (req, res) => {
res.send({ currentUser: req.currentUser || null });
});
Could you edit this get request to show how can I catch the probable error if I am not the writer of the middleware?
If you had a catchall exception handler, and your middleware threw an exception, you would determine the response.
If your middleware threw an exception and you did not catch it, the system might just exit the process.
If your middleware did not throw an exception, and did not call next(), and did not respond, the request would hang.
If your middleware returned a response, and did not call next(), your send function would never get invoked.
The bottom line is that you need to dump the response on your server and see exactly how your middleware handles this.
In most of my auth middleware, I choose to not call next(), and return a 403 error. But there are some benefits by throwing an exception, then returning a 403 from a catchall handler.
You need to respond with an error HTTP status code, and an error message in the body. The exact status and message depends on the type of the exception and its parameters, so you need to catch it and check it.
The current express middleware does not handle errors, it just does not set the req.currentUser = payload;, so you won't know about the user. I don't think this is a proper solution for an authentication error.
In the documentation you can see how error are handled:
https://expressjs.com/en/guide/using-middleware.html
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(500).send('Something broke!')
})
So I would rewrite the code and if the JWT verification fails, then I return for example 401 unauthorized. https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
I guess you are using this JWT library: https://github.com/auth0/node-jsonwebtoken According to the docs and the code there are 3 types of errors: TokenExpiredError, JsonWebTokenError, NotBeforeError for verify. Here you can check when they are thrown: https://github.com/auth0/node-jsonwebtoken/blob/master/verify.js , here are their definitions: https://github.com/auth0/node-jsonwebtoken/tree/master/lib
So in the catch block you just check the type of the error with instanceof e.g. if (err instanceof jwt.JsonWebTokenError) ... and send the message accordingly with the res.status(401) and put the next() to the end of the try block, because it should be called only if the verification does not fail.
I have a node.js app and am using typescript with express in it. I have declared a error middleware function using
app.use((error:any, req:Request, res:Response, next:NextFunction) => {
res.status(500).json({message:error.message, stack:error.stack, path:req.path});
});
However I get errors about all the data types of the 4 inputs.
No overload matches this call.
The last overload gave the following error.
Argument of type '(error: any, req: Request, res: Response, next: NextFunction) => void' is not assignable to parameter of type 'PathParams'.ts(2769)
index.d.ts(163, 5): The last overload is declared here.
How can I fix this?
Be sure that you imported the right classess from Express
import express, { Request, Response, Express, NextFunction } from "express";
I am new to tsoa and I want to do CSRF implementation in my node app. I have been able to make api using app.use() but I want to write in tsoa. Is there any way?
In the pre-released version, you can use the #Middlewares() decorator.
Just put what you had in a app.use() to the #Middlewares() decorator.
You can define your Middleware / Middlewares like this:
import { Request, Response, NextFunction } from 'express';
// ... controller logic ...
// #Get('/endpoint') | #Post('/endpoint') etc.
#Middlewares([
(req: Request, res: Response, next: NextFunction) => {
console.log(req.headers);
next();
},
(req: Request, res: Response, next: NextFunction) => {
console.log('Second middleware, but we can also use only one!');
next();
},
])
// getEndpoint(): string {
// return 'Hello World!';
// }
// ... controller logic ...
Also remember to have set the experimentalDecorators to true in your tsconfig.json.1
1 https://github.com/lukeautry/tsoa/pull/1123#issuecomment-1018251162
With Firebase HTTP functions, we can install express and use middlewares. Middlewares are useful (among other things) for checking pre-conditions before functions execute. For example, we can check authentication, authorization, etc in middlewares so that they don't need to be repeated in every endpoint definition.
How are developers achieving the same thing with Firebase callable functions? How are you extracting out all functionality that would typically be in chained middlewares when you have a large number of callable functions?
It seems that there's no readily available middleware framework for callable functions, so inspired by this, I rolled my own. There are some general purpose chained middleware frameworks on NPM, but the middleware I need is so simple that it was easier to roll my own than to configure a library to work with callable functions.
Optional: Type declaration for Middleware if you're using TypeScript:
export type Middleware = (
data: any,
context: functions.https.CallableContext,
next: (
data: any,
context: functions.https.CallableContext,
) => Promise<any>,
) => Promise<any>;
Here's the middleware framework:
export const withMiddlewares = (
middlewares: Middleware[],
handler: Handler,
) => (data: any, context: functions.https.CallableContext) => {
const chainMiddlewares = ([
firstMiddleware,
...restOfMiddlewares
]: Middleware[]) => {
if (firstMiddleware)
return (
data: any,
context: functions.https.CallableContext,
): Promise<any> => {
try {
return firstMiddleware(
data,
context,
chainMiddlewares(restOfMiddlewares),
);
} catch (error) {
return Promise.reject(error);
}
};
return handler;
};
return chainMiddlewares(middlewares)(data, context);
};
To use it, you would attach withMiddlewares to any callable function. For example:
export const myCallableFunction = functions.https.onCall(
withMiddlewares([assertAppCheck, assertAuthenticated], async (data, context) => {
// Your callable function handler
}),
);
There are 2 middlewares used in the above example. They are chained so assertAppCheck is called first, then assertAuthenticated, and only after they both pass does your hander get called.
The 2 middleware are:
assertAppCheck:
/**
* Ensures request passes App Check
*/
const assertAppCheck: Middleware = (data, context, next) => {
if (context.app === undefined)
throw new HttpsError('failed-precondition', 'Failed App Check.');
return next(data, context);
};
export default assertAppCheck;
assertAuthenticated:
/**
* Ensures user is authenticated
*/
const assertAuthenticated: Middleware = (data, context, next) => {
if (!context.auth?.uid)
throw new HttpsError('unauthenticated', 'Unauthorized.');
return next(data, context);
};
export default assertAuthenticated;
As a bonus, here's a validation middleware that uses Joi to ensure the data is validated before your handler gets called:
const validateData: (schema: Joi.ObjectSchema<any>) => Middleware = (
schema: Joi.ObjectSchema<any>,
) => {
return (data, context, next) => {
const validation = schema.validate(data);
if (validation.error)
throw new HttpsError(
'invalid-argument',
validation.error.message,
);
return next(data, context);
};
};
export default validateData;
Use the validation middleware like this:
export const myCallableFunction = functions.https.onCall(
withMiddlewares(
[
assertAuthenticated,
validateData(
Joi.object({
name: Joi.string().required(),
email: Joi.string().email().required(),
}),
),
],
async (data, context) => {
// Your handler
},
),
);
Middleware for Firebase callable functions is not possible. Callable functions force your endpoint to use a certain path, a certain type of input (JSON via POST) and a certain type of output (also JSON). Express wouldn't really help you out, given the constraints of how callables work. You can read about all the callable protocol details in the documentation. You can see that callables abstract away all the details of the request and response, which you would normally work with when using Express.
As per this community answer,
HTTP requests to callable functions don't really come "from" a URL. They come from anywhere on the internet. It could be a web site, Android or iOS app, or someone who simply knows the protocol to call the function.
If you're building a web app and you want to pass along the URL of the page making the request, you'll have to add that data into the object that the client passes to the function, which shows up in data.
So unless you workaround that by sending the URL in the data of the callable function, it will not work. And even if you do, it just would go against the principle of callable functions, so I would recommend that you use HTTP Functions for that purpose.
Is there a way to specify a type for express's request. I was hoping there was I could make the request object of a custom type.I was looking into possibly extending the router type in order to have this.Or is there a way I can refactor it to make the type the custom type.
app.get('text',function(req: CustomResponse,res,next)
{
//route code here
});
This code throws the following error:
No overload matches this call.
Overload 1 of 3, '(path: PathParams, ...handlers: RequestHandler<ParamsDictionary, any, any, ParsedQs>[]): Router', gave the following error.
Argument of type '(req: Request, res: Response<any, number>, next: NextFunction) => Promise<void>' is not assignable to parameter of type 'RequestHandler<ParamsDictionary, any, any, ParsedQs>'.
Types of parameters 'req' and 'req' are incompatible.
So I was wondering the best way to go about this.
It's better if you'll use its corresponding type and just set a type on your custom req or res inside the function
Install
npm install #types/express --save-dev
Code Sample
import { Request, Response, NextFunction } from 'express';
app.use('text', (req: Request, res: Response, next: NextFunction) => {
const body: CustomRequest = req.body; // req.body or other properties under req
// and assign it to your corresponding type
});
In your code, I have noticed that you specify the req with CustomResponse.
Note that the 1st parameter req is a request and the 2nd parameter res is response
Express allows you to easily add to its request or response prototype. You can see the source that enables that here. You will need to use the original response object in order to actually send a response so the general model is that you extend the existing object rather than replace it entirely.
Here's a simple example:
const express = require('express');
const app = express();
// add .test() method to the Express response object
app.response.test = function(str) {
console.log("got our .test() method call");
this.send(str);
}
app.get("/", (req, res) => {
res.test("Hi!!!!!");
});
app.listen(80);
In this example, we add a .test(str) method to the response object prototype. So, whenever the response object is passed to an Express request handler, that .test() method will be there already and you can see how it is called in the example above.
I had the use case where I wanted to add the type for a custom attribute inside each request. And custom request which would hold the different body according to request.
case 1: Custom attribute inside each request
I am using the name space & adding the custom attribute type for each request.
declare namespace Express {
export interface Request {
context: {
authorizerId: string;
organisationid: string;
};
}
}
Case 2: Custom request with differnet body
a: Custom request body
interfce CustomReqestBody {
name: string;
description: string;
}
b: Custom Request
export interface CustomRequest<B> extends Request {
body: B;
}
c: Router
import CustomReqestBody from './path';
import CustomRequest from './path';
mspRouter.post('/', async (request: CustomRequest<CustomReqestBody>, response: Response) => {
return response.status(200).json({ message: 'Connected!' });
});