Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Express.Request] Trying to type a req.locals field #765

Closed
Lzok opened this issue Nov 16, 2021 · 5 comments
Closed

[Express.Request] Trying to type a req.locals field #765

Lzok opened this issue Nov 16, 2021 · 5 comments

Comments

@Lzok
Copy link

Lzok commented Nov 16, 2021

Hi! How are you?

I am trying to type an express request.locals field without success by the moment. I can see the types when I hover on the element but Typescript is complaining me.
Here in the images you can see the typing is correct (or I think so), but the VS Code is complaining.

// request.locals.project seems to be ok

image

But TS is complaining:

image

Here is an exact copy of my code at this moment

import express, { Request, Response } from 'express';
import { ParamsDictionary } from 'express-serve-static-core';
import { z, ZodType, ZodTypeDef } from 'zod';

const router = express.Router();

export type TypedRequestLocals<TLocals extends ZodType<any, ZodTypeDef, any>> = Request<
	ParamsDictionary,
	any,
	any,
	any,
	z.infer<TLocals>
>;

const projectData = z.object({
	id: z.string(),
	name: z.string(),
	days: z.number().min(1),
});
const base = z.object({
	locals: z.object({ project: projectData }),
});

router.route('/base').get(async (request: TypedRequestLocals<typeof base>, response: Response) => {
	const { project } = request.locals;

	return response.json({ project });
});

What my main goal is? To be able to type a request.locals object where I will store data related to the request.

Any clue? Thank you!

@Lzok
Copy link
Author

Lzok commented Nov 16, 2021

Well I achieved correct typing but I have serious doubts of my method. First, I learned that in my original comment, the line z.infer<TLocals> is so wrong in that place I could go to jail. That line refers to a generic param that the Request is passing to the Response down the line, in the following simplified code:

export interface Request<
    P = ParamsDictionary,
    ResBody = any,
    ReqBody = any,
    ReqQuery = ParsedQs,
    Locals extends Record<string, any> = Record<string, any>
> extends http.IncomingMessage,
        Express.Request {
            // ... etc
           res?: Response<ResBody, Locals> | undefined;
        }

My solution at this moment implies extend the Request Express interface to add my own locals object. I have to put there my possibilities, inferred by Zod to maintain the single source of truth. The "refactor" of my previous comment is like the following:

// @src/types/express/index.d.ts
declare global {
	namespace Express {
		interface Request {
			locals: { project: ProjectData; user: UserId };
		}
	}
}

// Other file
import express, { Request, Response } from 'express';
import { ParamsDictionary } from 'express-serve-static-core';
import { z, ZodType, ZodTypeDef } from 'zod';

const router = express.Router();

export type TypedRequestLocals<TLocals extends ZodType<any, ZodTypeDef, any>> = Request<
	ParamsDictionary,
	any,
	any,
	any,
> & z.infer<TLocals>; // Line moved here

const projectData = z.object({
	id: z.string(),
	name: z.string(),
	days: z.number().min(1),
});
type ProjectData = z.infer<typeof ProjectData>;

const base = z.object({
	locals: z.object({ project: projectData }),
});

router.route('/base').get(async (request: TypedRequestLocals<typeof base>, response: Response) => {
	const { project } = request.locals;

	return response.json({ project });
});

Why I have doubts? Because in the request.locals object, when I hover, I see the following:
image

Hover on project shows the nice typings I expect:
image

So far the typing is ok and TS doesn't complies. But I am sure I can improve this piece of code.
What will you do?

@scotttrinh
Copy link
Collaborator

@Lzok

I'm not super familiar with Express, but locals doesn't seem to be a property of the Request object but rather the Response object. See here: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/03a8c197dd9c8c23ad0725f2a9e2fe37b27a4ca3/types/express-serve-static-core/index.d.ts#L648

@Lzok
Copy link
Author

Lzok commented Nov 16, 2021

Yes, the Response object has a locals prop, but I want to have a .locals of my own in the Request. It could be named whatever, I would like to have a prop to group things related to that request only, like the information needed to operate later in the route or other middleware.
I thought it would be easier to achieve, or maybe I'm kinda dumb 😂

E.g:

route.get('/:projectId',
    validateRequest, // This middleware validates the structure of the projectId value
    hydrate, // In this middleware I would like to retrieve from the DB the minimum information of the project and put it in the req.locals
    checkPermission, // Here I take the req.locals.project and check for the permissions
    async (req, res, next) => { // The actual route here, permissions checked and req.locals fullfilled }

@scotttrinh
Copy link
Collaborator

@Lzok

Ahh, that makes sense. I think you can update your ambient module declaration to just be locals: Record<string, any> as long as you always plan on making this some kind of object. Let me know if that works. You don't even really need the ambient declaration since intersection should work already. Not sure if you need it for your middleware or not.

FWIW, I went through a similar exploration in our Koa app, and ended up moving all of the request validation logic into the endpoint with a few helper functions to reduce boilerplate since middleware doesn't make it easy to be type-safe since it doesn't "return" which is the only thing that is easily type-able.

@scotttrinh
Copy link
Collaborator

@Lzok going to close for now, but feel free to continue the discussion here if you think of something that is Zod-specific.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants