Skip to content

Instantly share code, notes, and snippets.

@zayniddindev
Last active May 12, 2025 06:00
Show Gist options
  • Select an option

  • Save zayniddindev/9d50836d9585296aec5c897d4e362432 to your computer and use it in GitHub Desktop.

Select an option

Save zayniddindev/9d50836d9585296aec5c897d4e362432 to your computer and use it in GitHub Desktop.
Runner for Node.js backend and React frontend
import { Context } from "./context";
import * as chalk from "chalk";
import * as Table from "cli-table3";
import * as ngrok from "@ngrok/ngrok";
import * as webpack from "webpack";
import * as WebpackDevServer from "webpack-dev-server";
import * as nodemon from "nodemon";
export const infoChalk = chalk.blue.bold;
export const warnChalk = chalk.bgYellow.bold;
export const errorChalk = chalk.bgRed.bold;
export const highlightChalk = chalk.greenBright.bold;
export const linkChalk = chalk.cyan;
export class AppRunner {
async run(ctx: Context, webpackBuildConfig: () => webpack.Configuration) {
console.log(
infoChalk("Info:"),
`Starting development server for ${highlightChalk(ctx.srcDir)}\n`,
);
const table = new Table();
const server = await this.runWebpackDevServer(
ctx,
table,
webpackBuildConfig,
);
await this.runBackendServer(ctx, table, server);
console.log(table.toString(), "\n");
}
private readonly runBackendServer = async (
ctx: Context,
table: Table.Table,
webpackDevServer: WebpackDevServer,
) => {
await new Promise((resolve) => {
const nodemonServer = nodemon({
script: ctx.backendEntry,
ext: "ts",
});
nodemonServer.on("start", resolve);
nodemonServer.on("crash", async () => {
console.log(errorChalk("Shutting down local server.\n"));
await webpackDevServer.stop();
process.exit(1);
});
});
if (ctx.ngrokEnabled) {
console.log(
warnChalk("Warning:"),
`ngrok exposes a local port via a public URL. Be mindful of what's exposed and shut down the server when it's not in use.\n`,
);
}
let url = ctx.backendUrl;
if (ctx.ngrokEnabled) {
try {
const ngrokListener = await ngrok.forward({
addr: ctx.backendPort,
// requires an `NGROK_AUTHTOKEN` env var to be set
authtoken_from_env: true,
});
url = ngrokListener.url() ?? url;
} catch (err) {
console.log(
errorChalk("Error:"),
`Unable to start ngrok server: ${err}`,
);
}
}
table.push(["Base URL (Backend)", linkChalk(url)]);
};
private readonly runWebpackDevServer = async (
ctx: Context,
table: Table.Table,
buildConfig: () => webpack.Configuration,
): Promise<WebpackDevServer> => {
const runtimeWebpackConfig = buildConfig();
const compiler = webpack(runtimeWebpackConfig);
const server = new WebpackDevServer(
runtimeWebpackConfig.devServer,
compiler,
);
await server.start();
table.push(["Development URL (Frontend)", linkChalk(ctx.frontendUrl)]);
return server;
};
}
import * as fs from "fs";
import * as path from "path";
type CliArgs = {
example?: string;
ngrok?: boolean;
};
export class Context {
constructor(
private readonly args: CliArgs,
private readonly rootDir: string,
) {}
get srcDir() {
const src = path.join(this.rootDir, "frontend", "src");
if (!fs.existsSync(src)) {
throw new Error(`Directory does not exist: ${src}`);
}
return src;
}
get backendEntry() {
const index = path.join(this.rootDir, "backend", "index.ts");
if (!fs.existsSync(index)) {
throw new Error(`backend/index.ts does not exist: ${index}`);
}
return index;
}
get ngrokEnabled() {
return !!this.args.ngrok;
}
get frontendUrl(): string {
return process.env.FRONTEND_URL!;
}
get backendUrl(): string {
return process.env.BACKEND_URL!;
}
get backendPort(): string {
return process.env.BACKEND_PORT!;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment