Skip to content

Commit 99c7bb3

Browse files
committed
feat: build react-spa frontend
1 parent 3d165b2 commit 99c7bb3

File tree

16 files changed

+116
-66
lines changed

16 files changed

+116
-66
lines changed

package.json

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
"test": "test"
99
},
1010
"scripts": {
11-
"build": "rm -rf dist && tsc",
12-
"watch": "npm run build -- --watch",
11+
"start": "npm run build && npm run build:client && fastify start -l info dist/app.js",
12+
"build": "tsc",
13+
"build:client": "vite build",
14+
"watch": "tsc -w",
15+
"dev": "npm run build && concurrently -k -p \"[{name}]\" -n \"TypeScript,App\" -c \"yellow.bold,cyan.bold\" \"npm:watch\" \"npm:dev:start\"",
16+
"dev:start": "fastify start --ignore-watch=.ts$ -l info -P dist/app.js",
1317
"test": "npm run db:seed && tap --jobs=1 test/**/*",
14-
"start": "fastify start -l info dist/app.js",
15-
"dev": "fastify start -w -l info -P dist/app.js",
1618
"standalone": "node --env-file=.env dist/server.js",
17-
"lint": "eslint --ignore-pattern=dist",
19+
"lint": "eslint src --ignore-pattern='src/client/dist'",
1820
"lint:fix": "npm run lint -- --fix",
1921
"db:migrate": "node --env-file=.env scripts/migrate.js",
2022
"db:seed": "node --env-file=.env scripts/seed-database.js"
@@ -35,19 +37,26 @@
3537
"@fastify/swagger-ui": "^4.0.1",
3638
"@fastify/type-provider-typebox": "^4.0.0",
3739
"@fastify/under-pressure": "^8.3.0",
40+
"@fastify/vite": "^6.0.7",
3841
"@sinclair/typebox": "^0.33.7",
42+
"@vitejs/plugin-react": "^4.3.1",
43+
"concurrently": "^8.2.2",
3944
"fastify": "^4.26.1",
4045
"fastify-cli": "^6.1.1",
4146
"fastify-plugin": "^4.0.0",
42-
"postgrator": "^7.2.0"
47+
"postgrator": "^7.2.0",
48+
"react": "^18.3.1",
49+
"react-dom": "^18.3.1"
4350
},
4451
"devDependencies": {
4552
"@types/node": "^22.0.0",
53+
"@types/react": "^18.3.4",
54+
"@types/react-dom": "^18.3.0",
4655
"eslint": "^9.4.0",
4756
"fastify-tsconfig": "^2.0.0",
4857
"mysql2": "^3.10.1",
4958
"neostandard": "^0.7.0",
50-
"tap": "^19.2.2",
59+
"tap": "^21.0.1",
5160
"typescript": "^5.4.5"
5261
}
5362
}

src/app.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import path from "node:path";
66
import fastifyAutoload from "@fastify/autoload";
77
import { FastifyInstance, FastifyPluginOptions } from "fastify";
8+
import fastifyVite from "@fastify/vite";
89

910
export default async function serviceApp(
1011
fastify: FastifyInstance,
@@ -14,24 +15,24 @@ export default async function serviceApp(
1415
// those should be registered first as your custom plugins might depend on them
1516
await fastify.register(fastifyAutoload, {
1617
dir: path.join(import.meta.dirname, "plugins/external"),
17-
options: { ...opts }
18+
options: {}
1819
});
1920

2021
// This loads all your custom plugins defined in plugins/custom
2122
// those should be support plugins that are reused
2223
// through your application
23-
fastify.register(fastifyAutoload, {
24+
await fastify.register(fastifyAutoload, {
2425
dir: path.join(import.meta.dirname, "plugins/custom"),
25-
options: { ...opts }
26+
options: {}
2627
});
2728

2829
// This loads all plugins defined in routes
2930
// define your routes in one of these
30-
fastify.register(fastifyAutoload, {
31+
await fastify.register(fastifyAutoload, {
3132
dir: path.join(import.meta.dirname, "routes"),
3233
autoHooks: true,
3334
cascadeHooks: true,
34-
options: { ...opts }
35+
options: {}
3536
});
3637

3738
fastify.setErrorHandler((err, request, reply) => {
@@ -48,14 +49,10 @@ export default async function serviceApp(
4849
"Unhandled error occurred"
4950
);
5051

51-
reply.code(err.statusCode ?? 500);
52+
const statusCode = err.statusCode ?? 500
53+
reply.code(statusCode);
5254

53-
let message = "Internal Server Error";
54-
if (err.statusCode === 401) {
55-
message = err.message;
56-
}
57-
58-
return { message };
55+
return { message: "Internal Server Error" };
5956
});
6057

6158
// An attacker could search for valid URLs if your 404 error handling is not rate limited.
@@ -84,4 +81,20 @@ export default async function serviceApp(
8481

8582
return { message: "Not Found" };
8683
});
84+
85+
// We setup the SPA
86+
await fastify.register(fastifyVite, function (fastify) {
87+
return {
88+
root: path.resolve(import.meta.dirname, '../'),
89+
dev: fastify.config.FASTIFY_VITE_DEV_MODE,
90+
spa: true
91+
}
92+
});
93+
94+
// Route must match vite "base": https://vitejs.dev/config/shared-options.html#base
95+
fastify.get('/', (req, reply) => {
96+
return reply.html();
97+
});
98+
99+
await fastify.vite.ready();
87100
}

src/client/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function App () {
2+
return (
3+
<p>Welcome to the official Fastify demo!</p>
4+
)
5+
}

src/client/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<link data-rh="true" rel="icon" href="/favicon.ico" />
7+
<title>Fastify demo</title>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/mount.tsx"></script>
12+
</body>
13+
</html>

src/client/mount.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { createRoot } from "react-dom/client";
2+
import { App } from "./App";
3+
4+
const rootElement =
5+
document.getElementById("root") || document.createElement("div");
6+
7+
const root = createRoot(rootElement);
8+
root.render(<App />);

src/client/public/favicon.ico

1.06 KB
Binary file not shown.

src/client/tsconfig.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"compilerOptions": {
3+
"composite": true,
4+
"target": "ES2020",
5+
"useDefineForClassFields": true,
6+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
7+
"module": "ESNext",
8+
"skipLibCheck": true,
9+
10+
/* Bundler mode */
11+
"moduleResolution": "bundler",
12+
"resolveJsonModule": true,
13+
"isolatedModules": true,
14+
"jsx": "react-jsx",
15+
16+
/* Linting */
17+
"strict": true,
18+
"noUnusedLocals": true,
19+
"noUnusedParameters": true,
20+
"noFallthroughCasesInSwitch": true
21+
},
22+
"include": ["**/*"],
23+
"exclude": ["dist"]
24+
}

src/client/vite-env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="vite/client" />

src/plugins/external/1-env.ts renamed to src/plugins/external/env.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ declare module "fastify" {
1111
MYSQL_DATABASE: string;
1212
JWT_SECRET: string;
1313
RATE_LIMIT_MAX: number;
14+
FASTIFY_VITE_DEV_MODE: boolean;
1415
};
1516
}
1617
}
@@ -52,6 +53,12 @@ const schema = {
5253
RATE_LIMIT_MAX: {
5354
type: "number",
5455
default: 100
56+
},
57+
58+
// Frontend
59+
FASTIFY_VITE_DEV_MODE: {
60+
type: "boolean",
61+
default: true
5562
}
5663
}
5764
};

src/routes/home.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

0 commit comments

Comments
 (0)