initial comit

This commit is contained in:
creations 2024-08-08 08:20:58 -04:00
commit 8f843dc27d
13 changed files with 674 additions and 0 deletions

176
.gitignore vendored Normal file
View file

@ -0,0 +1,176 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Caches
.cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store
/.vscode

21
LICENCE Normal file
View file

@ -0,0 +1,21 @@
MIT License
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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

20
README.md Normal file
View file

@ -0,0 +1,20 @@
# 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.
## Getting Started
To start the project, you can run either `start.bat` or `start.sh`.
## Cleanup
You can clean up your project with the following commands:
- **Windows:** `start.bat clean`
- **Unix/Linux:** `start.sh clean`
Have fun - **Creations** // **creations@creations.works**
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

43
config/environment.ts Normal file
View file

@ -0,0 +1,43 @@
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;
}
};
}
const __dirname : string = join(dirname(fileURLToPath(import.meta.url)), "..");
const environment : IEnvironment = {
development: process.argv.includes("--development"),
fastify: {
host: "0.0.0.0",
port: 7788
},
paths: {
src: __dirname,
www: {
root: join(__dirname, "src", "www"),
views: join(__dirname, "src", "www", "views"),
public: join(__dirname, "src", "www", "public")
}
}
};
export default environment;
export { environment };

21
package.json Normal file
View file

@ -0,0 +1,21 @@
{
"name": "ts_fastify_example",
"module": "src/index.ts",
"type": "module",
"scripts": {
"dev": "bun run --watch src/index.ts --development"
},
"devDependencies": {
"@types/bun": "latest",
"@types/ejs": "^3.1.5"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"@fastify/static": "^7.0.4",
"@fastify/view": "^9.1.0",
"ejs": "^3.1.10",
"fastify": "^4.28.1"
}
}

135
src/fastify/manager.ts Normal file
View file

@ -0,0 +1,135 @@
import Fastify from "fastify";
import ejs from "ejs";
import { IncomingMessage, Server, ServerResponse } from "http";
import { readdir, stat } from "fs/promises";
import { join } from "path";
import { environment } from "../../config/environment";
// types
import type { FastifyInstance, FastifyReply, FastifyRequest, RouteShorthandMethod } from "fastify";
import type { Stats } from "fs";
import type { AddressInfo } from "net";
// official plugins
import { fastifyView } from "@fastify/view";
import { fastifyStatic } from "@fastify/static";
//custom plugins
import faviconPlugin from "./plugins/favicon";
class FastifyManager {
private readonly port : number = environment.fastify.port;
private readonly server : FastifyInstance<Server, IncomingMessage, ServerResponse>;
constructor() {
this.server = Fastify({
logger: false,
trustProxy: !environment.development,
ignoreTrailingSlash: true
});
}
private async registerPlugins() : Promise<void> {
console.log(environment.paths.www.views);
// official plugins
this.server.register(fastifyView, {
engine: {
ejs
},
root: environment.paths.www.views,
includeViewExtension: true
});
this.server.register(fastifyStatic, {
root: environment.paths.www.public,
prefix: "/public/",
prefixAvoidTrailingSlash: true
});
// custom plugins
this.server.register(faviconPlugin, { path: join(environment.paths.www.public, "assets", "favicon.ico") });
}
// dynamic route loading
private async loadRoutes(): Promise<void> {
// 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]
];
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);
} catch (err) {
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 });
}
} catch (err) {
console.error("Failed to load route", file, "error:", err);
}
}
}
}
private async readDirRecursive(dir: string, recursive: boolean): Promise<string[]> {
let results: string[] = [];
const list : string[] = await readdir(dir);
for (const file of list) {
const filePath : string = join(dir, file);
const statObj : Stats = await stat(filePath);
if (statObj && statObj.isDirectory()) {
if (recursive) {
results = results.concat(await this.readDirRecursive(filePath, recursive));
}
} else {
results.push(filePath);
}
}
return results;
}
public async start() : Promise<void> {
try {
await this.registerPlugins();
await this.loadRoutes();
await this.server.listen({
port: environment.fastify.port,
host: environment.fastify.host
});
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 resolvedScheme: string = resolvedAddress[0] === "localhost" ? "http" : "https";
return [...resolvedAddress, resolvedScheme];
})();
console.info("Started Listening on", `${scheme}://${_address}:${_port}`);
console.info("Registered routes: ", "\n", this.server.printRoutes());
} catch (error) {
console.error("Failed to start Fastify server", error);
}
}
}
const fastifyManager = new FastifyManager();
export default fastifyManager;
export { fastifyManager };

View file

@ -0,0 +1,35 @@
import { readFile } from 'fs/promises';
import type { FastifyInstance, FastifyPluginAsync } from 'fastify';
import type { FastifyRequest } from "fastify/types/request";
import type { FastifyReply } from "fastify/types/reply";
interface FaviconOptions {
path: string;
}
const faviconPlugin: FastifyPluginAsync<FaviconOptions> = async (fastify: FastifyInstance, options: FaviconOptions): Promise<void> => {
let faviconData: Buffer | null = null;
try {
faviconData = await readFile(options.path);
} catch (err) {
console.error("Error reading favicon:", err);
}
fastify.get('/favicon.ico', async (_request: FastifyRequest, reply: FastifyReply): Promise<void> => {
if (faviconData) {
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'
});
}
});
};
export default faviconPlugin as FastifyPluginAsync<FaviconOptions>;

15
src/index.ts Normal file
View file

@ -0,0 +1,15 @@
import { environment } from "../config/environment";
import { fastifyManager } from "./fastify/manager";
class Index {
public static async main() : Promise<void> {
const main = new Index();
await main.start();
};
public async start() : Promise<void> {
await fastifyManager.start();
}
}
const index : Promise<void> = Index.main();

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

17
src/www/routes/index.ts Normal file
View file

@ -0,0 +1,17 @@
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
const route : (fastify: FastifyInstance) => Promise<void> = async (fastify: FastifyInstance) : Promise<void> => {
const handleRequest: (request : FastifyRequest, reply : FastifyReply) => Promise<void> = async (request : FastifyRequest, reply : FastifyReply) : Promise<void> => {
return reply.view("index.ejs", {
h1: "Hello, World!",
p: "This is a placeholder page.",
discord: "https://discord.gg/DxFhhbr2pm",
});
}
fastify.get("/", handleRequest);
}
export default route as (fastify: FastifyInstance) => Promise<void>;

14
src/www/views/index.ejs Normal file
View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1><%= h1 %></h1>
<p><%= p %></p>
<p>join this cool discord: <a href="<%= discord %>">click here</a></p>
</body>
</html>

150
start.bat Normal file
View file

@ -0,0 +1,150 @@
@echo off
setlocal enabledelayedexpansion
for %%I in ("%~dp0.") do set "SCRIPT_DIR=%%~fI"
cd /d "!SCRIPT_DIR!"
if not exist package.json (
echo package.json not found in !SCRIPT_DIR!
exit /b 1
)
set "PKG_MANAGER=bun"
set "MODE=dev"
set "next_is_value=0"
set "last_key="
for %%a in (%*) do (
set "arg=%%a"
if "!next_is_value!"=="1" (
if "!last_key!"=="manager" (
set "PKG_MANAGER=%%a"
)
if "!last_key!"=="mode" (
set "MODE=%%a"
)
set "next_is_value=0"
)
if "!arg!"=="clean" (
goto cleanup
)
if "!arg!"=="cleanup" (
goto cleanup
)
if "!arg!"=="manager" (
set "last_key=manager"
set "next_is_value=1"
)
if "!arg!"=="mode" (
set "last_key=mode"
set "next_is_value=1"
)
)
set "ALLOWED_MANAGERS=bun npm yarn pnpm"
set "ALLOWED_MODES=dev prod development production"
set "manager_allowed=0"
for %%a in (!ALLOWED_MANAGERS!) do (
if "!PKG_MANAGER!"=="%%a" (
set "manager_allowed=1"
)
)
set "mode_allowed=0"
for %%a in (!ALLOWED_MODES!) do (
if "!MODE!"=="%%a" (
set "mode_allowed=1"
)
)
if "!manager_allowed!"=="0" (
echo !PKG_MANAGER! is not a valid package manager, please use one of the following: !ALLOWED_MANAGERS!
exit /b 1
)
if "!mode_allowed!"=="0" (
echo !MODE! is not a valid mode, please use one of the following: !ALLOWED_MODES!
exit /b 1
)
echo Using package manager: !PKG_MANAGER!
echo Mode: !MODE!
set "NPM_MARKER=node_modules/.npm_used"
set "YARN_MARKER=node_modules/.yarn_used"
set "PNPM_MARKER=node_modules/.pnpm_used"
set "BUN_MARKER=node_modules/.bun_used"
if "!PKG_MANAGER!"=="npm" (
set "INSTALL_CMD=npm install"
set "RUN_CMD=npm run !MODE!"
set "CURRENT_MARKER=!NPM_MARKER!"
) else if "!PKG_MANAGER!"=="yarn" (
set "INSTALL_CMD=yarn"
set "RUN_CMD=yarn !MODE!"
set "CURRENT_MARKER=!YARN_MARKER!"
) else if "!PKG_MANAGER!"=="pnpm" (
set "INSTALL_CMD=pnpm install"
set "RUN_CMD=pnpm !MODE!"
set "CURRENT_MARKER=!PNPM_MARKER!"
) else if "!PKG_MANAGER!"=="bun" (
set "INSTALL_CMD=bun install"
set "RUN_CMD=bun !MODE!"
set "CURRENT_MARKER=!BUN_MARKER!"
)
if exist "!NPM_MARKER!" if not "!CURRENT_MARKER!"=="!NPM_MARKER!" goto cleanup
if exist "!YARN_MARKER!" if not "!CURRENT_MARKER!"=="!YARN_MARKER!" goto cleanup
if exist "!PNPM_MARKER!" if not "!CURRENT_MARKER!"=="!PNPM_MARKER!" goto cleanup
if exist "!BUN_MARKER!" if not "!CURRENT_MARKER!"=="!BUN_MARKER!" goto cleanup
goto run
:cleanup
set "files_to_delete=package-lock.json yarn.lock pnpm-lock.yaml bun.lockb"
set "folders_to_delete=node_modules dist logs"
set "markers_to_delete=node_modules/.npm_used node_modules/.yarn_used node_modules/.pnpm_used node_modules/.bun_used"
echo Cleaning up files and folders...
for %%a in (!files_to_delete!) do (
if exist "%%a" (
echo Found %%a in !SCRIPT_DIR! - deleting...
del "%%a"
)
)
for %%a in (!folders_to_delete!) do (
if exist "%%a" (
echo Found %%a in !SCRIPT_DIR! - deleting...
rmdir /s /q "%%a"
)
)
for %%a in (!markers_to_delete!) do (
if exist "%%a" del "%%a"
)
if defined RUN_CMD (
goto run
)
exit /b 0
:run
if defined RUN_CMD (
echo Running !RUN_CMD!...
!INSTALL_CMD!
!RUN_CMD!
) else (
echo No run command set, exiting...
)
exit /b 0

27
tsconfig.json Normal file
View file

@ -0,0 +1,27 @@
{
"compilerOptions": {
// Enable latest features
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}