Расширение объекта экспресс-запроса с помощью 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.