tangled
alpha
login
or
join now
graham.systems
/
statusphere-react
forked from
samuel.fm/statusphere-react
0
fork
atom
the statusphere demo reworked into a vite/react app in a monorepo
0
fork
atom
overview
issues
pulls
pipelines
more tidy
dholms
2 years ago
19149b18
d6ebb4ef
+39
-134
7 changed files
expand all
collapse all
unified
split
src
common
__tests__
errorHandler.test.ts
requestLogger.test.ts
middleware
rateLimiter.ts
router.ts
routes
index.ts
util.ts
server.ts
-45
src/common/__tests__/errorHandler.test.ts
···
1
1
-
import express, { type Express } from "express";
2
2
-
import { StatusCodes } from "http-status-codes";
3
3
-
import request from "supertest";
4
4
-
5
5
-
import errorHandler from "#/common/middleware/errorHandler";
6
6
-
7
7
-
describe("Error Handler Middleware", () => {
8
8
-
let app: Express;
9
9
-
10
10
-
beforeAll(() => {
11
11
-
app = express();
12
12
-
13
13
-
app.get("/error", () => {
14
14
-
throw new Error("Test error");
15
15
-
});
16
16
-
app.get("/next-error", (_req, _res, next) => {
17
17
-
const error = new Error("Error passed to next()");
18
18
-
next(error);
19
19
-
});
20
20
-
21
21
-
app.use(errorHandler());
22
22
-
app.use("*", (req, res) => res.status(StatusCodes.NOT_FOUND).send("Not Found"));
23
23
-
});
24
24
-
25
25
-
describe("Handling unknown routes", () => {
26
26
-
it("returns 404 for unknown routes", async () => {
27
27
-
const response = await request(app).get("/this-route-does-not-exist");
28
28
-
expect(response.status).toBe(StatusCodes.NOT_FOUND);
29
29
-
});
30
30
-
});
31
31
-
32
32
-
describe("Handling thrown errors", () => {
33
33
-
it("handles thrown errors with a 500 status code", async () => {
34
34
-
const response = await request(app).get("/error");
35
35
-
expect(response.status).toBe(StatusCodes.INTERNAL_SERVER_ERROR);
36
36
-
});
37
37
-
});
38
38
-
39
39
-
describe("Handling errors passed to next()", () => {
40
40
-
it("handles errors passed to next() with a 500 status code", async () => {
41
41
-
const response = await request(app).get("/next-error");
42
42
-
expect(response.status).toBe(StatusCodes.INTERNAL_SERVER_ERROR);
43
43
-
});
44
44
-
});
45
45
-
});
-52
src/common/__tests__/requestLogger.test.ts
···
1
1
-
import express from "express";
2
2
-
import { StatusCodes } from "http-status-codes";
3
3
-
import request from "supertest";
4
4
-
5
5
-
import errorHandler from "#/common/middleware/errorHandler";
6
6
-
import requestLogger from "#/common/middleware/requestLogger";
7
7
-
8
8
-
describe("Request Logger Middleware", () => {
9
9
-
const app = express();
10
10
-
11
11
-
beforeAll(() => {
12
12
-
app.use(requestLogger);
13
13
-
app.get("/success", (req, res) => res.status(StatusCodes.OK).send("Success"));
14
14
-
app.get("/redirect", (req, res) => res.redirect("/success"));
15
15
-
app.get("/error", () => {
16
16
-
throw new Error("Test error");
17
17
-
});
18
18
-
app.use(errorHandler());
19
19
-
});
20
20
-
21
21
-
describe("Successful requests", () => {
22
22
-
it("logs successful requests", async () => {
23
23
-
const response = await request(app).get("/success");
24
24
-
expect(response.status).toBe(StatusCodes.OK);
25
25
-
});
26
26
-
27
27
-
it("checks existing request id", async () => {
28
28
-
const requestId = "test-request-id";
29
29
-
const response = await request(app).get("/success").set("X-Request-Id", requestId);
30
30
-
expect(response.status).toBe(StatusCodes.OK);
31
31
-
});
32
32
-
});
33
33
-
34
34
-
describe("Re-directions", () => {
35
35
-
it("logs re-directions correctly", async () => {
36
36
-
const response = await request(app).get("/redirect");
37
37
-
expect(response.status).toBe(StatusCodes.MOVED_TEMPORARILY);
38
38
-
});
39
39
-
});
40
40
-
41
41
-
describe("Error handling", () => {
42
42
-
it("logs thrown errors with a 500 status code", async () => {
43
43
-
const response = await request(app).get("/error");
44
44
-
expect(response.status).toBe(StatusCodes.INTERNAL_SERVER_ERROR);
45
45
-
});
46
46
-
47
47
-
it("logs 404 for unknown routes", async () => {
48
48
-
const response = await request(app).get("/unknown");
49
49
-
expect(response.status).toBe(StatusCodes.NOT_FOUND);
50
50
-
});
51
51
-
});
52
52
-
});
-15
src/common/middleware/rateLimiter.ts
···
1
1
-
import type { Request } from "express";
2
2
-
import { rateLimit } from "express-rate-limit";
3
3
-
4
4
-
import { env } from "#/common/utils/envConfig";
5
5
-
6
6
-
const rateLimiter = rateLimit({
7
7
-
legacyHeaders: true,
8
8
-
limit: env.COMMON_RATE_LIMIT_MAX_REQUESTS,
9
9
-
message: "Too many requests, please try again later.",
10
10
-
standardHeaders: true,
11
11
-
windowMs: 15 * 60 * env.COMMON_RATE_LIMIT_WINDOW_MS,
12
12
-
keyGenerator: (req: Request) => req.ip as string,
13
13
-
});
14
14
-
15
15
-
export default rateLimiter;
-19
src/router.ts
···
1
1
-
import express from "express";
2
2
-
import type { AppContext } from "#/config";
3
3
-
4
4
-
export const createRouter = (ctx: AppContext) => {
5
5
-
const router = express.Router();
6
6
-
7
7
-
router.get("/", async (req, res) => {
8
8
-
const posts = await ctx.db
9
9
-
.selectFrom("post")
10
10
-
.selectAll()
11
11
-
.orderBy("indexedAt", "desc")
12
12
-
.limit(10)
13
13
-
.execute();
14
14
-
const postTexts = posts.map((row) => row.text);
15
15
-
res.json(postTexts);
16
16
-
});
17
17
-
18
18
-
return router;
19
19
-
};
+23
src/routes/index.ts
···
1
1
+
import express from "express";
2
2
+
import type { AppContext } from "#/config";
3
3
+
import { handler } from "./util";
4
4
+
5
5
+
export const createRouter = (ctx: AppContext) => {
6
6
+
const router = express.Router();
7
7
+
8
8
+
router.get(
9
9
+
"/",
10
10
+
handler(async (req, res) => {
11
11
+
const posts = await ctx.db
12
12
+
.selectFrom("post")
13
13
+
.selectAll()
14
14
+
.orderBy("indexedAt", "desc")
15
15
+
.limit(10)
16
16
+
.execute();
17
17
+
const postTexts = posts.map((row) => row.text);
18
18
+
res.json(postTexts);
19
19
+
})
20
20
+
);
21
21
+
22
22
+
return router;
23
23
+
};
+15
src/routes/util.ts
···
1
1
+
import type express from "express";
2
2
+
3
3
+
export const handler =
4
4
+
(fn: express.Handler) =>
5
5
+
async (
6
6
+
req: express.Request,
7
7
+
res: express.Response,
8
8
+
next: express.NextFunction
9
9
+
) => {
10
10
+
try {
11
11
+
await fn(req, res, next);
12
12
+
} catch (err) {
13
13
+
next(err);
14
14
+
}
15
15
+
};
+1
-3
src/server.ts
···
6
6
import { pino } from "pino";
7
7
8
8
import errorHandler from "#/common/middleware/errorHandler";
9
9
-
import rateLimiter from "#/common/middleware/rateLimiter";
10
9
import requestLogger from "#/common/middleware/requestLogger";
11
10
import { env } from "#/common/utils/envConfig";
12
11
import { createDb, migrateToLatest } from "#/db";
13
12
import { Firehose } from "#/firehose";
14
14
-
import { createRouter } from "#/router";
13
13
+
import { createRouter } from "#/routes";
15
14
import type { AppContext } from "./config";
16
15
17
16
export class Server {
···
48
47
app.use(express.urlencoded({ extended: true }));
49
48
app.use(cors({ origin: env.CORS_ORIGIN, credentials: true }));
50
49
app.use(helmet());
51
51
-
app.use(rateLimiter);
52
50
53
51
// Request logging
54
52
app.use(requestLogger);