Express + TypeScript: Create type inference for response.locals - node.js

I want to add types for my response.locals. It is used to append data to your request-response cycle.
What I tried
// ./types/express/index.d.ts
declare global {
declare namespace Express {
interface Response {
locals: {
userId: number;
};
}
}
}
// tsconfig.json
"compilerOptions": {
"typeRoots": ["./types"],
}
myController.post("/", async (request, response) => {
// Can't get type in my controller
const { userId } = response.locals; // <- userId is any
Goal: Get correct type inference for my response.locals variable
Versions:
"express": "^4.17.1",
"#types/express": "^4.17.8",
"typescript": "^4.5.4"

Without a proper TypeScript background, I was able to compile the following (after some trial and error):
import {Request, Response} from "express";
interface Locals extends Record<string, any> {
userId: number;
}
interface MyResponse extends Response {
locals: Locals;
}
export function middleware(request: Request, response: MyResponse) {
const {userId} = response.locals;
response.end(userId);
return userId;
}
The declaration file (.d.ts) contains:
export declare function middleware(request: Request, response: MyResponse): number;
so the number type was correctly inferred.
And it works when I use this middleware function in express.

Related

Typescript express typing on Request object

I seem to have an issue with Typescript typings on my Express Request object. The project for now exists out of 2 sub-projects (user-service and a common project which includes reusable Errors and Middlewares)
The common folder is installed as a dependency in the user-service like:
"#myPackage/common": "file:../common",
In there I have a current-user middleware:
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
interface UserPayload {
id: string;
email: string;
}
declare global {
namespace Express {
interface Request {
currentUser?: UserPayload;
}
}
}
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) {
console.error(err);
}
return next();
};
export default currentUser;
with a declared global for the currentUser property on the Request object.
In my user-service project I have the following route
import express, { Request, Response } from 'express';
import { Middlewares } from '#myPackage/common';
const router = express.Router();
router.get('/api/users/currentuser', Middlewares.currentUser, (
req: Request,
res: Response,
) => {
res.send({ currentUser: req.currentUser || null });
});
export default router;
On req.currentUser I get the following error message:
Property 'currentUser' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.
Shouldn't the package typings automatically be taken over in the code in which you import it? I hope I made myself clear on what the problem is :)
I've also always had trouble declaring a global namespace to attach types to express's request object. Found myself a solution using "declare module" instead of "declare global". So instead of
declare global {
namespace Express {
interface Request {
currentUser?: UserPayload;
}
}
}
maybe give the following approach a try:
declare module "express-serve-static-core" {
interface Request {
currentUser?: UserPayload;
}
}
Normally your currentUser property should also be available in other files with this approach, but you can of course export the manipulated Request interface if not.
Note that in most cases you will need to reference the "express-serve-static-core" module as this is where the Request interface is declared.

Accessing middleware injected data in Typescript

Here's a high level view of a controller in Typescript-Node :
As I'm storing details of user in product model, I have used a middleware to check if user if logged in before accessing the endpoint and also injecting user info to the req which can be further used in different controllers
exports.addProduct = async (req: Request, res: Response, next: NextFunction) => {
// images:
try {
// logic to handle data from req.body
// getting this user id from middleware isLoggedIn
// injecting user id into request in the isLoggedIn middleware
req.body.user = req.user._id;
const product = await Product.create(req.body);
return res.status(200).json({
success: true,
product,
});
} catch (error) {
logger.error(error);
}
};
Getting error : Property 'user' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>' , on the line
req.body.user = req.user._id;
isLoggedIn is typical function to check Bearer token or header or cookies and then inject user info to the request
It worked Perfectly in Javascript, now trying same in Typescript as a part to learn Typescipt
There are two ways to achieve this:
Extending express Request locally
Extending express Request globally
Using the local way require to write lots of redundent code and that's why
the global way is much better. it can be done by creating file as follows:
index.d.ts
import { User } from "../../models/user";
// to make the file a module and avoid the TypeScript error
export {};
declare global {
namespace Express {
export interface Request {
user: User;
}
}
}
Then add this config to tsconfig.json file
"typeRoots": [
"src/#types",
"./node_modules/#types",
],
Then Request object will recognize user and user can be injected from any middleware to be used in any controller.
The problem is that according to the typing of req, there is no property named user. TypeScript is notifying you that req.user should be undefined, according to the available typings. There are some possible solutions to fix your problem.
You could explicitly type the variable as any. This is considered to be bad practice sometimes, because in general you should try to type everything correctly (nevertheless: it works).
// Option 1: Explicitly declare variable as any
req.body.user = (req as any).user._id;
You could also check if req.user is defined, like this:
// Option 2: Check req.user manually
if (req.user) req.body.user = req.user._id;
else throw new Error("Some Error");
You could also type the req correctly, according to the API specifications of your middleware. This is usually a lot of work if done manually. Some modules ship with correct TypeScript-typings already.
Maybe you want to also look into this question since it is very similar to your question.
first create a folder call types it should be at the root of your project
then at yow tsconfig.json in the compilerOptions section add a paths prop
{
"compilerOptions": {
...
"paths": {
"express": [
"./types/express/index.d.ts"
],
}
}
then at the types dir add a new dir call express inside add an index.d.ts go ahead a copy them express definitions
// Type definitions for Express 4.17
// Project: http://expressjs.com
// Definitions by: Boris Yankov <https://github.com/borisyankov>
// China Medical University Hospital <https://github.com/CMUH>
// Puneet Arora <https://github.com/puneetar>
// Dylan Frankland <https://github.com/dfrankland>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/* =================== USAGE ===================
import express = require("express");
var app = express();
=============================================== */
/// <reference types="express-serve-static-core" />
/// <reference types="serve-static" />
import * as bodyParser from 'body-parser';
import * as serveStatic from 'serve-static';
import * as core from 'express-serve-static-core';
import * as qs from 'qs';
/**
* Creates an Express application. The express() function is a top-level function exported by the express module.
*/
declare function e (): core.Express;
declare namespace e {
/**
* This is a built-in middleware function in Express. It parses incoming requests with JSON payloads and is based on body-parser.
* #since 4.16.0
*/
var json: typeof bodyParser.json;
/**
* This is a built-in middleware function in Express. It parses incoming requests with Buffer payloads and is based on body-parser.
* #since 4.17.0
*/
var raw: typeof bodyParser.raw;
/**
* This is a built-in middleware function in Express. It parses incoming requests with text payloads and is based on body-parser.
* #since 4.17.0
*/
var text: typeof bodyParser.text;
/**
* These are the exposed prototypes.
*/
var application: Application;
var request: Request;
var response: Response;
/**
* This is a built-in middleware function in Express. It serves static files and is based on serve-static.
*/
var static: serveStatic.RequestHandlerConstructor<Response>;
/**
* This is a built-in middleware function in Express. It parses incoming requests with urlencoded payloads and is based on body-parser.
* #since 4.16.0
*/
var urlencoded: typeof bodyParser.urlencoded;
/**
* This is a built-in middleware function in Express. It parses incoming request query parameters.
*/
export function query (options: qs.IParseOptions | typeof qs.parse): Handler;
export function Router (options?: RouterOptions): core.Router;
interface RouterOptions {
/**
* Enable case sensitivity.
*/
caseSensitive?: boolean | undefined;
/**
* Preserve the req.params values from the parent router.
* If the parent and the child have conflicting param names, the child’s value take precedence.
*
* #default false
* #since 4.5.0
*/
mergeParams?: boolean | undefined;
/**
* Enable strict routing.
*/
strict?: boolean | undefined;
}
interface SessionData {
userIp: string;
ipDetails: any;
publicKey: string;
session: string;
iv: string;
decrypted: any;
}
interface Application extends core.Application { }
interface CookieOptions extends core.CookieOptions { }
interface Errback extends core.Errback { }
interface ErrorRequestHandler<
P = core.ParamsDictionary,
ResBody = any,
ReqBody = any,
ReqQuery = core.Query,
Locals extends Record<string, any> = Record<string, any>
> extends core.ErrorRequestHandler<P, ResBody, ReqBody, ReqQuery, Locals> { }
interface Express extends core.Express { }
interface Handler extends core.Handler { }
interface IRoute extends core.IRoute { }
interface IRouter extends core.IRouter { }
interface IRouterHandler<T> extends core.IRouterHandler<T> { }
interface IRouterMatcher<T> extends core.IRouterMatcher<T> { }
interface MediaType extends core.MediaType { }
interface NextFunction extends core.NextFunction { }
interface Request<
P = core.ParamsDictionary,
ResBody = any,
ReqBody = any,
ReqQuery = core.Query,
Locals extends Record<string, any> = Record<string, any>
> extends core.Request<P, ResBody, ReqBody, ReqQuery, Locals> { }
interface RequestHandler<
P = core.ParamsDictionary,
ResBody = any,
ReqBody = any,
ReqQuery = core.Query,
Locals extends Record<string, SessionData> = Record<string, SessionData>
> extends core.RequestHandler<P, ResBody, ReqBody, ReqQuery, Locals> { }
interface RequestParamHandler extends core.RequestParamHandler { }
export interface Response<ResBody = any, Locals extends Record<string, SessionData> = Record<string, SessionData>>
extends core.Response<ResBody, Locals> { }
interface Router extends core.Router { }
interface Send extends core.Send { }
}
export = e;
if you notice from the above I added an interface call SessionData if you look almost at the end I set Locals to be equals to it. at the Response
now at yow endPoint you can apply it like this
import type e from "express";
export const endPoint: e.RequestHandler = (req, res, next) => {
//code
};
you can go beong that you can also add them params if there are any, the res body, the req body and stuff
const endPoint: e.RequestHandler<YowParamsObj,YowResBodyObj,YowReqBodyObj,ThemQueryParamsObj> = (req, res, next) => {
//code
};

Property 'user' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'

Please help, I am getting this error
src/app/middlewares/authentication.ts:16:17 - error TS2339: Property 'user' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.
16 req.user = user;
I have created the .d.ts file and also included it in tsconfig file. Still I am not able to run this code
Please find attached screenshots
Create a types folder in your src directory
Create a folder within the types folder with the name of the package you intend to extend. (In this case express).
Create an index.d.ts file in that folder
src/
- types/
- express/
- index.d.ts
add this code to the index file
import express from "express";
declare global {
namespace Express {
interface Request {
user?: Record<string,any>
}
}
}
remember to update your tsconfig.json file
{
"compilerOptions": {
"typeRoots" : ["./src/types", "./node_modules/#types"]
}
}
This should work
I was stuck on the same problem earlier. Here is how I solved it.
I created a separate directory called #types in my project for declaration merging to work.
Next I created a file in it called index.d.ts with following content. Please pay attention that we need to declare our own request within global. Also, importing express is important as well.
import * as express from "express"
declare global {
namespace Express {
interface Request {
user? : Record<string,any>
}
}
}
I added the following line under compilerOptions in my tsconfig.json.
"compilerOptions": {
...other settings,
"typeRoots": ["#types", "node_modules/#types"],
...other settings
}
And that's it. It should works with these changes.
Another way:
import { NextFunction, Request, Response } from 'express';
interface IDecode {
address: string,
role: string,
iat: number,
exp: number
};
interface RequestWithUserRole extends Request {
user?: IDecode,
}
const parseToken = (secret: string) =>
async (req: RequestWithUserRole, res: Response, next: NextFunction) => {
try{
const token = req.headers?.authorization?.split(' ')[1];
// console.log(req.headers?.authorization);
if (!token) {
return res.status(403).json({message: 'Token not found'})
}
const decodedData = <IDecode> jwt.verify(token, secret);
req.user = decodedData;
// console.log(decodedData);
return next();
}
catch(e) {
return res.status(500).json({e})
}
};
A quick walkaround if you are doing a quick prototype or following a tutorial
(req as any).user
NB: Do not use this in a real production app, because it would make your code dirty and it's not scalable.

correlationId dosn't exist in node.js

I am trying to add correlationId using express-correlation-id. I am exactly following the page: https://www.npmjs.com/package/express-correlation-id. I've imported the express-correlation-id pkg and have found it in the package.json and node modules.
But when i tried to get req.correlationId(), it always said:
Property 'correlationId' dosn't exist on type 'Request<ParamsDictionary, any, any, ParsedQs>'.
I'm using TypeScript and Express, this is what the code looks like:
import correlator = require("express-correlation-id");
app.use(correlator());
app.get('/', (req, res) => {
req.correlationId(); // where the error occurs
});
Since this package is written in js language, there are no type definition files for typescript, so you need to extend the Express.Request interface by yourself, add the correlationId method to the interface.
Package versions:
"typescript": "^3.9.7"
"express-correlation-id": "^1.3.1",
"express": "^4.17.1",
E.g.
server.ts:
import express from 'express';
import correlator = require('express-correlation-id');
declare global {
namespace Express {
export interface Request {
correlationId(): string;
}
}
}
const app = express();
app.use(correlator());
app.get('/', (req, res) => {
req.correlationId();
});
This is because the property correlationId doesn't exist in the typing of req.
So, if you see the typing of req is:
export interface Request<P = ParamsDictionary, ResBody = any, ReqBody = any, ReqQuery = ParsedQs> extends http.IncomingMessage, Express.Request {
So, you must create a new Request type with the new functions that you're going to use. For your case:
import express from "express";
import correlator = require("express-correlation-id");
import { ParamsDictionary, Request } from "express-serve-static-core";
import { ParsedQs } from "qs";
const app = express();
interface CustomReq<P = ParamsDictionary, ResBody = any, ReqBody = any, ReqQuery = ParsedQs> extends Request<P, ResBody, ReqBody, ReqQuery> {
// extended options
correlationId: () => any;
}
app.use(correlator());
app.get('/', (req: CustomReq, res) => {
req.correlationId(); // OK
});
As you see in the code above, my new Request is CustomReq and I use the new type for the req.

Node + Express router + Type Script : string arg is not assignable to requesthandlerparams type

I'm following a tuto to create a full rest api, then, i create a TS class that export a new express router :
import { Router, Request, Response, NextFunction } from 'express';
export class TourRouter {
public router: Router;
constructor() {
this.router = Router();
this.init();
}
public init(): void {
this.router.get(
'/',
this.getTours
);
}
private getTours(request: Request, response: Response, next: NextFunction) {
// Some JSON
const tours = {
"now": "2018-09-09 18:40:00",
"date": "2018-09-09",
"tours": [
{
"time": "8",
"places": 8,
"isPast": false
},
{
"time": "11",
"places": 8,
"isPast": false
},
{
"time": "14",
"places": 8,
"isPast": false
},
{
"time": "17",
"places": 8,
"isPast": false
}
]
};
response.send(tours);
}
}
const tourRoutes = new TourRouter();
tourRoutes.init();
export default tourRoutes.router;
When i want to use this new Router in my App.ts as follow, i'm getting a TS error and the build failed with the message :
Argument of type 'string' is not assignable to parameter of type 'RequestHandlerParams'
The use statement is :
private _routes(): void {
let router = express.Router();
router.get('/', (request, response, next) => {
response.json({
message: 'Hello World'
});
});
this.express.use('/', router);
this.express.use('/api/v1/tours', TourRouter);
}
As if TourRouter, in my case was not a Router as i expect but something else, but don't know why. What i'm missing ?
Regards
It sounds like you are importing the TourRouter class instead of the actual router, which is the default export. Replace import { TourRouter } from './routes/TourRouter'; with import TourRouter from './routes/TourRouter';. You might want to import the router using a name other than TourRouter to avoid confusion.
TypeScript knows that the arguments you are passing to this.express.use do not match any of the valid overload signatures, but it doesn't know which overload signature you intended to use. It (incorrectly) guesses the (...handlers: RequestHandlerParams[]) => T signature, which explains the strange error that the string path is not assignable to RequestHandlerParams.
For me it's perhaps a strange problem with the "Request" "Response" "NextFunction" type.
Can you try to simply import * from express and qualify each type with express. ?
Request --> express.Request
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/16639

Resources