Розширте експрес-запит за допомогою Typescript


128

Я намагаюся додати властивість для вираження об'єкта запиту від середнього програмного забезпечення за допомогою typecript. Однак я не можу зрозуміти, як додати до об’єкта додаткові властивості. Я вважаю за краще не використовувати позначення дужок, якщо можливо.

Я шукаю рішення, яке дозволило б мені написати щось подібне до цього (якщо можливо):

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

ви повинні мати можливість розширити інтерфейс запиту, який надає файл express.d.ts із потрібними полями.
toskv

Відповіді:


147

Ви хочете створити власне визначення та використовувати функцію в 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 globalяк @basarat pointet в своїх роботах з одягу, але я повинен був зробити це import {Request} from 'express'спочатку.
Лев

5
FWIW, ця відповідь застаріла . Відповідь JCM - це правильний спосіб збільшити Requestоб’єкт у expressjs (принаймні 4.x)
Ерік Ліпранді,

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

79

Як запропоновано коментарями у програмі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 .


Чому глобальна потреба в декларації? Що станеться, якщо її немає?
Джейсон Курт

Це працює з інтерфейсами, але у випадку, коли комусь потрібно об’єднати типи, зауважте, що типи "закриті" і їх неможливо об'єднати: github.com/Microsoft/TypeScript/isissue/…
Peter W

Містер @basarat Я зобов'язаний вам пивом.
marcellsimon

Я також повинен був додати до свого tsconfig.json: {"compilerOptions": {"typeRoots": ["./src/typings/", "./node_modules/@types"]}, "files": ["./ src / typping / express / index.d.ts "]}
marcellsimon

Жодне з перерахованих вище рішень не працювало .. але цей зробив цю роботу в першому запуску .. велике спасибі .. !!
Ritesh

55

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

Це потрібно, тому що тепер об’єкт Express надходить звідти: https://github.com/DefinitelyTyped/DefinrelyTyped/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 {}який також працює.
Danyal Aytekin

2
Переконайтеся, що файл, у який входить цей код, не викликається express.d.ts, інакше компілятор спробує об'єднати це з експрес-типізацією, що призведе до помилок.
Том Спенсер

3
Переконайтеся, що ваші типи повинні бути першими у typeRoots! type / 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файлах.
im.pankratov

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 repo .

Створіть файл у базі під назвою 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

Жодне із запропонованих рішень не працювало для мене. Я закінчила просто розширення інтерфейсу Request:

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
це буде спрацьовувати, але досить багатослівно, якщо у вас є 100-ти функцій проміжного програмного забезпечення, аміріт
Олександр Міллз

1
@ user2473015 Так, останні версії Typescript зламали це. Дивіться мою оновлену відповідь.
Том Меттам

8

У TypeScript інтерфейси відкриті. Це означає, що ви можете додавати до них властивості з будь-якого місця, просто переробивши їх.

Зважаючи на те, що ви використовуєте цей файл express.d.ts , ви повинні мати змогу перевизначити інтерфейс запиту, щоб додати додаткове поле.

interface Request {
  property: string;
}

Тоді у вашій функції проміжного програмного забезпечення параметр req також повинен мати це властивість. Ви повинні мати можливість користуватися ним без змін у вашому коді.


1
Як ви "ділитесь" цією інформацією у всьому коді? Якщо я визначаю властивість в запиті, скажімо , Request.user = {};в app.tsтому ж userController.tsзнає про це?
Nepoxx

2
@Nepoxx, якщо ви переглянете інтерфейс, компілятор об'єднає властивості та зробить їх видимими скрізь, ось чому. В ідеалі ви б переробили визначення у файлі .d.ts. :)
toskv

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

Я б не очікував цього, якщо не express.Handler розширює інтерфейс Request. робить це?
toskv

2
Я можу змусити цю роботу, якщо користуюсь, declare module "express"але ні, якщо використовую declare namespace Express. Я вважаю за краще використовувати синтаксис простору імен, але він просто не працює для мене.
WillyC

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 {
    ...
}

Це працює для мого основного файлу, але не в моїх файлах маршрутизації чи контролерах, я не отримую вкладок, але коли я намагаюся скомпілювати, він каже, що "Властивість" користувача "не існує для типу" Запити "." (Я використовую користувача замість орендаря), але якщо я додаю // @ ts-ignore над ними, він працює (хоча це, безумовно, дурний спосіб виправити це. Чи є у вас думки, чому це може бути не так працюю над іншими моїми файлами?
Логан

Це дуже дивна річ @ Логан. Можете чи ви поділитися .d.ts, tsconfig.jsonі екземпляр використання? Крім того, яку версію машинопису ви використовуєте, оскільки цей імпорт у глобальні модулі підтримується лише починаючи з TS 2.9? Це може допомогти краще.
16kb

Я завантажив сюди дані, 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();
});

Тож тепер ми також можемо додати до цього об’єкта будь-яку додаткову властивість.


2

Якщо ви шукаєте рішення, яке працює з express4, ось це:

@ vrste / 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/isissue/715#issuecomment-526757308


2

Усі ці відповіді, схоже, так чи інакше є неправильними або застарілими.

Це працювало для мене в травні 2020 року:

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

import * as express from "express"

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

в tsconfig.json, додайте / об'єднайте властивість таким чином:

"typeRoots": [ "@types" ]

Ура.


Працює з Webpack + Docker, імпорт * може бути замінений експортом {};
Дуомель

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()

(не редагуйте існуючі файли визначення з власними властивостями - це неможливо досягти. Якщо визначення неправильні, відкрийте запит на витягнення)

EDIT

Дивіться коментар нижче, простий кастинг працює в цьому випадку req as MyRequest


@akshay У цьому випадку так, тому що MyRequestрозширює http.IncomingMessage. Як би це не так, подвійний кастинг через 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 не скаржився і зміг знайти визначення тексту, маючи на увазі, що щось не так із ts-nodeкомплаєром.

Початок оновлення 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, наприклад:
{
  "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.