This commit is contained in:
2026-02-25 10:56:18 +01:00
commit 3727fc8892
11 changed files with 2469 additions and 0 deletions

14
services/api/Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM node:slim
WORKDIR /app
COPY . .
RUN npm i
# Generate kysely types
RUN npx kysely-codegen
RUN npm run build
CMD ["npm", "run", "start"]

2283
services/api/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

34
services/api/package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "honong",
"version": "1.0.0",
"private": true,
"description": "Honong web app",
"license": "ISC",
"author": "Adam Vo",
"type": "module",
"main": "dist/index.js",
"scripts": {
"dev": "tsx --watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"gen-t": "kysely-codegen"
},
"devDependencies": {
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/morgan": "^1.9.10",
"@types/node": "^25.3.0",
"@types/pg": "^8.16.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
},
"dependencies": {
"cors": "^2.8.6",
"dotenv": "^17.3.1",
"express": "^5.2.1",
"kysely": "^0.28.11",
"kysely-codegen": "^0.20.0",
"morgan": "^1.10.1",
"pg": "^8.18.0"
}
}

35
services/api/src/app.ts Normal file
View File

@@ -0,0 +1,35 @@
import "dotenv/config";
import express from "express";
import db from "./lib/db.js";
import crypto from "crypto";
import morgan from "morgan";
const app = express();
app.use(morgan("dev"));
app.get("/", async (req, res, next) => {
const notes = await db.selectFrom("notes").selectAll().execute();
res.json({ notes });
});
app.post("/", async (req, res, next) => {
const created = await db
.insertInto("notes")
.values({
title: "test",
content: "text",
completed: true,
uuid: crypto.randomUUID(),
created_at: new Date(),
updated_at: new Date(),
completed_at: new Date(),
})
.returningAll()
.executeTakeFirstOrThrow();
res.json({ created });
});
export { app };

10
services/api/src/index.ts Normal file
View File

@@ -0,0 +1,10 @@
import "dotenv/config";
import { app } from "./app.js";
import { shutdown } from "./lib/shutdown.js";
const server = app.listen(process.env.PORT, () => {
console.log(`App running on port ${process.env.PORT} !`);
});
process.on("SIGTERM", () => shutdown("SIGTERM", server));
process.on("SIGINT", () => shutdown("SIGINT", server));

View File

@@ -0,0 +1,13 @@
import { Kysely, PostgresDialect } from "kysely";
import type { DB } from "kysely-codegen";
import { Pool } from "pg";
const db = new Kysely<DB>({
dialect: new PostgresDialect({
pool: new Pool({
connectionString: process.env.DATABASE_URL,
}),
}),
});
export default db;

View File

@@ -0,0 +1,26 @@
import type { SignalConstants } from "node:os";
import db from "./db.js";
import type { Server } from "node:http";
export const shutdown = async (
signal: keyof SignalConstants,
server: Server,
) => {
console.log(`${signal} received. Shutting down gracefully...`);
server.close(async () => {
try {
await db.destroy();
console.log("Database connection closed.");
process.exit(0);
} catch (err) {
console.error("Error during shutdown:", err);
process.exit(1);
}
});
setTimeout(() => {
console.error("Forcing shutdown after timeout");
process.exit(1);
}, 10000);
};

View File

@@ -0,0 +1,42 @@
{
// Visit https://aka.ms/tsconfig to read more about this file
"compilerOptions": {
// File Layout
"rootDir": "./src",
"outDir": "./dist",
// Environment Settings
// See also https://aka.ms/tsconfig/module
"module": "nodenext",
"target": "esnext",
// For nodejs:
"lib": ["esnext"],
"types": ["node"],
// Other Outputs
"sourceMap": true,
"declaration": true,
"declarationMap": true,
// Stricter Typechecking Options
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
// Style Options
// "noImplicitReturns": true,
// "noImplicitOverride": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
// "noFallthroughCasesInSwitch": true,
// "noPropertyAccessFromIndexSignature": true,
// Recommended Options
"strict": true,
"jsx": "react-jsx",
"verbatimModuleSyntax": true,
"isolatedModules": true,
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
"skipLibCheck": true,
},
}