init
This commit is contained in:
14
services/api/Dockerfile
Normal file
14
services/api/Dockerfile
Normal 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
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
34
services/api/package.json
Normal 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
35
services/api/src/app.ts
Normal 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
10
services/api/src/index.ts
Normal 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));
|
||||
13
services/api/src/lib/db.ts
Normal file
13
services/api/src/lib/db.ts
Normal 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;
|
||||
26
services/api/src/lib/shutdown.ts
Normal file
26
services/api/src/lib/shutdown.ts
Normal 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);
|
||||
};
|
||||
42
services/api/tsconfig.json
Normal file
42
services/api/tsconfig.json
Normal 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,
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user