Initial commit
This commit is contained in:
32
artifacts/api-server/.replit-artifact/artifact.toml
Normal file
32
artifacts/api-server/.replit-artifact/artifact.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
kind = "api"
|
||||
previewPath = "/api" # TODO - should be excluded from preview in the first place
|
||||
title = "API Server"
|
||||
version = "1.0.0"
|
||||
id = "3B4_FFSkEVBkAeYMFRJ2e"
|
||||
|
||||
[[services]]
|
||||
localPort = 8080
|
||||
name = "API Server"
|
||||
paths = ["/api"]
|
||||
|
||||
[services.development]
|
||||
run = "pnpm --filter @workspace/api-server run dev"
|
||||
|
||||
[services.production]
|
||||
|
||||
[services.production.build]
|
||||
args = ["pnpm", "--filter", "@workspace/api-server", "run", "build"]
|
||||
|
||||
[services.production.build.env]
|
||||
NODE_ENV = "production"
|
||||
|
||||
[services.production.run]
|
||||
# we don't run through pnpm to make startup faster in production
|
||||
args = ["node", "--enable-source-maps", "artifacts/api-server/dist/index.mjs"]
|
||||
|
||||
[services.production.run.env]
|
||||
PORT = "8080"
|
||||
NODE_ENV = "production"
|
||||
|
||||
[services.production.health.startup]
|
||||
path = "/api/healthz"
|
||||
126
artifacts/api-server/build.mjs
Normal file
126
artifacts/api-server/build.mjs
Normal file
@@ -0,0 +1,126 @@
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { build as esbuild } from "esbuild";
|
||||
import esbuildPluginPino from "esbuild-plugin-pino";
|
||||
import { rm } from "node:fs/promises";
|
||||
|
||||
// Plugins (e.g. 'esbuild-plugin-pino') may use `require` to resolve dependencies
|
||||
globalThis.require = createRequire(import.meta.url);
|
||||
|
||||
const artifactDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
async function buildAll() {
|
||||
const distDir = path.resolve(artifactDir, "dist");
|
||||
await rm(distDir, { recursive: true, force: true });
|
||||
|
||||
await esbuild({
|
||||
entryPoints: [path.resolve(artifactDir, "src/index.ts")],
|
||||
platform: "node",
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
outdir: distDir,
|
||||
outExtension: { ".js": ".mjs" },
|
||||
logLevel: "info",
|
||||
// Some packages may not be bundleable, so we externalize them, we can add more here as needed.
|
||||
// Some of the packages below may not be imported or installed, but we're adding them in case they are in the future.
|
||||
// Examples of unbundleable packages:
|
||||
// - uses native modules and loads them dynamically (e.g. sharp)
|
||||
// - use path traversal to read files (e.g. @google-cloud/secret-manager loads sibling .proto files)
|
||||
external: [
|
||||
"*.node",
|
||||
"sharp",
|
||||
"better-sqlite3",
|
||||
"sqlite3",
|
||||
"canvas",
|
||||
"bcrypt",
|
||||
"argon2",
|
||||
"fsevents",
|
||||
"re2",
|
||||
"farmhash",
|
||||
"xxhash-addon",
|
||||
"bufferutil",
|
||||
"utf-8-validate",
|
||||
"ssh2",
|
||||
"cpu-features",
|
||||
"dtrace-provider",
|
||||
"isolated-vm",
|
||||
"lightningcss",
|
||||
"pg-native",
|
||||
"oracledb",
|
||||
"mongodb-client-encryption",
|
||||
"nodemailer",
|
||||
"handlebars",
|
||||
"knex",
|
||||
"typeorm",
|
||||
"protobufjs",
|
||||
"onnxruntime-node",
|
||||
"@tensorflow/*",
|
||||
"@prisma/client",
|
||||
"@mikro-orm/*",
|
||||
"@grpc/*",
|
||||
"@swc/*",
|
||||
"@aws-sdk/*",
|
||||
"@azure/*",
|
||||
"@opentelemetry/*",
|
||||
"@google-cloud/*",
|
||||
"@google/*",
|
||||
"googleapis",
|
||||
"firebase-admin",
|
||||
"@parcel/watcher",
|
||||
"@sentry/profiling-node",
|
||||
"@tree-sitter/*",
|
||||
"aws-sdk",
|
||||
"classic-level",
|
||||
"dd-trace",
|
||||
"ffi-napi",
|
||||
"grpc",
|
||||
"hiredis",
|
||||
"kerberos",
|
||||
"leveldown",
|
||||
"miniflare",
|
||||
"mysql2",
|
||||
"newrelic",
|
||||
"odbc",
|
||||
"piscina",
|
||||
"realm",
|
||||
"ref-napi",
|
||||
"rocksdb",
|
||||
"sass-embedded",
|
||||
"sequelize",
|
||||
"serialport",
|
||||
"snappy",
|
||||
"tinypool",
|
||||
"usb",
|
||||
"workerd",
|
||||
"wrangler",
|
||||
"zeromq",
|
||||
"zeromq-prebuilt",
|
||||
"playwright",
|
||||
"puppeteer",
|
||||
"puppeteer-core",
|
||||
"electron",
|
||||
],
|
||||
sourcemap: "linked",
|
||||
plugins: [
|
||||
// pino relies on workers to handle logging, instead of externalizing it we use a plugin to handle it
|
||||
esbuildPluginPino({ transports: ["pino-pretty"] })
|
||||
],
|
||||
// Make sure packages that are cjs only (e.g. express) but are bundled continue to work in our esm output file
|
||||
banner: {
|
||||
js: `import { createRequire as __bannerCrReq } from 'node:module';
|
||||
import __bannerPath from 'node:path';
|
||||
import __bannerUrl from 'node:url';
|
||||
|
||||
globalThis.require = __bannerCrReq(import.meta.url);
|
||||
globalThis.__filename = __bannerUrl.fileURLToPath(import.meta.url);
|
||||
globalThis.__dirname = __bannerPath.dirname(globalThis.__filename);
|
||||
`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
buildAll().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
32
artifacts/api-server/package.json
Normal file
32
artifacts/api-server/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "@workspace/api-server",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "export NODE_ENV=development && pnpm run build && pnpm run start",
|
||||
"build": "node ./build.mjs",
|
||||
"start": "node --enable-source-maps ./dist/index.mjs",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@workspace/api-zod": "workspace:*",
|
||||
"@workspace/db": "workspace:*",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.6",
|
||||
"drizzle-orm": "catalog:",
|
||||
"express": "^5.2.1",
|
||||
"pino": "^9.14.0",
|
||||
"pino-http": "^10.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cookie-parser": "^1.4.10",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "catalog:",
|
||||
"esbuild": "0.27.3",
|
||||
"esbuild-plugin-pino": "^2.3.3",
|
||||
"pino-pretty": "^13.1.3",
|
||||
"thread-stream": "3.1.0"
|
||||
}
|
||||
}
|
||||
34
artifacts/api-server/src/app.ts
Normal file
34
artifacts/api-server/src/app.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import express, { type Express } from "express";
|
||||
import cors from "cors";
|
||||
import pinoHttp from "pino-http";
|
||||
import router from "./routes";
|
||||
import { logger } from "./lib/logger";
|
||||
|
||||
const app: Express = express();
|
||||
|
||||
app.use(
|
||||
pinoHttp({
|
||||
logger,
|
||||
serializers: {
|
||||
req(req) {
|
||||
return {
|
||||
id: req.id,
|
||||
method: req.method,
|
||||
url: req.url?.split("?")[0],
|
||||
};
|
||||
},
|
||||
res(res) {
|
||||
return {
|
||||
statusCode: res.statusCode,
|
||||
};
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
app.use("/api", router);
|
||||
|
||||
export default app;
|
||||
25
artifacts/api-server/src/index.ts
Normal file
25
artifacts/api-server/src/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import app from "./app";
|
||||
import { logger } from "./lib/logger";
|
||||
|
||||
const rawPort = process.env["PORT"];
|
||||
|
||||
if (!rawPort) {
|
||||
throw new Error(
|
||||
"PORT environment variable is required but was not provided.",
|
||||
);
|
||||
}
|
||||
|
||||
const port = Number(rawPort);
|
||||
|
||||
if (Number.isNaN(port) || port <= 0) {
|
||||
throw new Error(`Invalid PORT value: "${rawPort}"`);
|
||||
}
|
||||
|
||||
app.listen(port, (err) => {
|
||||
if (err) {
|
||||
logger.error({ err }, "Error listening on port");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
logger.info({ port }, "Server listening");
|
||||
});
|
||||
0
artifacts/api-server/src/lib/.gitkeep
Normal file
0
artifacts/api-server/src/lib/.gitkeep
Normal file
20
artifacts/api-server/src/lib/logger.ts
Normal file
20
artifacts/api-server/src/lib/logger.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import pino from "pino";
|
||||
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
|
||||
export const logger = pino({
|
||||
level: process.env.LOG_LEVEL ?? "info",
|
||||
redact: [
|
||||
"req.headers.authorization",
|
||||
"req.headers.cookie",
|
||||
"res.headers['set-cookie']",
|
||||
],
|
||||
...(isProduction
|
||||
? {}
|
||||
: {
|
||||
transport: {
|
||||
target: "pino-pretty",
|
||||
options: { colorize: true },
|
||||
},
|
||||
}),
|
||||
});
|
||||
0
artifacts/api-server/src/middlewares/.gitkeep
Normal file
0
artifacts/api-server/src/middlewares/.gitkeep
Normal file
11
artifacts/api-server/src/routes/health.ts
Normal file
11
artifacts/api-server/src/routes/health.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Router, type IRouter } from "express";
|
||||
import { HealthCheckResponse } from "@workspace/api-zod";
|
||||
|
||||
const router: IRouter = Router();
|
||||
|
||||
router.get("/healthz", (_req, res) => {
|
||||
const data = HealthCheckResponse.parse({ status: "ok" });
|
||||
res.json(data);
|
||||
});
|
||||
|
||||
export default router;
|
||||
8
artifacts/api-server/src/routes/index.ts
Normal file
8
artifacts/api-server/src/routes/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Router, type IRouter } from "express";
|
||||
import healthRouter from "./health";
|
||||
|
||||
const router: IRouter = Router();
|
||||
|
||||
router.use(healthRouter);
|
||||
|
||||
export default router;
|
||||
17
artifacts/api-server/tsconfig.json
Normal file
17
artifacts/api-server/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../lib/db"
|
||||
},
|
||||
{
|
||||
"path": "../../lib/api-zod"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user