forked from remix-run/blues-stack
-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.ts
138 lines (114 loc) · 4.24 KB
/
server.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import path from "path";
import express from "express";
import compression from "compression";
import morgan from "morgan";
import { createRequestHandler } from "@remix-run/express";
import prometheusMiddleware from "express-prometheus-middleware";
const app = express();
if (process.env.ENABLE_METRICS) {
const metricsApp = express();
app.use(
prometheusMiddleware({
metricsPath: "/metrics",
metricsApp: metricsApp,
collectDefaultMetrics: true,
requestDurationBuckets: [0.1, 0.5, 1, 1.5],
requestLengthBuckets: [512, 1024, 5120, 10240, 51200, 102400],
responseLengthBuckets: [512, 1024, 5120, 10240, 51200, 102400],
customLabels: ["region", "app", "instance"],
transformLabels: (labels) => {
// region: short 3 letter airport code for the region
labels.region = process.env.FLY_REGION || "unknown";
// app: the app exposing these metrics
labels.app = process.env.FLY_APP_NAME || "unknown";
// instance: your app instance ID
labels.instance = process.env.FLY_ALLOC_ID || "unknown";
},
})
);
const metricsPort = process.env.METRICS_PORT || 9091;
metricsApp.listen(metricsPort, () => {
console.log(`✅ metrics ready: http://localhost:${metricsPort}`);
});
}
app.use((req, res, next) => {
// helpful headers:
res.set("x-fly-region", process.env.FLY_REGION ?? "unknown");
res.set("Strict-Transport-Security", `max-age=${60 * 60 * 24 * 365 * 100}`);
// /clean-urls/ -> /clean-urls
if (req.path.endsWith("/") && req.path.length > 1) {
const query = req.url.slice(req.path.length);
const safepath = req.path.slice(0, -1).replace(/\/+/g, "/");
res.redirect(301, safepath + query);
return;
}
next();
});
// if we're not in the primary region, then we need to make sure all
// non-GET/HEAD/OPTIONS requests hit the primary region rather than read-only
// Postgres DBs.
// learn more: https://fly.io/docs/getting-started/multi-region-databases/#replay-the-request
app.all("*", function getReplayResponse(req, res, next) {
const { method, path: pathname } = req;
const { PRIMARY_REGION, FLY_REGION } = process.env;
const isMethodReplayable = !["GET", "OPTIONS", "HEAD"].includes(method);
const isReadOnlyRegion =
FLY_REGION && PRIMARY_REGION && FLY_REGION !== PRIMARY_REGION;
const shouldReplay = isMethodReplayable && isReadOnlyRegion;
if (!shouldReplay) return next();
const logInfo = {
pathname,
method,
PRIMARY_REGION,
FLY_REGION,
};
console.info(`Replaying:`, logInfo);
res.set("fly-replay", `region=${PRIMARY_REGION}`);
return res.sendStatus(409);
});
app.use(compression());
// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
app.disable("x-powered-by");
// Remix fingerprints its assets so we can cache forever.
app.use(
"/build",
express.static("public/build", { immutable: true, maxAge: "1y" })
);
// Everything else (like favicon.ico) is cached for an hour. You may want to be
// more aggressive with this caching.
app.use(express.static("public", { maxAge: "1h" }));
app.use(morgan("tiny"));
const MODE = process.env.NODE_ENV;
const BUILD_DIR = path.join(process.cwd(), "build");
app.all(
"*",
MODE === "production"
? createRequestHandler({ build: require(BUILD_DIR) })
: (...args) => {
purgeRequireCache();
const requestHandler = createRequestHandler({
build: require(BUILD_DIR),
mode: MODE,
});
return requestHandler(...args);
}
);
const port = process.env.PORT || 3000;
app.listen(port, () => {
// require the built app so we're ready when the first request comes in
require(BUILD_DIR);
console.log(`✅ app ready: http://localhost:${port}`);
});
function purgeRequireCache() {
// purge require cache on requests for "server side HMR" this won't let
// you have in-memory objects between requests in development,
// alternatively you can set up nodemon/pm2-dev to restart the server on
// file changes, we prefer the DX of this though, so we've included it
// for you by default
for (const key in require.cache) {
if (key.startsWith(BUILD_DIR)) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete require.cache[key];
}
}
}