Расширение объекта экспресс-запроса с помощью Typescript


129

Я пытаюсь добавить свойство для выражения объекта запроса из промежуточного программного обеспечения с использованием машинописного текста. Однако я не могу понять, как добавить к объекту дополнительные свойства. Я бы предпочел не использовать скобки, если это возможно.

Я ищу решение, которое позволило бы мне написать что-то подобное (если возможно):

app.use((req, res, next) => {
    req.property = setProperty(); 
    next();
});

вы должны иметь возможность расширить интерфейс запроса, предоставляемый файлом express.d.ts, с помощью нужных полей.
toskv

Ответы:


148

Вы хотите создать собственное определение и использовать в Typescript функцию слияния деклараций . Это обычно используется, например, в method-override.

Создайте файл custom.d.tsи убедитесь , чтобы включить его в tsconfig.json«s files-сече- , если таковые имеются. Содержимое может выглядеть следующим образом:

declare namespace Express {
   export interface Request {
      tenant?: string
   }
}

Это позволит вам в любой момент кода использовать что-то вроде этого:

router.use((req, res, next) => {
    req.tenant = 'tenant-X'
    next()
})

router.get('/whichTenant', (req, res) => {
    res.status(200).send('This is your tenant: '+req.tenant)
})

2
Я только что сделал это, но я заставил его работать, не добавляя файл custom.d.ts в раздел файлов в моем tsconfig.json, но он все еще работает. Это ожидаемое поведение?
Хаим Фридман

1
@ChaimFriedman Да. filesСекция ограничивает набор файлов , включенных машинопись. Если вы не укажете filesили include, то все *.d.tsбудут включены по умолчанию, поэтому нет необходимости добавлять туда свои собственные типы ввода.
interphx

9
Не работает для меня: я получаю, Property 'tenantчто не существует по типу «Запрос». Не имеет значения, включаю я его явно tsconfig.jsonили нет. ОБНОВЛЕНИЕ С declare globalas @basarat pointet в его ответах работает, но я должен был сделать import {Request} from 'express'сначала.
Lion

5
FWIW, этот ответ теперь устарел . Ответ JCM - правильный способ увеличить Requestобъект в expressjs (по крайней мере, 4.x)
Эрик Липранди

3
Для будущих поисков - хороший пример, который, как я нашел, сработал из коробки: github.com/3mard/ts-node-example
jd291

80

Как указано в комментариях вindex.d.ts , вы просто объявляете в глобальном Expressпространстве имен любые новые члены. Пример:

declare global {
  namespace Express {
    interface Request {
      context: Context
    }
  }
}

Полный пример:

import * as express from 'express';

export class Context {
  constructor(public someContextVariable) {
  }

  log(message: string) {
    console.log(this.someContextVariable, { message });
  }
}

declare global {
  namespace Express {
    interface Request {
      context: Context
    }
  }
}

const app = express();

app.use((req, res, next) => {
  req.context = new Context(req.url);
  next();
});

app.use((req, res, next) => {
  req.context.log('about to return')
  res.send('hello world world');
});

app.listen(3000, () => console.log('Example app listening on port 3000!'))

Более подробно о расширении глобальных пространств имен можно прочитать в моем GitBook .


Зачем нужен global в декларации? Что будет, если его нет?
Джейсон Кухрт

Это работает с интерфейсами, но в случае, если кому-то нужно объединить типы, обратите внимание, что типы «закрыты» и не могут быть объединены: github.com/Microsoft/TypeScript/issues/…
Питер В.

Мистер @basarat, я должен вам пива.
marcellsimon

Мне также пришлось добавить в свой tsconfig.json: {"compilerOptions": {"typeRoots": ["./src/typings/", "./node_modules/@types"]}, "files": ["./ src / typings / express / index.d.ts "]}
marcellsimon

Ни одно из вышеперечисленных решений не помогло ... но это сработало при первом запуске ... большое спасибо .. !!
Ритеш

56

Для более новых версий Express вам необходимо расширить express-serve-static-coreмодуль.

Это необходимо, потому что теперь объект Express исходит оттуда: https://github.com/DefinitiTyped/DefinitiTyped/blob/8fb0e959c2c7529b5fa4793a44b41b797ae671b9/types/express/index.d.ts#L19

В основном используйте следующее:

declare module 'express-serve-static-core' {
  interface Request {
    myField?: string
  }
  interface Response {
    myField?: string
  }
}

1
Это сработало для меня, тогда как расширение старого 'express'модуля - нет. Спасибо!
Бен Кригер

4
Я боролся с этим, чтобы заставить это работать, мне также пришлось импортировать модуль:import {Express} from "express-serve-static-core";
andre_b

1
@andre_b Спасибо за подсказку. Я думаю, что оператор импорта превращает файл в модуль, и это необходимая часть. Я перешел на использование, export {}которое тоже работает.
Даньял Айтекин

2
Убедитесь , что файл этот код идет в это не называется express.d.ts, в противном случае компилятор будет пытаться объединить это к экспресс типизации, что приводит к ошибкам.
Том Спенсер

3
Убедитесь, что ваши типы должны быть первыми в typeRoots! types / express / index.d.ts и tsconfig => "typeRoots": ["./src/types", "./node_modules/@types"]
kaya

31

Принятый ответ (как и другие) не работает для меня, но

declare module 'express' {
    interface Request {
        myProperty: string;
    }
}

сделал. Надеюсь, это кому-то поможет.


2
Подобный метод описан в документации TS в разделе «Расширение модуля». Это здорово, если вы не хотите использовать *.d.tsфайлы и просто храните свои типы в обычных *.tsфайлах.
им.панкратов

3
это единственное, что сработало и у меня, все остальные ответы, кажется, должны быть в файлах .d.ts
парламент

Это работает и для меня, при условии, что я помещаю свой custom-declarations.d.tsфайл в корень проекта TypeScript.
focorner

Я расширил исходный тип, чтобы сохранить его: import { Request as IRequest } from 'express/index';и interface Request extends IRequest. Также пришлось добавить typeRoot
Бен

17

После того, как попробовали 8 или около того ответов, но безуспешно. Я , наконец , удалось получить его работу с jd291 комментарием указывающей «ы к 3mards репо .

Создайте в базе файл под названием types/express/index.d.ts. И в нем напишите:

declare namespace Express {
    interface Request {
        yourProperty: <YourType>;
    }
}

и включить его в tsconfig.json:

{
    "compilerOptions": {
        "typeRoots": ["./types"]
    }
}

Затем yourPropertyдолжны быть доступны по каждому запросу:

import express from 'express';

const app = express();

app.get('*', (req, res) => {
    req.yourProperty = 
});

14

Ни одно из предложенных решений у меня не помогло. В итоге я просто расширил интерфейс запроса:

import {Request} from 'express';

export interface RequestCustom extends Request
{
    property: string;
}

Затем использовать:

import {NextFunction, Response} from 'express';
import {RequestCustom} from 'RequestCustom';

someMiddleware(req: RequestCustom, res: Response, next: NextFunction): void
{
    req.property = '';
}

Изменить : последние версии TypeScript жалуются на это. Вместо этого мне пришлось сделать:

someMiddleware(expressRequest: Request, res: Response, next: NextFunction): void
{
    const req = expressRequest as RequestCustom;
    req.property = '';
}

1
это будет работать, но довольно многословно, если у вас есть сотни функций промежуточного программного обеспечения, амирит
Александр Миллс

1
@ user2473015 Да, последние версии Typescript сломали это. См. Мой обновленный ответ.
Том Меттам

8

В TypeScript интерфейсы открыты. Это означает, что вы можете добавлять к ним свойства из любого места, просто переопределяя их.

Учитывая, что вы используете этот файл express.d.ts , у вас должна быть возможность переопределить интерфейс запроса, чтобы добавить дополнительное поле.

interface Request {
  property: string;
}

Затем в вашей функции промежуточного программного обеспечения параметр req также должен иметь это свойство. Вы сможете использовать его без каких-либо изменений в вашем коде.


1
Как вы «делитесь» этой информацией по всему вашему коду? Если я определяю свойство в запросе, скажем , Request.user = {};в app.tsтом же userController.tsзнает об этом?
Nepoxx 04

2
@Nepoxx, если вы переопределите интерфейс, компилятор объединит свойства и сделает их видимыми везде, вот почему. В идеале вам следует сделать переопределение в файле .d.ts. :)
toskv 04

Кажется, это работает, однако, если я использую тип express.Handler(вместо того, чтобы указывать вручную (req: express.Request, res: express.Response, next: express.NextFunction) => any)), он, похоже, не относится к тому же, Requestпоскольку жалуется, что моего свойства не существует.
Nepoxx 04

Я бы этого не ожидал, если только express.Handler не расширит интерфейс Request. Является ли?
toskv 04

2
Я могу заставить это работать, если использую, declare module "express"но не если использую declare namespace Express. Я бы предпочел использовать синтаксис пространства имен, но у меня он просто не работает.
WillyC 01

5

Хотя это очень старый вопрос, я недавно наткнулся на эту проблему. Принятый ответ работает нормально, но мне нужно было добавить настраиваемый интерфейс Request- интерфейс, который я использовал в своем коде, и который не работал так хорошо с принятым ответ. По логике, я попробовал это:

import ITenant from "../interfaces/ITenant";

declare namespace Express {
    export interface Request {
        tenant?: ITenant;
    }
}

Но это не сработало, потому что Typescript обрабатывает .d.tsфайлы как глобальный импорт, и когда в них есть импорт, они рассматриваются как обычные модули. Вот почему приведенный выше код не работает со стандартной настройкой машинописного текста.

Вот что я в итоге сделал

// typings/common.d.ts

declare namespace Express {
    export interface Request {
        tenant?: import("../interfaces/ITenant").default;
    }
}
// interfaces/ITenant.ts

export interface ITenant {
    ...
}

Это работает для моего основного файла, но не для моих файлов маршрутизации или контроллеров, я не получаю линтинга, но когда я пытаюсь скомпилировать, он говорит: «Свойство 'user' не существует для типа 'Request'». (Я использую пользователя вместо арендатора), но если я добавлю // @ ts-ignore над ними, то это сработает (хотя это, конечно, глупый способ исправить это. Есть ли у вас какие-либо мысли о том, почему это может быть не так? работает с другими моими файлами?
Логан

Это очень странная вещь, @Logan. Вы можете поделиться своим .d.ts,tsconfig.json и экземпляр использования? Кроме того, какую версию машинописного текста вы используете, поскольку этот импорт в глобальные модули поддерживается только начиная с TS 2.9? Это могло бы помочь лучше.
16kb 02

Я загрузил сюда данные pastebin.com/0npmR1Zr Я не уверен, почему подсветка все испорчена, хотя это из основного файла prnt.sc/n6xsyl Это из другого файла prnt.sc/n6xtp0 Очевидно, что это какая-то часть он понимает, что происходит, а компилятор - нет. Я использую машинописную версию 3.2.2
Логан

1
Удивительно, ... "include": [ "src/**/*" ] ...но у меня работает, но "include": ["./src/", "./src/Types/*.d.ts"],не работает. Я еще не углубился в попытки понять это
16kb

У меня работает импорт интерфейса с использованием динамического импорта. Спасибо
Роман Махоцкий

3

Возможно, на этот вопрос был дан ответ, но я хочу немного поделиться, теперь иногда интерфейс, такой как другие ответы, может быть слишком ограничительным, но мы действительно можем поддерживать необходимые свойства, а затем добавлять любые дополнительные свойства, создавая ключ с типом stringс типом значенияany

import { Request, Response, NextFunction } from 'express'

interface IRequest extends Request {
  [key: string]: any
}

app.use( (req: IRequest, res: Response, next: NextFunction) => {
  req.property = setProperty();

  next();
});

Итак, теперь мы также можем добавить к этому объекту любое дополнительное свойство, которое захотим.


3

Все эти ответы кажутся ошибочными или устаревшими в той или иной степени.

Это сработало для меня в мае 2020 года:

в ${PROJECT_ROOT}/@types/express/index.d.ts:

import * as express from "express"

declare global {
    namespace Express {
        interface Request {
            my_custom_property: TheCustomType
        }
    }
}

in tsconfig.jsonдобавьте / объедините свойство таким образом, чтобы:

"typeRoots": [ "@types" ]

Приветствия.


Работает с Webpack + Docker, import * можно заменить на export {};
Дооомель

2

Если вы ищете решение, которое работает с express4, вот оно:

@ types / express / index.d.ts: -------- должно быть /index.d.ts

declare namespace Express { // must be namespace, and not declare module "Express" { 
  export interface Request {
    user: any;
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2016",
    "typeRoots" : [
      "@types", // custom merged types must be first in a list
      "node_modules/@types",
    ]
  }
}

Ссылка с https://github.com/TypeStrong/ts-node/issues/715#issuecomment-526757308


1

Одно из возможных решений - использовать «двойное приведение к любому».

1- определить интерфейс с вашей собственностью

export interface MyRequest extends http.IncomingMessage {
     myProperty: string
}

2- двойной заброс

app.use((req: http.IncomingMessage, res: http.ServerResponse, next: (err?: Error) => void) => {
    const myReq: MyRequest = req as any as MyRequest
    myReq.myProperty = setProperty()
    next()
})

Преимущества двойного литья:

  • доступен набор текста
  • он не загрязняет существующие определения, а расширяет их, избегая путаницы
  • поскольку приведение явное, он компилирует штрафы с -noImplicitanyфлагом

В качестве альтернативы есть быстрый (нетипизированный) маршрут:

 req['myProperty'] = setProperty()

(не редактируйте существующие файлы определений своими собственными свойствами - это невозможно поддерживать. Если определения неверны, откройте запрос на перенос)

РЕДАКТИРОВАТЬ

См. Комментарий ниже, в этом случае работает простой кастинг req as MyRequest


@akshay В данном случае да, потому что MyRequestрасширяет http.IncomingMessage. Если бы это было не так, anyто единственной альтернативой была бы двойная
перекачка

Рекомендуется использовать значение unknown вместо any.
dev,

0

Этот ответ будет полезен тем, кто полагается на пакет npm ts-node .

Я также боролся с той же проблемой расширения запроса объекта , я следил за множеством ответов в переполнении стека и закончил, следуя указанной ниже стратегии.

Я объявил расширенную типизацию для экспресс в следующем каталоге.${PROJECT_ROOT}/api/@types/express/index.d.ts

declare namespace Express {
  interface Request {
    decoded?: any;
  }
}

затем обновите мой tsconfig.jsonдо чего-то вроде этого.

{
  "compilerOptions": {
     "typeRoots": ["api/@types", "node_modules/@types"]
      ...
  }
}

даже после выполнения описанных выше шагов визуальная студия перестала жаловаться, но, к сожалению, ts-nodeкомпилятор все еще бросал.

 Property 'decoded' does not exist on type 'Request'.

По-видимому, ts-nodeне удалось найти определения расширенного типа для запроса объекта .

В конце концов, потратив часы, я знал, что VS Code не жаловался и смог найти определения ввода, подразумевая, что что-то не так с ts-node complier.

Обновление начало scriptв package.jsonфиксированной для меня.

"start": "ts-node --files api/index.ts",

что --filesаргументы играют ключевую роль здесь найти определение пользовательских определений типа.

Для получения дополнительной информации посетите: https://github.com/TypeStrong/ts-node#help-my-types-are-missing


0

Чтобы помочь всем, кто просто ищет что-то еще, чтобы попробовать, вот что у меня сработало в конце мая 2020 года, когда я пытался расширить запрос ExpressJS. Мне пришлось перепробовать более дюжины вещей, прежде чем это заработало:

  • Измените порядок того, что все рекомендуют в «typeRoots» вашего tsconfig.json (и не забудьте отказаться от пути src, если у вас есть параметр rootDir в tsconfig, такой как «./src»). Пример:
"typeRoots": [
      "./node_modules/@types",
      "./your-custom-types-dir"
]
  • Пример настраиваемого расширения ('./your-custom-types-dir/express/index.d.ts "). Мне пришлось использовать встроенный импорт и экспорт по умолчанию, чтобы использовать классы в качестве типа в моем опыте, так что это тоже показано:
declare global {
  namespace Express {
    interface Request {
      customBasicProperty: string,
      customClassProperty: import("../path/to/CustomClass").default;
    }
  }
}
  • Обновите файл nodemon.json, чтобы добавить команду "--files" в ts-node, например:
{
  "restartable": "rs",
  "ignore": [".git", "node_modules/**/node_modules"],
  "verbose": true,
  "exec": "ts-node --files",
  "watch": ["src/"],
  "env": {
    "NODE_ENV": "development"
  },
  "ext": "js,json,ts"
}

0

Может быть, уже довольно поздно для этого ответа, но в любом случае вот как я решил:

  1. Убедитесь, что в ваш tsconfigфайл включен источник ваших типов (это может быть совершенно новый поток)
  2. Внутри каталога типов добавьте новый каталог и назовите его как пакет, для которого вы хотите расширить или создать типы. В этом конкретном случае вы создадите каталог с именемexpress
  3. Внутри expressкаталога создайте файл и назовите его index.d.ts(ДОЛЖЕН БЫТЬ ТОЧНО ТАК)
  4. Наконец, чтобы сделать расширение типов, вам просто нужно поместить код, подобный следующему:
declare module 'express' {
    export interface Request {
        property?: string;
    }
}

-2

зачем нам столько хлопот, как в принятых выше ответах, когда мы можем уйти от этого

вместо того, чтобы прикреплять наше свойство к запросу , мы можем прикрепить его к заголовкам запроса

   req.headers[property] = "hello"
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.