Meal API: Elysia backend serving the menu API and a single-file frontend

This commit is contained in:
2026-09-16 20:14:10 +08:00
commit 3899025fa3
6 changed files with 1100 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
# dev
.yarn/
!.yarn/releases
.vscode/*
!.vscode/launch.json
!.vscode/*.code-snippets
.idea/workspace.xml
.idea/usage.statistics.xml
.idea/shelf
# deps
node_modules/
# env
.env
.env.production
# logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# misc
.DS_Store
# local scratch: the old Hono starter this project began from (not part of the submission)
_hono-scaffold/
+90
View File
@@ -0,0 +1,90 @@
# Meal API
A small food menu API with a single-page frontend.
## Files
| File | Purpose |
| --- | --- |
| `index.html` | The frontend (page, styles and script are all inline) |
| `server.js` | The whole backend: the menu data + the routes |
| `package.json` | Only needed so `elysia` can be installed |
`index.html` and `server.js` are the whole application — no `public/` folder,
no separate `menu.js`, `app.js` or `style.css`.
## Run it
```sh
npm install
npm start # node server.js
```
Then open <http://localhost:3000>.
Other ways to run the exact same file:
```sh
npm run dev # node --watch server.js (auto-restart on save)
npm run bun # bun server.js
```
`server.js` runs on **both** Node and Bun. Elysia serves through `Bun.serve` on
Bun, and `server.js` loads the `@elysiajs/node` adapter automatically whenever
Bun isn't present, so neither runtime needs its own version of the file.
## Endpoints
| Method | Path | Result |
| --- | --- | --- |
| GET | `/` or `/index.html` | the frontend |
| GET | `/api/menu` | the whole menu |
| GET | `/api/menu/:id` | one menu item |
```sh
curl http://localhost:3000/api/menu
curl http://localhost:3000/api/menu/2
```
### Responses
`GET /api/menu` -> `200`
```json
[
{ "id": 1, "food": "Adobo", "price": 75 },
{ "id": 2, "food": "Fried Chicken", "price": 50 },
{ "id": 3, "food": "Yum Burger", "price": 70 }
]
```
`GET /api/menu/2` -> `200`
```json
{ "id": 2, "food": "Fried Chicken", "price": 50 }
```
| Case | Status | Body |
| --- | --- | --- |
| id doesn't exist (`/api/menu/99`) | `404` | `{ "error": "No menu item with ID 99." }` |
| id isn't a whole number (`/api/menu/abc`, `/api/menu/0`, `/api/menu/1.5`) | `400` | `{ "error": "ID must be a whole number, like 1." }` |
| any non-GET method | `405` | `{ "error": "Method not allowed" }` |
| unknown path (`/nope`) | `404` | `Not found` |
## Menu data
Defined in `server.js`:
```js
const menu = [
{ id: 1, food: 'Adobo', price: 75 },
{ id: 2, food: 'Fried Chicken', price: 50 },
{ id: 3, food: 'Yum Burger', price: 70 },
]
```
## Frontend
`index.html` lets you either load the whole menu or look up a single item by ID.
It fetches the API with `fetch()`, renders the results into a table, and shows a
message instead of the table whenever the API returns an error.
+216
View File
@@ -0,0 +1,216 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Food Menu</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: #f3f4f6;
color: #111827;
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
}
.card {
width: 100%;
max-width: 560px;
background: #ffffff;
border-radius: 12px;
padding: 28px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
h1 {
margin: 0 0 20px;
font-size: 24px;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 18px;
}
#search-input {
flex: 1 1 140px;
padding: 10px 12px;
font-size: 15px;
border: 1px solid #d1d5db;
border-radius: 8px;
}
#search-input:focus {
outline: 2px solid #2563eb;
outline-offset: -1px;
border-color: #2563eb;
}
button {
padding: 10px 16px;
font-size: 15px;
font-weight: 600;
color: #ffffff;
background: #2563eb;
border: none;
border-radius: 8px;
cursor: pointer;
}
button:hover {
background: #1d4ed8;
}
button.secondary {
color: #1f2937;
background: #e5e7eb;
}
button.secondary:hover {
background: #d1d5db;
}
.message {
margin: 0 0 16px;
padding: 10px 12px;
border-radius: 8px;
background: #fef3c7;
color: #92400e;
font-size: 14px;
}
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
padding: 10px 12px;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
th {
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.04em;
color: #6b7280;
}
tbody tr:last-child td {
border-bottom: none;
}
</style>
</head>
<body>
<main class="card">
<h1>Food Menu</h1>
<form id="search-form" class="controls">
<input
type="number"
id="search-input"
placeholder="Enter ID #"
min="1"
step="1"
autocomplete="off"
>
<button type="submit">Search</button>
<button type="button" id="load-btn" class="secondary">Load Menu</button>
</form>
<p id="message" class="message" hidden></p>
<table id="menu-table" hidden>
<thead>
<tr>
<th>ID</th>
<th>Food</th>
<th>Price</th>
</tr>
</thead>
<tbody id="menu-body"></tbody>
</table>
</main>
<script>
const form = document.getElementById('search-form');
const input = document.getElementById('search-input');
const loadBtn = document.getElementById('load-btn');
const message = document.getElementById('message');
const table = document.getElementById('menu-table');
const tbody = document.getElementById('menu-body');
function showMessage(text) {
message.textContent = text;
message.hidden = false;
table.hidden = true;
}
function showItems(items) {
tbody.innerHTML = '';
for (const item of items) {
const row = document.createElement('tr');
const id = document.createElement('td');
id.textContent = item.id;
const food = document.createElement('td');
food.textContent = item.food;
const price = document.createElement('td');
price.textContent = `₱${item.price}`;
row.append(id, food, price);
tbody.append(row);
}
message.hidden = true;
table.hidden = false;
}
async function fetchMenu(url) {
try {
const res = await fetch(url);
const data = await res.json();
if (!res.ok) {
return showMessage(data.error || 'Something went wrong.');
}
showItems(Array.isArray(data) ? data : [data]);
} catch (err) {
showMessage('Could not reach the API. Is the server running?');
}
}
form.addEventListener('submit', (event) => {
event.preventDefault();
const id = input.value.trim();
if (id === '') {
return showMessage('Please enter an ID number first.');
}
fetchMenu(`/api/menu/${encodeURIComponent(id)}`);
});
loadBtn.addEventListener('click', () => {
input.value = '';
fetchMenu('/api/menu');
});
</script>
</body>
</html>
+683
View File
@@ -0,0 +1,683 @@
{
"name": "bonpin-adrian-meal-api",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bonpin-adrian-meal-api",
"version": "1.0.0",
"dependencies": {
"@elysiajs/node": "^1.4.5",
"@elysiajs/openapi": "^1.4.16",
"elysia": "^1.4.30",
"zod": "^4.6.5"
}
},
"node_modules/@borewit/text-codec": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz",
"integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==",
"license": "MIT",
"peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@elysiajs/node": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@elysiajs/node/-/node-1.4.5.tgz",
"integrity": "sha512-K58HEre7RQWZOkLcMNl2RFnuZFzmUUyxHRBSfC2XaJz4j2AjrpHKu/f26tN9LRi3nx1L0PbH22vgZ4O2Fqppbw==",
"license": "MIT",
"dependencies": {
"crossws": "^0.4.1",
"srvx": "^0.11.2"
},
"peerDependencies": {
"elysia": ">= 1.4.0"
}
},
"node_modules/@elysiajs/openapi": {
"version": "1.4.16",
"resolved": "https://registry.npmjs.org/@elysiajs/openapi/-/openapi-1.4.16.tgz",
"integrity": "sha512-kj8RBffE4J6myjm7nPPj6Dw1ujWt45NkdGW5sG9u3T0OfUzKN71s630/LEjajGo5jRZqFF3VHyk40lwXH2AbmQ==",
"license": "MIT",
"peerDependencies": {
"@sinclair/typebox": ">= 0.34.0 < 1",
"elysia": ">= 1.4.0",
"typescript": ">= 5.0.0"
}
},
"node_modules/@sinclair/typebox": {
"version": "0.34.52",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
"integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT",
"peer": true
},
"node_modules/@tokenizer/inflate": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz",
"integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==",
"license": "MIT",
"peer": true,
"dependencies": {
"debug": "^4.4.3",
"token-types": "^6.1.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@tokenizer/token": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
"license": "MIT",
"peer": true
},
"node_modules/@typescript/typescript-aix-ppc64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz",
"integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==",
"cpu": [
"ppc64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"aix"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-darwin-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz",
"integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-darwin-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz",
"integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-freebsd-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz",
"integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-freebsd-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz",
"integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-arm": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz",
"integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz",
"integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-loong64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz",
"integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==",
"cpu": [
"loong64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-mips64el": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz",
"integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==",
"cpu": [
"mips64el"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-ppc64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz",
"integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==",
"cpu": [
"ppc64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-riscv64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz",
"integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==",
"cpu": [
"riscv64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-s390x": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz",
"integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==",
"cpu": [
"s390x"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz",
"integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-netbsd-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz",
"integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"netbsd"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-netbsd-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz",
"integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"netbsd"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-openbsd-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz",
"integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"openbsd"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-openbsd-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz",
"integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"openbsd"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-sunos-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz",
"integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"sunos"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-win32-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz",
"integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-win32-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz",
"integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/cookie": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/crossws": {
"version": "0.4.12",
"resolved": "https://registry.npmjs.org/crossws/-/crossws-0.4.12.tgz",
"integrity": "sha512-aypfsr6t0uNvkqaZc6zvBfXzC6pLI0/sIulpkV6RwCVtZqG5ebBzv4weImKK0VNCj91Wl9F5j7p5WU4MNrybng==",
"license": "MIT",
"peerDependencies": {
"srvx": ">=0.11.5"
},
"peerDependenciesMeta": {
"srvx": {
"optional": true
}
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"peer": true,
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/elysia": {
"version": "1.4.30",
"resolved": "https://registry.npmjs.org/elysia/-/elysia-1.4.30.tgz",
"integrity": "sha512-S2qqV0CbM4faB1hQQ5IYoeYnAUinjHlzRduxaBc4OoNqh0GjOc8k6tt3hv5ozWcG6Svr6hugw6JL4zNke4jwfA==",
"license": "MIT",
"dependencies": {
"cookie": "^1.1.1",
"exact-mirror": "^0.2.7",
"fast-decode-uri-component": "^1.0.1",
"memoirist": "^0.4.0"
},
"peerDependencies": {
"@sinclair/typebox": ">= 0.34.0 < 1",
"@types/bun": ">= 1.2.0",
"exact-mirror": ">= 0.0.9",
"file-type": ">= 20.0.0",
"openapi-types": ">= 12.0.0",
"typescript": ">= 5.0.0"
},
"peerDependenciesMeta": {
"@types/bun": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/exact-mirror": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/exact-mirror/-/exact-mirror-0.2.7.tgz",
"integrity": "sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg==",
"license": "MIT",
"peerDependencies": {
"@sinclair/typebox": "^0.34.15"
},
"peerDependenciesMeta": {
"@sinclair/typebox": {
"optional": true
}
}
},
"node_modules/fast-decode-uri-component": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz",
"integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==",
"license": "MIT"
},
"node_modules/file-type": {
"version": "22.1.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-22.1.0.tgz",
"integrity": "sha512-fbvf9u6jnHWr1EP6V8R99E9iLMhNLXgZkUzWBiPpJ5/89UJ/Atuxjn5YXR+XaZWo2hc2/8mGfTez+NDAgOcpLA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@tokenizer/inflate": "^0.4.1",
"strtok3": "^10.3.5",
"token-types": "^6.1.2",
"uint8array-extras": "^1.5.0"
},
"engines": {
"node": ">=22"
},
"funding": {
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause",
"peer": true
},
"node_modules/memoirist": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/memoirist/-/memoirist-0.4.0.tgz",
"integrity": "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT",
"peer": true
},
"node_modules/openapi-types": {
"version": "12.1.3",
"resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
"integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==",
"license": "MIT",
"peer": true
},
"node_modules/srvx": {
"version": "0.11.22",
"resolved": "https://registry.npmjs.org/srvx/-/srvx-0.11.22.tgz",
"integrity": "sha512-LqZxxBDMKuMAZzFzJnDCkFOrs9MZQZr0LvHiO/SuSZVdQaXD7xQ5UWTUxheJrQPve1qk9MG2B/yttUvJxw8egQ==",
"license": "MIT",
"bin": {
"srvx": "bin/srvx.mjs"
},
"engines": {
"node": ">=20.16.0"
}
},
"node_modules/strtok3": {
"version": "10.3.5",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz",
"integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@tokenizer/token": "^0.3.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/token-types": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz",
"integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==",
"license": "MIT",
"peer": true,
"dependencies": {
"@borewit/text-codec": "^0.2.1",
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/typescript": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
"integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc"
},
"engines": {
"node": ">=16.20.0"
},
"optionalDependencies": {
"@typescript/typescript-aix-ppc64": "7.0.2",
"@typescript/typescript-darwin-arm64": "7.0.2",
"@typescript/typescript-darwin-x64": "7.0.2",
"@typescript/typescript-freebsd-arm64": "7.0.2",
"@typescript/typescript-freebsd-x64": "7.0.2",
"@typescript/typescript-linux-arm": "7.0.2",
"@typescript/typescript-linux-arm64": "7.0.2",
"@typescript/typescript-linux-loong64": "7.0.2",
"@typescript/typescript-linux-mips64el": "7.0.2",
"@typescript/typescript-linux-ppc64": "7.0.2",
"@typescript/typescript-linux-riscv64": "7.0.2",
"@typescript/typescript-linux-s390x": "7.0.2",
"@typescript/typescript-linux-x64": "7.0.2",
"@typescript/typescript-netbsd-arm64": "7.0.2",
"@typescript/typescript-netbsd-x64": "7.0.2",
"@typescript/typescript-openbsd-arm64": "7.0.2",
"@typescript/typescript-openbsd-x64": "7.0.2",
"@typescript/typescript-sunos-x64": "7.0.2",
"@typescript/typescript-win32-arm64": "7.0.2",
"@typescript/typescript-win32-x64": "7.0.2"
}
},
"node_modules/uint8array-extras": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz",
"integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "4.6.5",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz",
"integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"name": "bonpin-adrian-meal-api",
"version": "1.0.0",
"description": "A simple food menu API with a frontend.",
"type": "module",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js",
"bun": "bun server.js"
},
"dependencies": {
"@elysiajs/node": "^1.4.5",
"@elysiajs/openapi": "^1.4.16",
"elysia": "^1.4.30",
"zod": "^4.6.5"
}
}
+62
View File
@@ -0,0 +1,62 @@
import { Elysia, status } from 'elysia'
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
const PORT = process.env.PORT || 3000
// Resolve index.html next to this file, so it works no matter where you run it from.
const INDEX_HTML = readFileSync(fileURLToPath(new URL('./index.html', import.meta.url)), 'utf8')
const HTML_HEADERS = {
'content-type': 'text/html; charset=utf-8',
'content-length': String(Buffer.byteLength(INDEX_HTML)),
}
// The food menu data.
const menu = [
{ id: 1, food: 'Adobo', price: 75 },
{ id: 2, food: 'Fried Chicken', price: 50 },
{ id: 3, food: 'Yum Burger', price: 70 },
]
// Elysia serves through Bun.serve on Bun, but it needs the Node adapter on Node.
// This one file therefore runs on either runtime: `bun server.js` or `node server.js`.
const adapter =
typeof Bun === 'undefined' ? (await import('@elysiajs/node')).node() : undefined
new Elysia(adapter ? { adapter } : {})
.onRequest(({ request }) => {
if (request.method !== 'GET') {
return status(405, { error: 'Method not allowed' })
}
})
// GET /api/menu -> the whole menu
.get('/api/menu', () => menu)
// GET /api/menu/:id -> one item
.get('/api/menu/:id', ({ params: { id }, status }) => {
const num = Number(id)
if (!Number.isInteger(num) || num < 1) {
return status(400, { error: 'ID must be a whole number, like 1.' })
}
const item = menu.find((food) => food.id === num)
if (!item) {
return status(404, { error: `No menu item with ID ${num}.` })
}
return item
})
// GET / -> the frontend
.get('/', () => new Response(INDEX_HTML, { headers: HTML_HEADERS }))
.get('/index.html', () => new Response(INDEX_HTML, { headers: HTML_HEADERS }))
.get('*', () => status(404, 'Not found'))
.listen(PORT)
const runtime = typeof Bun === 'undefined' ? 'Node.js' : 'Bun'
console.log(`Meal API running at http://localhost:${PORT} (${runtime})`)