diff --git a/.gitignore b/.gitignore index 0c54702..db81e2f 100644 --- a/.gitignore +++ b/.gitignore @@ -90,7 +90,7 @@ web_modules/ .node_repl_history -# Output of 'npm pack' +# Output of "npm pack" *.tgz diff --git a/LICENCE b/LICENCE index 94d3a0d..80c8258 100644 --- a/LICENCE +++ b/LICENCE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Creation's // [creations@creations.works] +Copyright (c) 2024 Creation"s // [creations@creations.works] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 414cf33..3791e41 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TS Fastify Example -This is just an example/template to get you started with Fastify and TypeScript. It starts off using EJS, but you can use something else if you'd like. +This is just an example/template to get you started with Fastify and TypeScript. It starts off using EJS, but you can use something else if you"d like. ## Getting Started diff --git a/bun.lockb b/bun.lockb new file mode 100644 index 0000000..1170bf3 Binary files /dev/null and b/bun.lockb differ diff --git a/config/environment.ts b/config/environment.ts index 5643cb5..8d76e0c 100644 --- a/config/environment.ts +++ b/config/environment.ts @@ -1,23 +1,7 @@ import { dirname, join } from "path"; import { fileURLToPath } from "url"; -export interface IEnvironment { - development: boolean; - - fastify: { - host: string; - port: number; - }; - - paths: { - src: string; - www: { - root: string; - views: string; - public: string; - } - }; -} +import type{ IEnvironment } from "../interfaces/environment"; const __dirname : string = join(dirname(fileURLToPath(import.meta.url)), ".."); diff --git a/interfaces/environment.ts b/interfaces/environment.ts new file mode 100644 index 0000000..8dac31d --- /dev/null +++ b/interfaces/environment.ts @@ -0,0 +1,17 @@ +export interface IEnvironment { + development: boolean; + + fastify: { + host: string; + port: number; + }; + + paths: { + src: string; + www: { + root: string; + views: string; + public: string; + } + }; +} \ No newline at end of file diff --git a/interfaces/routes.ts b/interfaces/routes.ts new file mode 100644 index 0000000..40345cd --- /dev/null +++ b/interfaces/routes.ts @@ -0,0 +1,7 @@ +import type { TMethod } from "../types/routes"; + +export interface IRouteInfo { + enabled: boolean; + path: string; + method: TMethod | TMethod[]; +} \ No newline at end of file diff --git a/src/fastify/manager.ts b/src/fastify/manager.ts index 6790b8e..8210181 100644 --- a/src/fastify/manager.ts +++ b/src/fastify/manager.ts @@ -8,7 +8,7 @@ import { join } from "path"; import { environment } from "../../config/environment"; // types -import type { FastifyInstance, FastifyReply, FastifyRequest, RouteShorthandMethod } from "fastify"; +import type { FastifyInstance } from "fastify"; import type { Stats } from "fs"; import type { AddressInfo } from "net"; @@ -31,8 +31,6 @@ class FastifyManager { }); } private async registerPlugins() : Promise { - console.log(environment.paths.www.views); - // official plugins this.server.register(fastifyView, { engine: { @@ -55,14 +53,13 @@ class FastifyManager { // dynamic route loading private async loadRoutes(): Promise { // you can specify a route path, prefix, and whether to load routes recursively e.g. ["routes", "/", true] will load all routes in the /routes directory with the prefix / and recursively load all routes in subdirectories - const routePaths: [string, string, boolean][] = [ - ["routes", '/', false] + ["routes", "/", false] ]; - + for (const [routePath, prefix, recursive] of routePaths) { const modifiedRoutePath = join(environment.paths.www.root, routePath); - + let files: string[]; try { files = await this.readDirRecursive(modifiedRoutePath, recursive); @@ -70,17 +67,44 @@ class FastifyManager { console.error("Failed to read route directory", modifiedRoutePath, "error:", err); return; } - + for (const file of files) { try { - const route = await import(file); - if (route.default) { - this.server.register(route.default, { prefix }); + const routeModule = await import(file); + const { default: routeData } = routeModule; + + if ( !routeData || !routeData.routeInfo || !routeData.route ) { + console.error(`Failed to load route from ${file}:`, "Route data is missing"); + continue; + } + + if (routeData.routeInfo.enabled && routeData.route) { + const { routeInfo, route } = routeData; + + let routePath = routeInfo.path || "/"; + + // Handle prefix and leading/trailing slashes + if (prefix) { + routePath = routePath === "/" ? prefix : join(prefix, routePath); + } + + // Normalize the path to avoid duplicate slashes + routePath = routePath.replace(/\/+/g, "/"); + + const methods = Array.isArray(routeInfo.method) ? routeInfo.method : [routeInfo.method]; + + for (const method of methods) { + this.server.route({ + method, + url: routePath, + handler: route + }); + } } } catch (err) { - console.error("Failed to load route", file, "error:", err); + console.error(`Failed to load route from ${file}:`, err); } - } + } } } @@ -115,7 +139,7 @@ class FastifyManager { const [_address, _port, scheme]: [string, number, string] = (() : [string, number, string] => { const address: string | AddressInfo | null = this.server.server.address(); - const resolvedAddress: [string, number] = (address && typeof address === 'object') ? [( address.address.startsWith("::") || address.address === "0.0.0.0") ? "localhost" : address.address, address.port] : ["localhost", this.port]; + const resolvedAddress: [string, number] = (address && typeof address === "object") ? [( address.address.startsWith("::") || address.address === "0.0.0.0") ? "localhost" : address.address, address.port] : ["localhost", this.port]; const resolvedScheme: string = resolvedAddress[0] === "localhost" ? "http" : "https"; return [...resolvedAddress, resolvedScheme]; diff --git a/src/fastify/plugins/favicon.ts b/src/fastify/plugins/favicon.ts index f2b6fc8..4e8ae59 100644 --- a/src/fastify/plugins/favicon.ts +++ b/src/fastify/plugins/favicon.ts @@ -1,6 +1,6 @@ -import { readFile } from 'fs/promises'; +import { readFile } from "fs/promises"; -import type { FastifyInstance, FastifyPluginAsync } from 'fastify'; +import type { FastifyInstance, FastifyPluginAsync } from "fastify"; import type { FastifyRequest } from "fastify/types/request"; import type { FastifyReply } from "fastify/types/reply"; @@ -17,16 +17,16 @@ const faviconPlugin: FastifyPluginAsync = async (fastify: Fastif console.error("Error reading favicon:", err); } - fastify.get('/favicon.ico', async (_request: FastifyRequest, reply: FastifyReply): Promise => { + fastify.get("/favicon.ico", async (_request: FastifyRequest, reply: FastifyReply): Promise => { if (faviconData) { - reply.header('Content-Type', 'image/x-icon') - .header('Cache-Control', 'public, max-age=86400') // 1 day + reply.header("Content-Type", "image/x-icon") + .header("Cache-Control", "public, max-age=86400") // 1 day .send(faviconData); } else { reply.status(404).send({ code: 404, - error: 'FILE_NOT_FOUND', - message: 'Favicon not found' + error: "FILE_NOT_FOUND", + message: "Favicon not found" }); } }); diff --git a/src/www/routes/index.ts b/src/www/routes/index.ts index cfe569e..9eee2f6 100644 --- a/src/www/routes/index.ts +++ b/src/www/routes/index.ts @@ -1,17 +1,18 @@ -import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; +import type { FastifyReply, FastifyRequest } from "fastify"; +import type { IRouteInfo } from "../../../interfaces/routes"; -const route : (fastify: FastifyInstance) => Promise = async (fastify: FastifyInstance) : Promise => { - const handleRequest: (request : FastifyRequest, reply : FastifyReply) => Promise = async (request : FastifyRequest, reply : FastifyReply) : Promise => { +const routeInfo: IRouteInfo = { + enabled: true, + path: "/", + method: "GET" +}; - return reply.view("index.ejs", { - h1: "Hello, World!", - p: "This is a placeholder page.", - discord: "https://discord.gg/DxFhhbr2pm", - }); - - } - - fastify.get("/", handleRequest); +async function route(request : FastifyRequest, reply : FastifyReply) : Promise { + return reply.viewAsync("index", { + h1: "Hello, World!", + p: "This is a placeholder page.", + discord: "https://discord.gg/DxFhhbr2pm", + }); } -export default route as (fastify: FastifyInstance) => Promise; +export default { routeInfo, route } \ No newline at end of file diff --git a/types/routes.ts b/types/routes.ts new file mode 100644 index 0000000..4c8e533 --- /dev/null +++ b/types/routes.ts @@ -0,0 +1 @@ +export type TMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS" | "HEAD"; \ No newline at end of file