Initial commit: ElysiaJS backend + TanStack Start frontend + Traefik compose

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
mane 2026-06-27 04:01:48 +00:00
commit a346cf3a5c
17 changed files with 505 additions and 0 deletions

6
frontend/.dockerignore Normal file
View file

@ -0,0 +1,6 @@
node_modules
.output
.vinxi
.git
.env
*.log

19
frontend/Dockerfile Normal file
View file

@ -0,0 +1,19 @@
# --- Frontend: TanStack Start (SSR, Node runtime) ---
FROM node:22-alpine AS build
WORKDIR /app
# VITE_API_URL must be present at build time: Vite inlines it into the bundle.
ARG VITE_API_URL
ENV VITE_API_URL=${VITE_API_URL}
COPY package.json package-lock.json* ./
RUN npm ci || npm install
COPY . .
RUN npm run build
FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
# TanStack Start (Vite) emits a self-contained server bundle in .output.
COPY --from=build /app/.output ./.output
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]

23
frontend/package.json Normal file
View file

@ -0,0 +1,23 @@
{
"name": "test-app-frontend",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev --port 3000",
"build": "vite build",
"start": "node .output/server/index.mjs"
},
"dependencies": {
"@tanstack/react-router": "^1.95.0",
"@tanstack/react-start": "^1.95.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@tanstack/router-plugin": "^1.95.0",
"@vitejs/plugin-react": "^4.3.4",
"typescript": "^5.7.2",
"vite": "^6.0.0"
}
}

18
frontend/src/router.tsx Normal file
View file

@ -0,0 +1,18 @@
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
// routeTree.gen.ts is auto-generated by the TanStack Router plugin on the
// first `vite dev` / `vite build`. It is git-ignored — do not edit by hand.
export function createRouter() {
return createTanStackRouter({
routeTree,
defaultPreload: "intent",
scrollRestoration: true,
});
}
declare module "@tanstack/react-router" {
interface Register {
router: ReturnType<typeof createRouter>;
}
}

View file

@ -0,0 +1,40 @@
import {
HeadContent,
Outlet,
Scripts,
createRootRoute,
} from "@tanstack/react-router";
import type { ReactNode } from "react";
export const Route = createRootRoute({
head: () => ({
meta: [
{ charSet: "utf-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
{ title: "test-app-stack" },
],
}),
component: RootComponent,
});
function RootComponent() {
return (
<RootDocument>
<Outlet />
</RootDocument>
);
}
function RootDocument({ children }: { children: ReactNode }) {
return (
<html lang="en">
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
);
}

View file

@ -0,0 +1,47 @@
import { createFileRoute } from "@tanstack/react-router";
import { useEffect, useState } from "react";
export const Route = createFileRoute("/")({
component: Home,
});
// Public URL of the backend, injected at build time via Vite.
// Falls back to localhost for `vite dev`.
const API_URL = import.meta.env.VITE_API_URL ?? "http://localhost:3000";
type Hello = { message: string; timestamp: string };
function Home() {
const [data, setData] = useState<Hello | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch(`${API_URL}/api/hello`)
.then((r) => {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json() as Promise<Hello>;
})
.then(setData)
.catch((e) => setError(String(e)));
}, []);
return (
<main style={{ fontFamily: "system-ui, sans-serif", padding: "2rem", maxWidth: 640, margin: "0 auto" }}>
<h1>test-app-stack</h1>
<p>TanStack Start frontend talking to an ElysiaJS (Bun) backend.</p>
<section style={{ marginTop: "1.5rem", padding: "1rem", border: "1px solid #ddd", borderRadius: 8 }}>
<h2 style={{ marginTop: 0 }}>Backend connection</h2>
<p><strong>API URL:</strong> <code>{API_URL}</code></p>
{error && <p style={{ color: "crimson" }}> {error}</p>}
{!error && !data && <p>Connecting</p>}
{data && (
<>
<p> <strong>{data.message}</strong></p>
<p style={{ color: "#666", fontSize: "0.85rem" }}>at {data.timestamp}</p>
</>
)}
</section>
</main>
);
}

15
frontend/tsconfig.json Normal file
View file

@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"noEmit": true,
"types": ["vite/client"]
},
"include": ["src", "vite.config.ts"]
}

10
frontend/vite.config.ts Normal file
View file

@ -0,0 +1,10 @@
import { defineConfig } from "vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";
// TanStack Start full-stack app. The plugin wires SSR + file-based routing
// (routes live in src/routes, routeTree.gen.ts is generated automatically).
export default defineConfig({
server: { port: 3000, host: true },
plugins: [tanstackStart(), viteReact()],
});