diff --git a/.env b/.env new file mode 100644 index 0000000..258d393 --- /dev/null +++ b/.env @@ -0,0 +1,8 @@ +SERVER_HOST=localhost +SERVER_PORT=3000 + +DATABASE_HOST=127.0.0.1 +DATABASE_PORT=5432 +DATABASE_USERNAME=malma +DATABASE_PASSWORD=kucing +DATABASE_NAME=goumrah diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ba6307a --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +SERVER_HOST= +SERVER_PORT= + +DATABASE_HOST= +DATABASE_PORT= +DATABASE_USER= +DATABASE_PASSWORD= +DATABASE_NAME= diff --git a/.vscode/settings.json b/.vscode/settings.json index 47b1035..73dbd14 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,11 @@ { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.codeActionsOnSave": {}, + "editor.codeActionsOnSave": { + "source.addMissingImports.ts": "always", + "source.organizeImports": "always", + "source.removeUnusedImports": "always" + }, "typescript.preferences.importModuleSpecifier": "non-relative" } diff --git a/bun.lock b/bun.lock index 380d05a..186e041 100644 --- a/bun.lock +++ b/bun.lock @@ -5,11 +5,16 @@ "dependencies": { "@mikro-orm/core": "6.5.9", "@mikro-orm/postgresql": "6.5.9", + "compression": "1.8.1", + "cors": "2.8.5", "express": "5.1.0", + "helmet": "8.1.0", "zod": "4.1.12", }, "devDependencies": { "@types/bun": "1.3.1", + "@types/compression": "1.8.1", + "@types/cors": "2.8.19", "@types/express": "5.0.5", "prettier": "3.6.2", "typescript": "5.9.3", @@ -33,8 +38,12 @@ "@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="], + "@types/compression": ["@types/compression@1.8.1", "", { "dependencies": { "@types/express": "*", "@types/node": "*" } }, "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q=="], + "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], + "@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="], + "@types/express": ["@types/express@5.0.5", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "^1" } }, "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ=="], "@types/express-serve-static-core": ["@types/express-serve-static-core@5.1.0", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA=="], @@ -75,6 +84,10 @@ "commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="], + "compressible": ["compressible@2.0.18", "", { "dependencies": { "mime-db": ">= 1.43.0 < 2" } }, "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg=="], + + "compression": ["compression@1.8.1", "", { "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" } }, "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w=="], + "content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], @@ -83,11 +96,13 @@ "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="], + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], "dataloader": ["dataloader@2.2.3", "", {}, "sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], @@ -155,6 +170,8 @@ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "helmet": ["helmet@8.1.0", "", {}, "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg=="], + "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="], "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], @@ -199,14 +216,18 @@ "mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + "on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="], + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], @@ -323,6 +344,14 @@ "zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="], + "accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "body-parser/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "express/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "finalhandler/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], "knex/debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], @@ -337,6 +366,20 @@ "raw-body/iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], + "router/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "send/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "body-parser/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "express/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "finalhandler/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "knex/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "router/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], } } diff --git a/package.json b/package.json index db1cd95..fb943a3 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,20 @@ { "scripts": { - "dev": "bun run src/main.ts" + "dev": "bun run src/server.ts" }, "dependencies": { "@mikro-orm/core": "6.5.9", "@mikro-orm/postgresql": "6.5.9", + "compression": "1.8.1", + "cors": "2.8.5", "express": "5.1.0", + "helmet": "8.1.0", "zod": "4.1.12" }, "devDependencies": { "@types/bun": "1.3.1", + "@types/compression": "1.8.1", + "@types/cors": "2.8.19", "@types/express": "5.0.5", "prettier": "3.6.2", "typescript": "5.9.3" diff --git a/src/application.ts b/src/application.ts new file mode 100644 index 0000000..e43dc43 --- /dev/null +++ b/src/application.ts @@ -0,0 +1,48 @@ +import { serverConfig } from "@/configs/server.config"; +import compression from "compression"; +import cors from "cors"; +import express from "express"; +import helmet from "helmet"; + +export class Application { + private readonly _app: express.Application; + + public constructor() { + this._app = express(); + } + + public initializeMiddlewares() { + this._app.use(helmet()); + this._app.use(cors()); + this._app.use(compression()); + this._app.use(express.json()); + this._app.use(express.urlencoded()); + } + + public initializeRouters() { + // this._app.use("/countries"); + // this._app.use("/cities"); + // this._app.use("/airlines"); + // this._app.use("/airports"); + // this._app.use("/flights"); + // this._app.use("/hotel-facilities"); + // this._app.use("/hotels"); + // this._app.use("/transportations"); + // this._app.use("/packages"); + } + + public initializeErrorHandlers() {} + + public run() { + const port = serverConfig.port; + const host = serverConfig.host; + + this._app.listen(port, host, (err) => { + if (err) { + console.log(`Failed to listen server: ${err.message}`); + } else { + console.log(`Server listening at ${host}:${port}`); + } + }); + } +} diff --git a/src/configs/_env.ts b/src/configs/_env.ts new file mode 100644 index 0000000..812117f --- /dev/null +++ b/src/configs/_env.ts @@ -0,0 +1,20 @@ +import z from "zod"; + +export const _env = z + .object({ + SERVER_HOST: z.string("Must be string."), + SERVER_PORT: z.coerce + .number("Must be number.") + .int("Must be integer.") + .min(0, "Min 0."), + + DATABASE_HOST: z.string("Must be string.").nonempty("Must not empty."), + DATABASE_PORT: z.coerce + .number("Must be number.") + .int("Must be integer.") + .min(0, "Min 0."), + DATABASE_USERNAME: z.string("Must be string.").nonempty("Must not empty."), + DATABASE_PASSWORD: z.string("Must be string.").nonempty("Must not empty."), + DATABASE_NAME: z.string("Must be string.").nonempty("Must not empty."), + }) + .parse(Bun.env); diff --git a/src/configs/database.config.ts b/src/configs/database.config.ts new file mode 100644 index 0000000..ff21773 --- /dev/null +++ b/src/configs/database.config.ts @@ -0,0 +1,9 @@ +import { _env } from "@/configs/_env"; + +export const databaseConfig = { + host: _env.DATABASE_HOST, + port: _env.DATABASE_PORT, + username: _env.DATABASE_USERNAME, + password: _env.DATABASE_PASSWORD, + name: _env.DATABASE_NAME, +} as const; diff --git a/src/configs/server.config.ts b/src/configs/server.config.ts new file mode 100644 index 0000000..98f8ca0 --- /dev/null +++ b/src/configs/server.config.ts @@ -0,0 +1,6 @@ +import { _env } from "@/configs/_env"; + +export const serverConfig = { + host: _env.SERVER_HOST, + port: _env.SERVER_PORT, +} as const; diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index 77cc333..0000000 --- a/src/main.ts +++ /dev/null @@ -1,11 +0,0 @@ -import express from "express"; - -const app = express(); - -app.listen(3000, "localhost", (err) => { - if (err) { - console.log("Error starting server:", err); - } else { - console.log("Server is running at http://localhost:3000"); - } -}); diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..30a60e7 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,20 @@ +import { Application } from "@/application"; + +process.on("uncaughtException", (err) => { + let message = `Uncaught exception: ${err.message || err}`; + if (err.stack) { + message += `\n- Stack: ${err.stack}`; + } + + console.warn(message); +}); + +process.on("unhandledRejection", (reason) => { + console.warn(`Unhandled rejection: ${reason}`); +}); + +const application = new Application(); +application.initializeMiddlewares(); +application.initializeRouters(); +application.initializeErrorHandlers(); +application.run(); diff --git a/tsconfig.json b/tsconfig.json index f023cdf..6ba655a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,12 @@ "skipDefaultLibCheck": true, "strict": true, - "verbatimModuleSyntax": true + "verbatimModuleSyntax": true, + + "baseUrl": "src", + "paths": { + "@/*": ["./*"] + } }, "include": ["src"]