Skip to content

feat: Setup for option to run as express server #2447

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
3 changes: 2 additions & 1 deletion .vercelignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.env
package-lock.json
coverage
coverage
express
19 changes: 19 additions & 0 deletions express/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM node:18-alpine

# Copy express/index.js, src, themes, vercel.json, package.json, and package-lock.json
COPY api /app/api
COPY express/*.js /app/express/
COPY src /app/
COPY themes /app/
COPY vercel.json /app/
COPY package.json /app/
COPY package-lock.json /app/

# Create app directory
WORKDIR /app

# Install app dependencies
RUN npm ci --omit dev --ignore-scripts --no-audit

# Run the app
CMD [ "node", "express/index.js" ]
126 changes: 126 additions & 0 deletions express/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* @file A simple express server variant of GRS can run GRS in a docker container.
* This is not intended to be used in the development; please use the `vercel dev`
* command instead.
*/
import { readFileSync } from "fs";
import http from "http";
import https from "https";
import glob from "glob";
import express from "express";
import helmet from "helmet";
import morgan from "morgan";

import { getEnv } from "../src/common/utils.js";

const ENVS = {
PORT: "PORT",
HTTPS_PORT: "HTTPS_PORT",
REDIRECT_HTTPS: "REDIRECT_HTTPS",
HTTPS_KEY: "HTTPS_KEY",
HTTPS_CERT: "HTTPS_CERT",
USE_HELMET: "USE_HELMET",
USE_MORGAN: "USE_MORGAN",
MORGAN_FORMAT: "MORGAN_FORMAT",
}

const vercelConfig = JSON.parse(readFileSync("./vercel.json", "utf8"));
const vercelFunctions = Object.keys(vercelConfig.functions);

const app = express();

const files = vercelFunctions.map((gl) => glob.sync(gl)).flat();
await loadApiRoutes(app, files);

const httpPort = getEnv(ENVS.PORT, 3000);
const httpsPort = getEnv(ENVS.HTTPS_PORT, 3443);

if (
getEnv(ENVS.REDIRECT_HTTPS, "true") === "true"
&& getEnv(ENVS.HTTPS_KEY)
&& getEnv(ENVS.HTTPS_CERT)
) {
app.use("*", redirectHttps);
}

if (getEnv(ENVS.USE_HELMET, "true") === "true") {
app.use(helmet());
}

if (getEnv(ENVS.USE_MORGAN, "true") === "true") {
app.use(morgan(getEnv(ENVS.MORGAN_FORMAT, "combined")));
}

const server = new Server(app)

server.start(
{ http: httpPort, https: httpsPort },
{
http: () => console.log(`HTTP server listening on port ${httpPort}`),
https: () => console.log(`HTTPS server listening on port ${httpsPort}`),
}
);

/**
* Loads all api routes from the given paths.
* @param {string[]} files Api endpoint files.
*/
async function loadApiRoutes(app, files) {
for (const file of files) {
const route = `/${file.replace(".js", "").replace("index", "")}`;
console.log(`Loading route '${route}' with handler from '${file}'`);
const handler = await import(`../${file}`);
app.get(route, handler.default);
}
}

/**
* A simple express middleware to redirect all HTTP requests to HTTPS.
* @param {import("express").Request} req
* @param {import("express").Response} res
* @param {import("express").NextFunction} next
* @returns {void}
*/
function redirectHttps(req, res, next) {
if (req.secure) {
next();
} else {
res.redirect(`https://${req.hostname}${req.url}`);
}
}

/**
* Simple wrapper around `http` and `https` node module.
* @returns {{ start: (options: { http: number, https: number }, cb: { http: () => void, https: () => void }) => void }}
*/
function Server (app) {
const tls = loadTLS();
if (tls) {
this.https = https.createServer(tls, app);
}
this.http = http.createServer(app);
this.start = function (port, callback) {
const result = {};
if (this.https && port.https) {
this.https = this.https.listen(port.https, callback.https);
}
this.http = this.http.listen(port.http, callback.http);
return result;
}
}

/**
* Attempts to loads the SSL/TLS certificate and key from the optional
* environment variables `HTTPS_CERT` and `HTTPS_KEY`.
* @returns {{
* cert: typeof import("buffer").Buffer,
* key: typeof import("buffer").Buffer
* }} The certificate and key.
*/
function loadTLS() {
if (!getEnv(ENVS.HTTPS_KEY) || !getEnv(ENVS.HTTPS_CERT)) return null
return {
key: readFileSync(getEnv(ENVS.HTTPS_KEY)),
cert: readFileSync(getEnv(ENVS.HTTPS_CERT)),
}
}
Loading