Skip to content

Commit f01e972

Browse files
committed
refactor: use node http base types [BREAKING CHANGE]
1 parent 0b30c5d commit f01e972

14 files changed

+116
-46
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![dependency Status](https://snyk.io/test/npm/http-proxy-middleware/badge.svg?style=flat-square)](https://snyk.io/test/npm/http-proxy-middleware)
66
[![npm](https://img.shields.io/npm/v/http-proxy-middleware?color=%23CC3534&style=flat-square)](https://www.npmjs.com/package/http-proxy-middleware)
77

8-
Node.js proxying made simple. Configure proxy middleware with ease for [connect](https://github.com/senchalabs/connect), [express](https://github.com/strongloop/express), [browser-sync](https://github.com/BrowserSync/browser-sync) and [many more](#compatible-servers).
8+
Node.js proxying made simple. Configure proxy middleware with ease for [connect](https://github.com/senchalabs/connect), [express](https://github.com/expressjs/express), [next.js](https://github.com/vercel/next.js) and [many more](#compatible-servers).
99

1010
Powered by the popular Nodejitsu [`http-proxy`](https://github.com/nodejitsu/node-http-proxy). [![GitHub stars](https://img.shields.io/github/stars/nodejitsu/node-http-proxy.svg?style=social&label=Star)](https://github.com/nodejitsu/node-http-proxy)
1111

@@ -534,6 +534,7 @@ View the [recipes](https://github.com/chimurai/http-proxy-middleware/tree/master
534534

535535
- [connect](https://www.npmjs.com/package/connect)
536536
- [express](https://www.npmjs.com/package/express)
537+
- [next.js](https://www.npmjs.com/package/next)
537538
- [fastify](https://www.npmjs.com/package/fastify)
538539
- [browser-sync](https://www.npmjs.com/package/browser-sync)
539540
- [lite-server](https://www.npmjs.com/package/lite-server)

cspell.json

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"middlewares",
2727
"millis",
2828
"mockttp",
29+
"nextjs",
2930
"Nodejitsu",
3031
"ntlm",
3132
"proxied",

jest.config.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
12
module.exports = {
23
preset: 'ts-jest',
34
testEnvironment: 'node',
45
coverageReporters: ['text', 'lcov'],
56
collectCoverageFrom: ['src/**/*.*'],
7+
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
68
};

jest.setup.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Uncomment the following lines for less noise in test output
3+
*/
4+
5+
// console.info = jest.fn();
6+
// console.log = jest.fn();
7+
// console.error = jest.fn();

recipes/servers.md

+33-4
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ Overview of `http-proxy-middleware` implementation in different servers.
44

55
Missing a server? Feel free to extend this list of examples.
66

7-
<!-- TOC depthfrom:2 insertanchor:false -->
8-
97
- [Express](#express)
108
- [Connect](#connect)
9+
- [Next.js](#nextjs)
1110
- [Browser-Sync](#browser-sync)
1211
- [fastify](#fastify)
1312
- [Polka](#polka)
@@ -17,8 +16,6 @@ Missing a server? Feel free to extend this list of examples.
1716
- [grunt-browser-sync](#grunt-browser-sync)
1817
- [gulp-webserver](#gulp-webserver)
1918

20-
<!-- /TOC -->
21-
2219
## Express
2320

2421
https://github.com/expressjs/express
@@ -62,6 +59,38 @@ app.use(apiProxy);
6259
http.createServer(app).listen(3000);
6360
```
6461

62+
## Next.js
63+
64+
https://github.com/vercel/next.js
65+
[![GitHub stars](https://img.shields.io/github/stars/vercel/next.js.svg?style=social&label=Star)](https://github.com/vercel/next.js)
66+
![next.js downloads](https://img.shields.io/npm/dm/next)
67+
68+
Next project: `/pages/api/users.ts`
69+
70+
```typescript
71+
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
72+
import type { NextApiRequest, NextApiResponse } from 'next';
73+
import { createProxyMiddleware } from 'http-proxy-middleware';
74+
75+
const proxyMiddleware = createProxyMiddleware({
76+
target: 'http://jsonplaceholder.typicode.com',
77+
changeOrigin: true,
78+
pathRewrite: {
79+
'^/api/users': '/users',
80+
},
81+
});
82+
83+
export default function handler(req: NextApiRequest, res: NextApiResponse) {
84+
proxyMiddleware(req, res, (result: unknown) => {
85+
if (result instanceof Error) {
86+
throw result;
87+
}
88+
});
89+
}
90+
91+
// curl http://localhost:3000/api/users
92+
```
93+
6594
## Browser-Sync
6695

6796
https://github.com/BrowserSync/browser-sync

src/_handlers.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import type * as express from 'express';
2-
import type { Options } from './types';
1+
import type { Options, Request, Response } from './types';
32
import type * as httpProxy from 'http-proxy';
43
import { getInstance } from './logger';
54
const logger = getInstance();
@@ -53,7 +52,7 @@ export function getHandlers(options: Options) {
5352
return handlers;
5453
}
5554

56-
function defaultErrorHandler(err, req: express.Request, res: express.Response) {
55+
function defaultErrorHandler(err, req: Request, res: Response) {
5756
// Re-throw error. Not recoverable since req & res are empty.
5857
if (!req && !res) {
5958
throw err; // "Error: Must provide a proper URL as target"

src/handlers/fix-request-body.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import type * as http from 'http';
2+
import type * as express from 'express';
23
import type { Request } from '../types';
34
import * as querystring from 'querystring';
45

56
/**
67
* Fix proxied body if bodyParser is involved.
78
*/
8-
export function fixRequestBody(proxyReq: http.ClientRequest, req: http.IncomingMessage): void {
9-
const requestBody = (req as Request).body;
9+
export function fixRequestBody(proxyReq: http.ClientRequest, req: Request): void {
10+
const requestBody = (req as Request<express.Request>).body;
1011

1112
if (!requestBody) {
1213
return;

src/http-proxy-middleware.ts

+10-11
Original file line numberDiff line numberDiff line change
@@ -35,28 +35,24 @@ export class HttpProxyMiddleware {
3535

3636
// https://github.com/chimurai/http-proxy-middleware/issues/19
3737
// expose function to upgrade externally
38-
(this.middleware as any).upgrade = (req, socket, head) => {
38+
this.middleware.upgrade = (req, socket, head) => {
3939
if (!this.wsInternalSubscribed) {
4040
this.handleUpgrade(req, socket, head);
4141
}
4242
};
4343
}
4444

4545
// https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript#red-flags-for-this
46-
public middleware: RequestHandler = async (
47-
req: Request,
48-
res: Response,
49-
next: express.NextFunction
50-
) => {
46+
public middleware: RequestHandler = async (req, res, next?) => {
5147
if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
5248
try {
5349
const activeProxyOptions = await this.prepareProxyRequest(req);
5450
this.proxy.web(req, res, activeProxyOptions);
5551
} catch (err) {
56-
next(err);
52+
next && next(err);
5753
}
5854
} else {
59-
next();
55+
next && next();
6056
}
6157

6258
/**
@@ -104,7 +100,7 @@ export class HttpProxyMiddleware {
104100
* Determine whether request should be proxied.
105101
*/
106102
private shouldProxy = (pathFilter: Filter, req: Request): boolean => {
107-
const path = req.originalUrl || req.url;
103+
const path = (req as Request<express.Request>).originalUrl || req.url;
108104
return matchPathFilter(pathFilter, path, req);
109105
};
110106

@@ -119,7 +115,7 @@ export class HttpProxyMiddleware {
119115
private prepareProxyRequest = async (req: Request) => {
120116
// https://github.com/chimurai/http-proxy-middleware/issues/17
121117
// https://github.com/chimurai/http-proxy-middleware/issues/94
122-
req.url = req.originalUrl || req.url;
118+
req.url = (req as Request<express.Request>).originalUrl || req.url;
123119

124120
// store uri before it gets rewritten for logging
125121
const originalPath = req.url;
@@ -179,7 +175,10 @@ export class HttpProxyMiddleware {
179175
};
180176

181177
private logError = (err, req: Request, res: Response, target?) => {
182-
const hostname = req.headers?.host || req.hostname || req.host; // (websocket) || (node0.10 || node 4/5)
178+
const hostname =
179+
req.headers?.host ||
180+
(req as Request<express.Request>).hostname ||
181+
(req as Request<express.Request>).host; // (websocket) || (node0.10 || node 4/5)
183182
const requestHref = `${hostname}${req.url}`;
184183
const targetHref = `${target?.href}`; // target is undefined when websocket errors
185184

src/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { HttpProxyMiddleware } from './http-proxy-middleware';
2-
import { Options } from './types';
2+
import type { Options, RequestHandler } from './types';
33

4-
export function createProxyMiddleware(options: Options) {
4+
export function createProxyMiddleware(options: Options): RequestHandler {
55
const { middleware } = new HttpProxyMiddleware(options);
66
return middleware;
77
}
@@ -15,4 +15,4 @@ export function createProxyMiddleware(options: Options) {
1515

1616
export * from './handlers';
1717

18-
export { Filter, Options, RequestHandler } from './types';
18+
export type { Filter, Options, RequestHandler } from './types';

src/types.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@
55

66
/* eslint-disable @typescript-eslint/no-empty-interface */
77

8-
import type * as express from 'express';
98
import type * as http from 'http';
109
import type * as httpProxy from 'http-proxy';
1110
import type * as net from 'net';
1211
import type * as url from 'url';
1312

14-
export interface Request extends express.Request {}
15-
export interface Response extends express.Response {}
13+
export type Request<T = http.IncomingMessage> = T;
14+
export type Response<T = http.ServerResponse> = T;
15+
export type NextFunction<T = (err?: any) => void> = T;
1616

17-
export interface RequestHandler extends express.RequestHandler {
17+
export interface RequestHandler {
18+
(req: Request, res: Response, next?: NextFunction): void | Promise<void>;
1819
upgrade?: (req: Request, socket: net.Socket, head: any) => void;
1920
}
2021

test/e2e/http-proxy-middleware.spec.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { createProxyMiddleware, createApp, createAppWithPath, fixRequestBody } from './test-kit';
22
import * as request from 'supertest';
33
import { Mockttp, getLocal, CompletedRequest } from 'mockttp';
4-
import { Request, Response } from '../../src/types';
5-
import { NextFunction } from 'express';
4+
import type { Request, Response } from '../../src/types';
5+
import type * as express from 'express';
66
import * as bodyParser from 'body-parser';
77

88
describe('E2E http-proxy-middleware', () => {
@@ -18,9 +18,12 @@ describe('E2E http-proxy-middleware', () => {
1818

1919
describe('pathFilter matching', () => {
2020
describe('do not proxy', () => {
21-
const mockReq: Request = { url: '/foo/bar', originalUrl: '/foo/bar' } as Request;
21+
const mockReq: Request<express.Request> = {
22+
url: '/foo/bar',
23+
originalUrl: '/foo/bar',
24+
} as Request<express.Request>;
2225
const mockRes: Response = {} as Response;
23-
const mockNext: NextFunction = jest.fn();
26+
const mockNext: express.NextFunction = jest.fn();
2427

2528
beforeEach(() => {
2629
const middleware = createProxyMiddleware({

test/e2e/http-server.spec.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as http from 'http';
2+
import { createProxyMiddleware } from './test-kit';
3+
import * as request from 'supertest';
4+
5+
describe('http integration', () => {
6+
it('should work with raw node http RequestHandler', async () => {
7+
const handler = createProxyMiddleware({
8+
changeOrigin: true,
9+
logLevel: 'silent',
10+
target: 'http://httpbin.org',
11+
});
12+
13+
const server = http.createServer(handler);
14+
const response = await request(server).get('/get').expect(200);
15+
16+
expect(response.ok).toBe(true);
17+
expect(response.body.url).toBe('http://httpbin.org/get');
18+
});
19+
});

test/types.spec.ts

+17-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
/* eslint-disable @typescript-eslint/no-empty-function */
2+
3+
import * as http from 'http';
14
import { createProxyMiddleware as middleware } from '../src';
2-
import { Options } from '../src/types';
5+
import type { Options } from '../src/types';
36

47
describe('http-proxy-middleware TypeScript Types', () => {
58
let options: Options;
@@ -10,9 +13,19 @@ describe('http-proxy-middleware TypeScript Types', () => {
1013
};
1114
});
1215

13-
it('should create proxy with just options', () => {
14-
const proxy = middleware(options);
15-
expect(proxy).toBeDefined();
16+
describe('createProxyMiddleware()', () => {
17+
it('should create proxy with just options', () => {
18+
const proxy = middleware(options);
19+
expect(proxy).toBeDefined();
20+
});
21+
22+
it('should create proxy and accept base http types (req, res) from native http server', () => {
23+
const proxy = middleware(options);
24+
const server = http.createServer(proxy);
25+
26+
expect(proxy).toBeDefined();
27+
expect(server).toBeDefined();
28+
});
1629
});
1730

1831
describe('HPM Filters', () => {
@@ -118,39 +131,33 @@ describe('http-proxy-middleware TypeScript Types', () => {
118131

119132
describe('HPM http-proxy events', () => {
120133
it('should have onError type', () => {
121-
// eslint-disable-next-line @typescript-eslint/no-empty-function
122134
options = { onError: (err, req, res) => {} };
123135
expect(options).toBeDefined();
124136
});
125137

126138
it('should have onProxyReq type', () => {
127-
// eslint-disable-next-line @typescript-eslint/no-empty-function
128139
options = { onProxyReq: (proxyReq, req, res) => {} };
129140
expect(options).toBeDefined();
130141
});
131142

132143
it('should have onProxyRes type', () => {
133-
// eslint-disable-next-line @typescript-eslint/no-empty-function
134144
options = { onProxyRes: (proxyRes, req, res) => {} };
135145
expect(options).toBeDefined();
136146
});
137147

138148
it('should have onProxyReqWs type', () => {
139149
options = {
140-
// eslint-disable-next-line @typescript-eslint/no-empty-function
141150
onProxyReqWs: (proxyReq, req, socket, opts, head) => {},
142151
};
143152
expect(options).toBeDefined();
144153
});
145154

146155
it('should have onOpen type', () => {
147-
// eslint-disable-next-line @typescript-eslint/no-empty-function
148156
options = { onOpen: (proxySocket) => {} };
149157
expect(options).toBeDefined();
150158
});
151159

152160
it('should have onClose type', () => {
153-
// eslint-disable-next-line @typescript-eslint/no-empty-function
154161
options = { onClose: (res, socket, head) => {} };
155162
expect(options).toBeDefined();
156163
});

test/unit/fix-request-body.spec.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ClientRequest } from 'http';
22
import * as querystring from 'querystring';
33

44
import { fixRequestBody } from '../../src/handlers/fix-request-body';
5+
import type * as express from 'express';
56
import type { Request } from '../../src/types';
67

78
const fakeProxyRequest = () => {
@@ -18,7 +19,7 @@ describe('fixRequestBody', () => {
1819
jest.spyOn(proxyRequest, 'setHeader');
1920
jest.spyOn(proxyRequest, 'write');
2021

21-
fixRequestBody(proxyRequest, { body: undefined } as Request);
22+
fixRequestBody(proxyRequest, { body: undefined } as Request<express.Request>);
2223

2324
expect(proxyRequest.setHeader).not.toHaveBeenCalled();
2425
expect(proxyRequest.write).not.toHaveBeenCalled();
@@ -31,7 +32,7 @@ describe('fixRequestBody', () => {
3132
jest.spyOn(proxyRequest, 'setHeader');
3233
jest.spyOn(proxyRequest, 'write');
3334

34-
fixRequestBody(proxyRequest, { body: {} } as Request);
35+
fixRequestBody(proxyRequest, { body: {} } as Request<express.Request>);
3536

3637
expect(proxyRequest.setHeader).toHaveBeenCalled();
3738
expect(proxyRequest.write).toHaveBeenCalled();
@@ -44,7 +45,7 @@ describe('fixRequestBody', () => {
4445
jest.spyOn(proxyRequest, 'setHeader');
4546
jest.spyOn(proxyRequest, 'write');
4647

47-
fixRequestBody(proxyRequest, { body: { someField: 'some value' } } as Request);
48+
fixRequestBody(proxyRequest, { body: { someField: 'some value' } } as Request<express.Request>);
4849

4950
const expectedBody = JSON.stringify({ someField: 'some value' });
5051
expect(proxyRequest.setHeader).toHaveBeenCalledWith('Content-Length', expectedBody.length);
@@ -58,7 +59,7 @@ describe('fixRequestBody', () => {
5859
jest.spyOn(proxyRequest, 'setHeader');
5960
jest.spyOn(proxyRequest, 'write');
6061

61-
fixRequestBody(proxyRequest, { body: { someField: 'some value' } } as Request);
62+
fixRequestBody(proxyRequest, { body: { someField: 'some value' } } as Request<express.Request>);
6263

6364
const expectedBody = querystring.stringify({ someField: 'some value' });
6465
expect(proxyRequest.setHeader).toHaveBeenCalledWith('Content-Length', expectedBody.length);

0 commit comments

Comments
 (0)