From 46002a2479335500b013f7e69bbe54c7087a7ca7 Mon Sep 17 00:00:00 2001 From: Syahdan Date: Sat, 7 Jun 2025 16:41:57 +0700 Subject: [PATCH] use mysql2/promise as db connector --- .env.example | 4 ++ bun.lock | 32 +++++++++++++- package.json | 4 +- src/index.ts | 121 ++++++++++++++++++++++++++++++++++++--------------- 4 files changed, 124 insertions(+), 37 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9e117af --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +DB_HOST=localhost +DB_USER=root +DB_PASSWORD=password +DB_NAME=mysql diff --git a/bun.lock b/bun.lock index 1cd0897..7783b7a 100644 --- a/bun.lock +++ b/bun.lock @@ -5,8 +5,10 @@ "name": "pemweb-api", "dependencies": { "elysia": "latest", + "mysql2": "^3.14.1", }, "devDependencies": { + "@types/node": "^22.15.29", "bun-types": "latest", }, }, @@ -18,7 +20,9 @@ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - "@types/node": ["@types/node@22.15.21", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ=="], + "@types/node": ["@types/node@22.15.29", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ=="], + + "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], "bun-types": ["bun-types@1.2.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-Kuh4Ub28ucMRWeiUUWMHsT9Wcbr4H3kLIO72RZZElSDxSu7vpetRvxIUDUaW6QtaIeixIpm7OXtNnZPf82EzwA=="], @@ -26,6 +30,8 @@ "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + "elysia": ["elysia@1.3.1", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.1.2", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": "^0.34.33", "openapi-types": "^12.1.3" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-En41P6cDHcHtQ0nvfsn9ayB+8ahQJqG1nzvPX8FVZjOriFK/RtZPQBtXMfZDq/AsVIk7JFZGFEtAVEmztNJVhQ=="], "exact-mirror": ["exact-mirror@0.1.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-wFCPCDLmHbKGUb8TOi/IS7jLsgR8WVDGtDK3CzcB4Guf/weq7G+I+DkXiRSZfbemBFOxOINKpraM6ml78vo8Zw=="], @@ -36,14 +42,36 @@ "file-type": ["file-type@21.0.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.7", "strtok3": "^10.2.2", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg=="], + "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], + + "lru.min": ["lru.min@1.1.2", "", {}, "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "mysql2": ["mysql2@3.14.1", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.6.3", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w=="], + + "named-placeholders": ["named-placeholders@1.1.3", "", { "dependencies": { "lru-cache": "^7.14.1" } }, "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w=="], + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], "peek-readable": ["peek-readable@7.0.0", "", {}, "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="], + + "sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="], + "strtok3": ["strtok3@10.2.2", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^7.0.0" } }, "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg=="], "token-types": ["token-types@6.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="], @@ -53,5 +81,7 @@ "uint8array-extras": ["uint8array-extras@1.4.0", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "bun-types/@types/node": ["@types/node@22.15.21", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ=="], } } diff --git a/package.json b/package.json index 0c15971..a571c07 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,11 @@ "dev": "bun run --watch src/index.ts" }, "dependencies": { - "elysia": "latest" + "elysia": "latest", + "mysql2": "^3.14.1" }, "devDependencies": { + "@types/node": "^22.15.29", "bun-types": "latest" }, "module": "src/index.js" diff --git a/src/index.ts b/src/index.ts index 85c4124..d57b0e1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,18 @@ import { Elysia, t } from "elysia"; +import mysql from "mysql2/promise"; + +const pool = mysql.createPool({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, +}); + +type Member = { + id: number; + name: string; + phone: string; +}; // dummy data const data = { @@ -7,81 +21,118 @@ const data = { const app = new Elysia().get("/", () => "Hello Elysia").listen(3000); -app.group("/api", (app) => { - return app +app.group("/api", (api) => { + return api .post( "/create", - ({ body: { food } }) => { - data.foods.push(food); + async ({ body }) => { + const { name, phone } = body; + if (!name || !phone) { + return { + status: "error", + message: "Name and phone are required.", + }; + } - const id = data.foods.length - 1; + const [res] = await pool.execute( + "INSERT INTO `member` (name, phone) VALUES (?, ?)", + [name, phone] + ); return { status: "success", - message: `${food} is added to the list.`, + message: `${name} is added to the list.`, data: { - id, - food: data.foods[id], - }, + name, + phone, + } as Omit, }; }, { body: t.Object({ - food: t.String(), + name: t.String(), + phone: t.String(), }), - }, + } ) - .get("/read", () => ({ - status: "success", - message: "Reading foods.", - data: data.foods, - })) + .get("/read", async () => { + const [rows] = await pool.query("SELECT * FROM `member`"); + + if (rows.length === 0) { + return { + status: "error", + message: "No members found.", + }; + } + + return { + status: "success", + message: "Members collected.", + data: rows as Member[], + }; + }) .put( "/update", - ({ body: { food, id } }) => { - if (data.foods[id] === undefined) { + async ({ body }) => { + const { id, name, phone } = body; + + const [res] = await pool.execute( + "UPDATE `member` SET name = ?, phone = ? WHERE id = ?", + [name, phone, id] + ); + + if (res.affectedRows === 0) { return { status: "error", - message: `${id} does not exist.`, + message: `Member with ID ${id} not found.`, }; } - data.foods[id] = food; + const [rows] = await pool.query( + "SELECT * FROM `member` WHERE id = ?", + [id] + ); return { status: "success", - message: `${food} is updated.`, - data: { - food: data.foods[id], - }, + message: `Member with ID ${id} is updated.`, + data: rows[0], }; }, { body: t.Object({ id: t.Number(), - food: t.String(), + name: t.String(), + phone: t.String(), }), - }, + } ) .delete( "/delete", - ({ body: { id } }) => { - const deleted = data.foods.splice(id, 1); - + async ({ body: { id } }) => { + const [rows] = await pool.query( + "SELECT * FROM `member` WHERE id = ?", + [id] + ); + if (rows.length === 0) { + return { + status: "error", + message: `Member with ID ${id} not found.`, + }; + } + await pool.execute("DELETE FROM `member` WHERE id = ?", [id]); return { status: "success", - message: `${deleted[0]} is deleted.`, - data: { - food: deleted[0], - }, + message: `Member with ID ${id} is deleted.`, + data: rows[0], }; }, - { body: t.Object({ id: t.Number() }) }, + { body: t.Object({ id: t.Number() }) } ); }); console.log( - `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`, + `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` );