Skip to content

Commit c37b09f

Browse files
authored
Fix paths for routes within Express routers (#45)
* Fix paths for routes within Express routers * Fix
1 parent 295ab34 commit c37b09f

File tree

4 files changed

+107
-12
lines changed

4 files changed

+107
-12
lines changed

src/express/listEndpoints.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,14 +216,14 @@ const parseStack = function (stack, basePath, endpoints) {
216216
return endpoints;
217217
};
218218

219-
const getEndpoints = function (app) {
219+
const getEndpoints = function (app, basePath) {
220220
const endpoints = parseEndpoints(app);
221221
return endpoints.flatMap((route) =>
222222
route.methods
223223
.filter((method) => !["HEAD", "OPTIONS"].includes(method.toUpperCase()))
224224
.map((method) => ({
225225
method,
226-
path: route.path,
226+
path: basePath + route.path,
227227
})),
228228
);
229229
};

src/express/middleware.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import type { Express, NextFunction, Request, Response, Router } from "express";
1+
import type { Express, NextFunction, Request, Response } from "express";
2+
import { Router } from "express";
3+
import type { ILayer } from "express-serve-static-core";
24
import { performance } from "perf_hooks";
35

46
import { ApitallyClient } from "../common/client.js";
@@ -19,12 +21,15 @@ declare module "express" {
1921
}
2022
}
2123

22-
export const useApitally = (app: Express | Router, config: ApitallyConfig) => {
24+
export const useApitally = (
25+
app: Express | Router,
26+
config: ApitallyConfig & { basePath?: string },
27+
) => {
2328
const client = new ApitallyClient(config);
2429
const middleware = getMiddleware(app, client);
2530
app.use(middleware);
2631
setTimeout(() => {
27-
client.setStartupData(getAppInfo(app, config.appVersion));
32+
client.setStartupData(getAppInfo(app, config.basePath, config.appVersion));
2833
}, 1000);
2934
};
3035

@@ -55,14 +60,15 @@ const getMiddleware = (app: Express | Router, client: ApitallyClient) => {
5560
};
5661
res.on("finish", () => {
5762
try {
58-
if (req.route) {
63+
const path = getRoutePath(req);
64+
if (path) {
5965
const responseTime = performance.now() - startTime;
6066
const consumer = getConsumer(req);
6167
client.consumerRegistry.addOrUpdateConsumer(consumer);
6268
client.requestCounter.addRequest({
6369
consumer: consumer?.identifier,
6470
method: req.method,
65-
path: req.route.path,
71+
path,
6672
statusCode: res.statusCode,
6773
responseTime: responseTime,
6874
requestSize: req.get("content-length"),
@@ -128,6 +134,25 @@ const getMiddleware = (app: Express | Router, client: ApitallyClient) => {
128134
};
129135
};
130136

137+
const getRoutePath = (req: Request) => {
138+
if (!req.route) {
139+
return;
140+
}
141+
if (req.baseUrl) {
142+
const router = req.app._router.stack.findLast((layer: ILayer) => {
143+
return layer.name === "router" && layer.regexp.test(req.baseUrl);
144+
});
145+
if (router && router.path) {
146+
if (Object.keys(router.params).length > 0) {
147+
// Routers mounted with path parameters are not supported yet
148+
return;
149+
}
150+
return router.path + req.route.path;
151+
}
152+
}
153+
return req.route.path;
154+
};
155+
131156
const getConsumer = (req: Request) => {
132157
if (req.apitallyConsumer) {
133158
return consumerFromStringOrObject(req.apitallyConsumer);
@@ -208,6 +233,7 @@ const subsetJoiMessage = (message: string, key: string) => {
208233

209234
const getAppInfo = (
210235
app: Express | Router,
236+
basePath?: string,
211237
appVersion?: string,
212238
): StartupData => {
213239
const versions: Array<[string, string]> = [
@@ -225,7 +251,7 @@ const getAppInfo = (
225251
versions.push(["app", appVersion]);
226252
}
227253
return {
228-
paths: listEndpoints(app),
254+
paths: listEndpoints(app, basePath || ""),
229255
versions: Object.fromEntries(versions),
230256
client: "js:express",
231257
};

tests/express/app.test.ts

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ApitallyClient } from "../../src/common/client.js";
66
import { mockApitallyHub } from "../utils.js";
77
import {
88
getAppWithCelebrate,
9+
getAppWithMiddlewareOnRouter,
910
getAppWithRouter,
1011
getAppWithValidator,
1112
} from "./app.js";
@@ -137,7 +138,7 @@ describe("Middleware for Express router", () => {
137138

138139
beforeEach(async () => {
139140
mockApitallyHub();
140-
app = getAppWithRouter();
141+
app = getAppWithMiddlewareOnRouter();
141142
appTest = request(app);
142143
client = ApitallyClient.getInstance();
143144

@@ -153,7 +154,55 @@ describe("Middleware for Express router", () => {
153154
expect(
154155
requests.some(
155156
(r) =>
156-
r.method === "GET" && r.path === "/hello" && r.status_code === 200,
157+
r.method === "GET" &&
158+
r.path === "/api/hello" &&
159+
r.status_code === 200,
160+
),
161+
).toBe(true);
162+
});
163+
164+
it("List endpoints", async () => {
165+
expect(client.startupData?.paths).toEqual([
166+
{
167+
method: "GET",
168+
path: "/api/hello",
169+
},
170+
]);
171+
});
172+
173+
afterEach(async () => {
174+
if (client) {
175+
await client.handleShutdown();
176+
}
177+
});
178+
});
179+
180+
describe("Middleware for Express with router", () => {
181+
let app: Express;
182+
let appTest: request.Agent;
183+
let client: ApitallyClient;
184+
185+
beforeEach(async () => {
186+
mockApitallyHub();
187+
app = getAppWithRouter();
188+
appTest = request(app);
189+
client = ApitallyClient.getInstance();
190+
191+
// Wait for 1.2 seconds for startup data to be set
192+
await new Promise((resolve) => setTimeout(resolve, 1200));
193+
});
194+
195+
it("Request logger", async () => {
196+
await appTest.get("/api/hello/bob").expect(200);
197+
198+
const requests = client.requestCounter.getAndResetRequests();
199+
expect(requests.length).toBe(1);
200+
expect(
201+
requests.some(
202+
(r) =>
203+
r.method === "GET" &&
204+
r.path === "/api/hello/:name" &&
205+
r.status_code === 200,
157206
),
158207
).toBe(true);
159208
});
@@ -162,7 +211,7 @@ describe("Middleware for Express router", () => {
162211
expect(client.startupData?.paths).toEqual([
163212
{
164213
method: "GET",
165-
path: "/hello",
214+
path: "/api/hello/:name",
166215
},
167216
]);
168217
});

tests/express/app.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,15 @@ export const getAppWithValidator = () => {
108108
return app;
109109
};
110110

111-
export const getAppWithRouter = () => {
111+
export const getAppWithMiddlewareOnRouter = () => {
112112
const app = express();
113113
const router = express.Router();
114114

115115
useApitally(router, {
116116
clientId: CLIENT_ID,
117117
env: ENV,
118118
appVersion: "1.2.3",
119+
basePath: "/api",
119120
});
120121

121122
router.get("/hello", (req, res) => {
@@ -126,3 +127,22 @@ export const getAppWithRouter = () => {
126127
app.use(errors());
127128
return app;
128129
};
130+
131+
export const getAppWithRouter = () => {
132+
const app = express();
133+
const router = express.Router();
134+
135+
useApitally(app, {
136+
clientId: CLIENT_ID,
137+
env: ENV,
138+
appVersion: "1.2.3",
139+
});
140+
141+
router.get("/hello/:name", (req, res) => {
142+
res.send(`Hello ${req.params.name}!`);
143+
});
144+
145+
app.use("/api", router);
146+
app.use(errors());
147+
return app;
148+
};

0 commit comments

Comments
 (0)