@sjoblomtobias/relay 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/route.d.ts +94 -0
- package/dist/route.js +79 -0
- package/dist/server.d.ts +40 -0
- package/dist/server.js +67 -0
- package/eslint.config.mts +88 -0
- package/package.json +35 -0
- package/prettier.config.js +11 -0
- package/src/index.ts +7 -0
- package/src/route.ts +136 -0
- package/src/server.ts +83 -0
- package/tsconfig.json +29 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Tobias Sjöblom
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/route.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author Tobias Sjöblom
|
|
3
|
+
* @since 1.0.0
|
|
4
|
+
* @summary Base class for defining routes in an Express application.
|
|
5
|
+
*/
|
|
6
|
+
import { Router } from "express";
|
|
7
|
+
import type { Request, Response, NextFunction } from "express";
|
|
8
|
+
/**
|
|
9
|
+
* Decorator to set the base path and authentication requirement for a route class.
|
|
10
|
+
* @param location - The base path for the route.
|
|
11
|
+
* @param isPrivate - Whether to use authentication middleware.
|
|
12
|
+
* @returns A decorator function.
|
|
13
|
+
*/
|
|
14
|
+
export declare function Location(location: string, isPrivate?: boolean): <T extends {
|
|
15
|
+
new (...args: any[]): object;
|
|
16
|
+
}>(constructor: T) => {
|
|
17
|
+
new (...args: any[]): {};
|
|
18
|
+
} & T;
|
|
19
|
+
/**
|
|
20
|
+
* Decorator to mark a route class as private.
|
|
21
|
+
* @param location - The base path for the route.
|
|
22
|
+
* @returns A decorator function.
|
|
23
|
+
*/
|
|
24
|
+
export declare function Private(location: string): <T extends {
|
|
25
|
+
new (...args: any[]): object;
|
|
26
|
+
}>(constructor: T) => {
|
|
27
|
+
new (...args: any[]): {};
|
|
28
|
+
} & T;
|
|
29
|
+
/**
|
|
30
|
+
* Decorator to mark a route class as public.
|
|
31
|
+
* @param location - The base path for the route.
|
|
32
|
+
* @returns A decorator function.
|
|
33
|
+
*/
|
|
34
|
+
export declare function Public(location: string): <T extends {
|
|
35
|
+
new (...args: any[]): object;
|
|
36
|
+
}>(constructor: T) => {
|
|
37
|
+
new (...args: any[]): {};
|
|
38
|
+
} & T;
|
|
39
|
+
export type Endpoint = {
|
|
40
|
+
path?: string;
|
|
41
|
+
handler: ((req: Request, res: Response, next: NextFunction) => void) | undefined;
|
|
42
|
+
routerMethod: (path: string, ...handlers: Array<(req: Request, res: Response, next: NextFunction) => void>) => void;
|
|
43
|
+
};
|
|
44
|
+
export declare abstract class Route {
|
|
45
|
+
router: Router;
|
|
46
|
+
location: string;
|
|
47
|
+
isPrivate: boolean;
|
|
48
|
+
authMiddleware?: (req: Request, res: Response, next: NextFunction) => void;
|
|
49
|
+
constructor(location: string, isPrivate?: boolean, authMiddleware?: (req: Request, res: Response, next: NextFunction) => void);
|
|
50
|
+
/**
|
|
51
|
+
* Initializes the routes based on the implemented methods in the subclass.
|
|
52
|
+
*/
|
|
53
|
+
protected initializeRoutes(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Returns an array of endpoint configurations.
|
|
56
|
+
* @returns Array of endpoint configurations. Override in subclass to customize endpoint paths.
|
|
57
|
+
*/
|
|
58
|
+
protected getEndpoints(): Array<Endpoint>;
|
|
59
|
+
/**
|
|
60
|
+
* Handles GET requests to retrieve a resource by ID.
|
|
61
|
+
* @param req - The Express request object.
|
|
62
|
+
* @param res - The Express response object.
|
|
63
|
+
* @param next - The next middleware function.
|
|
64
|
+
*/
|
|
65
|
+
protected getById?(req: Request, res: Response, next: NextFunction): void;
|
|
66
|
+
/**
|
|
67
|
+
* Handles GET requests to retrieve a resource.
|
|
68
|
+
* @param req - The Express request object.
|
|
69
|
+
* @param res - The Express response object.
|
|
70
|
+
* @param next - The next middleware function.
|
|
71
|
+
*/
|
|
72
|
+
protected get?(req: Request, res: Response, next: NextFunction): void;
|
|
73
|
+
/**
|
|
74
|
+
* Handles POST requests to create a new resource.
|
|
75
|
+
* @param req - The Express request object.
|
|
76
|
+
* @param res - The Express response object.
|
|
77
|
+
* @param next - The next middleware function.
|
|
78
|
+
*/
|
|
79
|
+
protected post?(req: Request, res: Response, next: NextFunction): void;
|
|
80
|
+
/**
|
|
81
|
+
* Handles PATCH requests to update an existing resource.
|
|
82
|
+
* @param req - The Express request object.
|
|
83
|
+
* @param res - The Express response object.
|
|
84
|
+
* @param next - The next middleware function.
|
|
85
|
+
*/
|
|
86
|
+
protected patch?(req: Request, res: Response, next: NextFunction): void;
|
|
87
|
+
/**
|
|
88
|
+
* Handles DELETE requests to remove a resource.
|
|
89
|
+
* @param req - The Express request object.
|
|
90
|
+
* @param res - The Express response object.
|
|
91
|
+
* @param next - The next middleware function.
|
|
92
|
+
*/
|
|
93
|
+
protected delete?(req: Request, res: Response, next: NextFunction): void;
|
|
94
|
+
}
|
package/dist/route.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author Tobias Sjöblom
|
|
3
|
+
* @since 1.0.0
|
|
4
|
+
* @summary Base class for defining routes in an Express application.
|
|
5
|
+
*/
|
|
6
|
+
import { Router } from "express";
|
|
7
|
+
/**
|
|
8
|
+
* Decorator to set the base path and authentication requirement for a route class.
|
|
9
|
+
* @param location - The base path for the route.
|
|
10
|
+
* @param isPrivate - Whether to use authentication middleware.
|
|
11
|
+
* @returns A decorator function.
|
|
12
|
+
*/
|
|
13
|
+
export function Location(location, isPrivate = false) {
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
+
return function (constructor) {
|
|
16
|
+
return class extends constructor {
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
constructor(...args) {
|
|
19
|
+
super(location, isPrivate, ...args);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Decorator to mark a route class as private.
|
|
26
|
+
* @param location - The base path for the route.
|
|
27
|
+
* @returns A decorator function.
|
|
28
|
+
*/
|
|
29
|
+
export function Private(location) {
|
|
30
|
+
return Location(location, true);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Decorator to mark a route class as public.
|
|
34
|
+
* @param location - The base path for the route.
|
|
35
|
+
* @returns A decorator function.
|
|
36
|
+
*/
|
|
37
|
+
export function Public(location) {
|
|
38
|
+
return Location(location, false);
|
|
39
|
+
}
|
|
40
|
+
export class Route {
|
|
41
|
+
constructor(location, isPrivate = false, authMiddleware) {
|
|
42
|
+
this.router = Router();
|
|
43
|
+
this.location = location;
|
|
44
|
+
this.isPrivate = isPrivate;
|
|
45
|
+
this.authMiddleware = authMiddleware;
|
|
46
|
+
this.initializeRoutes();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Initializes the routes based on the implemented methods in the subclass.
|
|
50
|
+
*/
|
|
51
|
+
initializeRoutes() {
|
|
52
|
+
this.getEndpoints().forEach(({ path, handler, routerMethod }) => {
|
|
53
|
+
if (typeof handler === "function") {
|
|
54
|
+
let handlers;
|
|
55
|
+
if (this.isPrivate && this.authMiddleware) {
|
|
56
|
+
handlers = [this.authMiddleware, handler.bind(this)];
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
handlers = [handler.bind(this)];
|
|
60
|
+
}
|
|
61
|
+
const fullPath = path ? `${this.location}${path}` : `${this.location}`;
|
|
62
|
+
routerMethod(fullPath, ...handlers);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Returns an array of endpoint configurations.
|
|
68
|
+
* @returns Array of endpoint configurations. Override in subclass to customize endpoint paths.
|
|
69
|
+
*/
|
|
70
|
+
getEndpoints() {
|
|
71
|
+
return [
|
|
72
|
+
{ path: "/:id", handler: this.getById, routerMethod: this.router.get.bind(this.router) },
|
|
73
|
+
{ handler: this.get, routerMethod: this.router.get.bind(this.router) },
|
|
74
|
+
{ handler: this.post, routerMethod: this.router.post.bind(this.router) },
|
|
75
|
+
{ path: "/:id", handler: this.patch, routerMethod: this.router.patch.bind(this.router) },
|
|
76
|
+
{ path: "/:id", handler: this.delete, routerMethod: this.router.delete.bind(this.router) }
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author Tobias Sjöblom
|
|
3
|
+
* @since 1.0.0
|
|
4
|
+
* @summary Class representing an Express server that dynamically loads routes from a specified directory.
|
|
5
|
+
*/
|
|
6
|
+
import express from "express";
|
|
7
|
+
import type { RequestHandler } from "express";
|
|
8
|
+
export declare class Server {
|
|
9
|
+
private port;
|
|
10
|
+
private routesDirectory;
|
|
11
|
+
private app;
|
|
12
|
+
private authMiddleware?;
|
|
13
|
+
/**
|
|
14
|
+
* Creates an instance of the Server class.
|
|
15
|
+
* @param port The port number on which the server will listen.
|
|
16
|
+
* @param routesDirectory The directory where route files are located.
|
|
17
|
+
* @param authMiddleware Optional authentication middleware to be used for private routes.
|
|
18
|
+
*/
|
|
19
|
+
constructor(port: number, routesDirectory: string, authMiddleware?: RequestHandler);
|
|
20
|
+
/**
|
|
21
|
+
* Loads all routes from the specified directory.
|
|
22
|
+
* @returns Promise that resolves when all routes are loaded.
|
|
23
|
+
*/
|
|
24
|
+
loadRoutes(): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Adds middleware functions to the Express application.
|
|
27
|
+
* @param handlers - Array of Express request handlers (middleware functions).
|
|
28
|
+
*/
|
|
29
|
+
use(...handlers: Array<express.RequestHandler>): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Gets the Express application instance.
|
|
32
|
+
* @returns The Express application instance.
|
|
33
|
+
*/
|
|
34
|
+
getApp(): express.Express;
|
|
35
|
+
/**
|
|
36
|
+
* Starts the Express server.
|
|
37
|
+
*/
|
|
38
|
+
start(): void;
|
|
39
|
+
setAuthMiddleware(middleware: RequestHandler): void;
|
|
40
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author Tobias Sjöblom
|
|
3
|
+
* @since 1.0.0
|
|
4
|
+
* @summary Class representing an Express server that dynamically loads routes from a specified directory.
|
|
5
|
+
*/
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import express from "express";
|
|
9
|
+
export class Server {
|
|
10
|
+
/**
|
|
11
|
+
* Creates an instance of the Server class.
|
|
12
|
+
* @param port The port number on which the server will listen.
|
|
13
|
+
* @param routesDirectory The directory where route files are located.
|
|
14
|
+
* @param authMiddleware Optional authentication middleware to be used for private routes.
|
|
15
|
+
*/
|
|
16
|
+
constructor(port, routesDirectory, authMiddleware) {
|
|
17
|
+
this.port = port;
|
|
18
|
+
this.routesDirectory = routesDirectory;
|
|
19
|
+
this.app = express();
|
|
20
|
+
this.authMiddleware = authMiddleware;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Loads all routes from the specified directory.
|
|
24
|
+
* @returns Promise that resolves when all routes are loaded.
|
|
25
|
+
*/
|
|
26
|
+
async loadRoutes() {
|
|
27
|
+
const routes = path.join(process.cwd(), this.routesDirectory);
|
|
28
|
+
if (!fs.existsSync(routes)) {
|
|
29
|
+
console.error(`Routes directory "${routes}" does not exist.`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const files = fs.readdirSync(routes);
|
|
33
|
+
await Promise.all(files.map(async (file) => {
|
|
34
|
+
const modulePath = path.join(routes, file);
|
|
35
|
+
const module = await import(modulePath);
|
|
36
|
+
const RouteClass = module.default;
|
|
37
|
+
// Pass authMiddleware to Route constructor
|
|
38
|
+
const routeInstance = new RouteClass(this.authMiddleware);
|
|
39
|
+
this.app.use(routeInstance.router);
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Adds middleware functions to the Express application.
|
|
44
|
+
* @param handlers - Array of Express request handlers (middleware functions).
|
|
45
|
+
*/
|
|
46
|
+
async use(...handlers) {
|
|
47
|
+
this.app.use(handlers);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Gets the Express application instance.
|
|
51
|
+
* @returns The Express application instance.
|
|
52
|
+
*/
|
|
53
|
+
getApp() {
|
|
54
|
+
return this.app;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Starts the Express server.
|
|
58
|
+
*/
|
|
59
|
+
start() {
|
|
60
|
+
this.app.listen(this.port, () => {
|
|
61
|
+
console.log(`⚡ Server running on port: ${this.port}`);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
setAuthMiddleware(middleware) {
|
|
65
|
+
this.authMiddleware = middleware;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import { defineConfig } from "eslint/config";
|
|
3
|
+
import importPlugin from "eslint-plugin-import";
|
|
4
|
+
import prettierConfig from "eslint-plugin-prettier/recommended";
|
|
5
|
+
import globals from "globals";
|
|
6
|
+
import tseslint from "typescript-eslint";
|
|
7
|
+
|
|
8
|
+
export default defineConfig([
|
|
9
|
+
{
|
|
10
|
+
languageOptions: {
|
|
11
|
+
globals: { ...globals.node }
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
js.configs.recommended,
|
|
15
|
+
tseslint.configs.recommended,
|
|
16
|
+
prettierConfig,
|
|
17
|
+
{
|
|
18
|
+
files: ["**/*.{ts,mts,cts}"],
|
|
19
|
+
plugins: {
|
|
20
|
+
"@typescript-eslint": tseslint.plugin,
|
|
21
|
+
import: importPlugin
|
|
22
|
+
},
|
|
23
|
+
languageOptions: {
|
|
24
|
+
parser: tseslint.parser,
|
|
25
|
+
parserOptions: { ecmaVersion: "latest", sourceType: "module" }
|
|
26
|
+
},
|
|
27
|
+
settings: {
|
|
28
|
+
"import/resolver": {
|
|
29
|
+
typescript: {
|
|
30
|
+
alwaysTryTypes: true,
|
|
31
|
+
project: "./tsconfig.json"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
rules: {
|
|
36
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
37
|
+
"no-unused-vars": "off",
|
|
38
|
+
"@typescript-eslint/no-unused-vars": [
|
|
39
|
+
"error",
|
|
40
|
+
{
|
|
41
|
+
vars: "all",
|
|
42
|
+
args: "after-used",
|
|
43
|
+
caughtErrors: "all",
|
|
44
|
+
argsIgnorePattern: "^_",
|
|
45
|
+
caughtErrorsIgnorePattern: "^_",
|
|
46
|
+
destructuredArrayIgnorePattern: "^_",
|
|
47
|
+
varsIgnorePattern: "^_"
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"@typescript-eslint/consistent-type-imports": ["error", { prefer: "type-imports" }],
|
|
51
|
+
"capitalized-comments": ["error", "always", { ignoreConsecutiveComments: true }],
|
|
52
|
+
eqeqeq: ["error", "always"],
|
|
53
|
+
"no-console": "warn",
|
|
54
|
+
curly: ["error", "all"],
|
|
55
|
+
"no-restricted-imports": [
|
|
56
|
+
"error",
|
|
57
|
+
{
|
|
58
|
+
patterns: [".*"]
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"import/order": [
|
|
62
|
+
"error",
|
|
63
|
+
{
|
|
64
|
+
groups: ["builtin", "external", "internal", "parent", "sibling", "index", "object", "type"],
|
|
65
|
+
pathGroups: [
|
|
66
|
+
{
|
|
67
|
+
pattern: "@/**",
|
|
68
|
+
group: "internal",
|
|
69
|
+
position: "before"
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
pathGroupsExcludedImportTypes: ["builtin"],
|
|
73
|
+
"newlines-between": "never",
|
|
74
|
+
alphabetize: {
|
|
75
|
+
order: "asc",
|
|
76
|
+
caseInsensitive: true
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
files: ["test/**/*.{ts,mts,cts}"],
|
|
84
|
+
rules: {
|
|
85
|
+
"no-restricted-imports": "off"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
]);
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sjoblomtobias/relay",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc && tsc-alias"
|
|
8
|
+
},
|
|
9
|
+
"author": "Tobias Sjöblom",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"description": "",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"express": "^5.1.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@eslint/js": "^9.35.0",
|
|
17
|
+
"@types/express": "^5.0.3",
|
|
18
|
+
"@types/node": "^24.5.2",
|
|
19
|
+
"@typescript-eslint/eslint-plugin": "^8.44.0",
|
|
20
|
+
"@typescript-eslint/parser": "^8.42.0",
|
|
21
|
+
"eslint": "^9.35.0",
|
|
22
|
+
"eslint-config-prettier": "^10.1.8",
|
|
23
|
+
"eslint-import-resolver-typescript": "^4.4.4",
|
|
24
|
+
"eslint-plugin-import": "^2.32.0",
|
|
25
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
26
|
+
"globals": "^16.4.0",
|
|
27
|
+
"jiti": "^2.5.1",
|
|
28
|
+
"nodemon": "^3.1.10",
|
|
29
|
+
"prettier": "^3.6.2",
|
|
30
|
+
"tsc-alias": "^1.8.16",
|
|
31
|
+
"tsx": "^4.20.5",
|
|
32
|
+
"typescript": "^5.9.2",
|
|
33
|
+
"typescript-eslint": "^8.44.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.ts
ADDED
package/src/route.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author Tobias Sjöblom
|
|
3
|
+
* @since 1.0.0
|
|
4
|
+
* @summary Base class for defining routes in an Express application.
|
|
5
|
+
*/
|
|
6
|
+
import { Router } from "express";
|
|
7
|
+
import type { Request, Response, NextFunction } from "express";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Decorator to set the base path and authentication requirement for a route class.
|
|
11
|
+
* @param location - The base path for the route.
|
|
12
|
+
* @param isPrivate - Whether to use authentication middleware.
|
|
13
|
+
* @returns A decorator function.
|
|
14
|
+
*/
|
|
15
|
+
export function Location(location: string, isPrivate: boolean = false) {
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
return function <T extends { new (...args: any[]): object }>(constructor: T) {
|
|
18
|
+
return class extends constructor {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
constructor(...args: any[]) {
|
|
21
|
+
super(location, isPrivate, ...args);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Decorator to mark a route class as private.
|
|
29
|
+
* @param location - The base path for the route.
|
|
30
|
+
* @returns A decorator function.
|
|
31
|
+
*/
|
|
32
|
+
export function Private(location: string) {
|
|
33
|
+
return Location(location, true);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Decorator to mark a route class as public.
|
|
38
|
+
* @param location - The base path for the route.
|
|
39
|
+
* @returns A decorator function.
|
|
40
|
+
*/
|
|
41
|
+
export function Public(location: string) {
|
|
42
|
+
return Location(location, false);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type Endpoint = {
|
|
46
|
+
path?: string;
|
|
47
|
+
handler: ((req: Request, res: Response, next: NextFunction) => void) | undefined;
|
|
48
|
+
routerMethod: (path: string, ...handlers: Array<(req: Request, res: Response, next: NextFunction) => void>) => void;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export abstract class Route {
|
|
52
|
+
public router: Router;
|
|
53
|
+
public location: string;
|
|
54
|
+
public isPrivate: boolean;
|
|
55
|
+
public authMiddleware?: (req: Request, res: Response, next: NextFunction) => void;
|
|
56
|
+
|
|
57
|
+
constructor(location: string, isPrivate: boolean = false, authMiddleware?: (req: Request, res: Response, next: NextFunction) => void) {
|
|
58
|
+
this.router = Router();
|
|
59
|
+
this.location = location;
|
|
60
|
+
this.isPrivate = isPrivate;
|
|
61
|
+
this.authMiddleware = authMiddleware;
|
|
62
|
+
this.initializeRoutes();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Initializes the routes based on the implemented methods in the subclass.
|
|
67
|
+
*/
|
|
68
|
+
protected initializeRoutes() {
|
|
69
|
+
this.getEndpoints().forEach(({ path, handler, routerMethod }) => {
|
|
70
|
+
if (typeof handler === "function") {
|
|
71
|
+
let handlers;
|
|
72
|
+
if (this.isPrivate && this.authMiddleware) {
|
|
73
|
+
handlers = [this.authMiddleware, handler.bind(this)];
|
|
74
|
+
} else {
|
|
75
|
+
handlers = [handler.bind(this)];
|
|
76
|
+
}
|
|
77
|
+
const fullPath = path ? `${this.location}${path}` : `${this.location}`;
|
|
78
|
+
routerMethod(fullPath, ...handlers);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Returns an array of endpoint configurations.
|
|
85
|
+
* @returns Array of endpoint configurations. Override in subclass to customize endpoint paths.
|
|
86
|
+
*/
|
|
87
|
+
protected getEndpoints(): Array<Endpoint> {
|
|
88
|
+
return [
|
|
89
|
+
{ path: "/:id", handler: this.getById, routerMethod: this.router.get.bind(this.router) },
|
|
90
|
+
{ handler: this.get, routerMethod: this.router.get.bind(this.router) },
|
|
91
|
+
{ handler: this.post, routerMethod: this.router.post.bind(this.router) },
|
|
92
|
+
{ path: "/:id", handler: this.patch, routerMethod: this.router.patch.bind(this.router) },
|
|
93
|
+
{ path: "/:id", handler: this.delete, routerMethod: this.router.delete.bind(this.router) }
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Handles GET requests to retrieve a resource by ID.
|
|
99
|
+
* @param req - The Express request object.
|
|
100
|
+
* @param res - The Express response object.
|
|
101
|
+
* @param next - The next middleware function.
|
|
102
|
+
*/
|
|
103
|
+
protected getById?(req: Request, res: Response, next: NextFunction): void;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Handles GET requests to retrieve a resource.
|
|
107
|
+
* @param req - The Express request object.
|
|
108
|
+
* @param res - The Express response object.
|
|
109
|
+
* @param next - The next middleware function.
|
|
110
|
+
*/
|
|
111
|
+
protected get?(req: Request, res: Response, next: NextFunction): void;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Handles POST requests to create a new resource.
|
|
115
|
+
* @param req - The Express request object.
|
|
116
|
+
* @param res - The Express response object.
|
|
117
|
+
* @param next - The next middleware function.
|
|
118
|
+
*/
|
|
119
|
+
protected post?(req: Request, res: Response, next: NextFunction): void;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Handles PATCH requests to update an existing resource.
|
|
123
|
+
* @param req - The Express request object.
|
|
124
|
+
* @param res - The Express response object.
|
|
125
|
+
* @param next - The next middleware function.
|
|
126
|
+
*/
|
|
127
|
+
protected patch?(req: Request, res: Response, next: NextFunction): void;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Handles DELETE requests to remove a resource.
|
|
131
|
+
* @param req - The Express request object.
|
|
132
|
+
* @param res - The Express response object.
|
|
133
|
+
* @param next - The next middleware function.
|
|
134
|
+
*/
|
|
135
|
+
protected delete?(req: Request, res: Response, next: NextFunction): void;
|
|
136
|
+
}
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author Tobias Sjöblom
|
|
3
|
+
* @since 1.0.0
|
|
4
|
+
* @summary Class representing an Express server that dynamically loads routes from a specified directory.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import express from "express";
|
|
10
|
+
import type { RequestHandler } from "express";
|
|
11
|
+
|
|
12
|
+
export class Server {
|
|
13
|
+
private port: number;
|
|
14
|
+
private routesDirectory: string;
|
|
15
|
+
private app: express.Express;
|
|
16
|
+
private authMiddleware?: RequestHandler;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates an instance of the Server class.
|
|
20
|
+
* @param port The port number on which the server will listen.
|
|
21
|
+
* @param routesDirectory The directory where route files are located.
|
|
22
|
+
* @param authMiddleware Optional authentication middleware to be used for private routes.
|
|
23
|
+
*/
|
|
24
|
+
constructor(port: number, routesDirectory: string, authMiddleware?: RequestHandler) {
|
|
25
|
+
this.port = port;
|
|
26
|
+
this.routesDirectory = routesDirectory;
|
|
27
|
+
this.app = express();
|
|
28
|
+
this.authMiddleware = authMiddleware;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Loads all routes from the specified directory.
|
|
33
|
+
* @returns Promise that resolves when all routes are loaded.
|
|
34
|
+
*/
|
|
35
|
+
public async loadRoutes(): Promise<void> {
|
|
36
|
+
const routes = path.join(process.cwd(), this.routesDirectory);
|
|
37
|
+
if (!fs.existsSync(routes)) {
|
|
38
|
+
console.error(`Routes directory "${routes}" does not exist.`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const files = fs.readdirSync(routes);
|
|
42
|
+
await Promise.all(
|
|
43
|
+
files.map(async (file) => {
|
|
44
|
+
const modulePath = path.join(routes, file);
|
|
45
|
+
const module = await import(modulePath);
|
|
46
|
+
const RouteClass = module.default;
|
|
47
|
+
|
|
48
|
+
// Pass authMiddleware to Route constructor
|
|
49
|
+
const routeInstance = new RouteClass(this.authMiddleware);
|
|
50
|
+
this.app.use(routeInstance.router);
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Adds middleware functions to the Express application.
|
|
57
|
+
* @param handlers - Array of Express request handlers (middleware functions).
|
|
58
|
+
*/
|
|
59
|
+
public async use(...handlers: Array<express.RequestHandler>): Promise<void> {
|
|
60
|
+
this.app.use(handlers);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Gets the Express application instance.
|
|
65
|
+
* @returns The Express application instance.
|
|
66
|
+
*/
|
|
67
|
+
public getApp(): express.Express {
|
|
68
|
+
return this.app;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Starts the Express server.
|
|
73
|
+
*/
|
|
74
|
+
public start(): void {
|
|
75
|
+
this.app.listen(this.port, () => {
|
|
76
|
+
console.log(`⚡ Server running on port: ${this.port}`);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public setAuthMiddleware(middleware: RequestHandler) {
|
|
81
|
+
this.authMiddleware = middleware;
|
|
82
|
+
}
|
|
83
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"baseUrl": ".",
|
|
4
|
+
"rootDir": "src",
|
|
5
|
+
"paths": {
|
|
6
|
+
"@/*": ["./src/*"]
|
|
7
|
+
},
|
|
8
|
+
"moduleResolution": "node",
|
|
9
|
+
"module": "esnext",
|
|
10
|
+
"target": "ES2020",
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"strict": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"outDir": "dist",
|
|
16
|
+
"incremental": false,
|
|
17
|
+
"typeRoots": ["./node_modules/@types", "./src/types"],
|
|
18
|
+
"types": ["node"],
|
|
19
|
+
"experimentalDecorators": true,
|
|
20
|
+
"emitDecoratorMetadata": true,
|
|
21
|
+
"declaration": true
|
|
22
|
+
},
|
|
23
|
+
"include": ["src/**/*"],
|
|
24
|
+
"exclude": ["node_modules", "dist", "src/test"],
|
|
25
|
+
"tsc-alias": {
|
|
26
|
+
"resolveFullPaths": true,
|
|
27
|
+
"verbose": true
|
|
28
|
+
}
|
|
29
|
+
}
|