@mathisalarcon/express-next 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/dist/index.js +82 -0
- package/package.json +23 -0
- package/src/index.ts +108 -0
- package/src/types/express.d.ts +7 -0
- package/tsconfig.json +21 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import express, { Router } from "express";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
export class ExpressNext {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.app = express();
|
|
8
|
+
this.root = options.root;
|
|
9
|
+
this.port = options.port ?? 3000;
|
|
10
|
+
}
|
|
11
|
+
set(key, value) {
|
|
12
|
+
this.app.set(key, value);
|
|
13
|
+
}
|
|
14
|
+
use(middleware) {
|
|
15
|
+
this.app.use(middleware);
|
|
16
|
+
}
|
|
17
|
+
async start() {
|
|
18
|
+
await this.handleRoute(this.app, this.root);
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
this.app.listen(this.port, (err) => {
|
|
21
|
+
if (err instanceof Error) {
|
|
22
|
+
// If port is in use, still resolve the promise
|
|
23
|
+
console.error("Couldn't start server:", err.message);
|
|
24
|
+
console.log(`Port ${this.port} might be in use. Trying with port ${++this.port}...`);
|
|
25
|
+
this.start().then(() => resolve());
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
console.log(`Server is running on port ${this.port}`);
|
|
29
|
+
resolve();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async handleRoute(router, dir) {
|
|
35
|
+
const files = fs
|
|
36
|
+
.readdirSync(dir)
|
|
37
|
+
.filter((file) => fs.statSync(path.join(dir, file)).isFile() &&
|
|
38
|
+
(file === "middleware.js" ||
|
|
39
|
+
file === "middleware.ts" ||
|
|
40
|
+
file === "route.js" ||
|
|
41
|
+
file === "route.ts"))
|
|
42
|
+
.sort((a, b) => {
|
|
43
|
+
if (a === "middleware.js" || a === "middleware.ts")
|
|
44
|
+
return -1;
|
|
45
|
+
if (b === "middleware.js" || b === "middleware.ts")
|
|
46
|
+
return 1;
|
|
47
|
+
return 0;
|
|
48
|
+
});
|
|
49
|
+
for (let file of files) {
|
|
50
|
+
const fullPath = path.join(dir, file);
|
|
51
|
+
if (file === "middleware.js" || file === "middleware.ts") {
|
|
52
|
+
const middleware = await import(pathToFileURL(fullPath).href);
|
|
53
|
+
router.use(middleware.default);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const route = await import(pathToFileURL(fullPath).href);
|
|
57
|
+
if (route.GET)
|
|
58
|
+
router.get("/", route.GET);
|
|
59
|
+
if (route.POST)
|
|
60
|
+
router.post("/", route.POST);
|
|
61
|
+
if (route.PUT)
|
|
62
|
+
router.put("/", route.PUT);
|
|
63
|
+
if (route.DELETE)
|
|
64
|
+
router.delete("/", route.DELETE);
|
|
65
|
+
if (route.PATCH)
|
|
66
|
+
router.patch("/", route.PATCH);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const subdirs = fs
|
|
70
|
+
.readdirSync(dir)
|
|
71
|
+
.filter((file) => fs.statSync(path.join(dir, file)).isDirectory());
|
|
72
|
+
for (let subdir of subdirs) {
|
|
73
|
+
const subdirPath = path.join(dir, subdir);
|
|
74
|
+
const subRouter = Router({ mergeParams: true });
|
|
75
|
+
const routerName = subdir.startsWith("[") && subdir.endsWith("]")
|
|
76
|
+
? `:${subdir.slice(1, -1)}`
|
|
77
|
+
: subdir;
|
|
78
|
+
await this.handleRoute(subRouter, subdirPath);
|
|
79
|
+
router.use(`/${routerName}`, subRouter);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mathisalarcon/express-next",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "npx tsc"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [],
|
|
11
|
+
"author": "",
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@types/express": "^5.0.6",
|
|
16
|
+
"express": "^5.2.1"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^25.0.9",
|
|
20
|
+
"tsx": "^4.21.0",
|
|
21
|
+
"typescript": "^5.9.3"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import express, { Router } from "express";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
export type ExpressNextOptions = {
|
|
7
|
+
root: string; // The root directory of the Next.js application
|
|
8
|
+
port?: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type RouteExport = {
|
|
12
|
+
GET: express.RequestHandler;
|
|
13
|
+
POST?: express.RequestHandler;
|
|
14
|
+
PUT?: express.RequestHandler;
|
|
15
|
+
DELETE?: express.RequestHandler;
|
|
16
|
+
PATCH?: express.RequestHandler;
|
|
17
|
+
};
|
|
18
|
+
type MiddlewareExport = {
|
|
19
|
+
default: express.RequestHandler;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class ExpressNext {
|
|
23
|
+
app: express.Express;
|
|
24
|
+
root: string;
|
|
25
|
+
port: number;
|
|
26
|
+
|
|
27
|
+
constructor(options: ExpressNextOptions) {
|
|
28
|
+
this.app = express();
|
|
29
|
+
this.root = options.root;
|
|
30
|
+
this.port = options.port ?? 3000;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
set(key: string, value: any) {
|
|
34
|
+
this.app.set(key, value);
|
|
35
|
+
}
|
|
36
|
+
use(middleware: express.RequestHandler) {
|
|
37
|
+
this.app.use(middleware);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async start(): Promise<void> {
|
|
41
|
+
await this.handleRoute(this.app, this.root);
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
this.app.listen(this.port, (err) => {
|
|
44
|
+
if (err instanceof Error) {
|
|
45
|
+
// If port is in use, still resolve the promise
|
|
46
|
+
console.error("Couldn't start server:", err.message);
|
|
47
|
+
console.log(
|
|
48
|
+
`Port ${this.port} might be in use. Trying with port ${++this.port}...`,
|
|
49
|
+
);
|
|
50
|
+
this.start().then(() => resolve());
|
|
51
|
+
} else {
|
|
52
|
+
console.log(`Server is running on port ${this.port}`);
|
|
53
|
+
resolve();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private async handleRoute(router: Router, dir: string) {
|
|
60
|
+
const files = fs
|
|
61
|
+
.readdirSync(dir)
|
|
62
|
+
.filter(
|
|
63
|
+
(file) =>
|
|
64
|
+
fs.statSync(path.join(dir, file)).isFile() &&
|
|
65
|
+
(file === "middleware.js" ||
|
|
66
|
+
file === "middleware.ts" ||
|
|
67
|
+
file === "route.js" ||
|
|
68
|
+
file === "route.ts"),
|
|
69
|
+
)
|
|
70
|
+
.sort((a, b) => {
|
|
71
|
+
if (a === "middleware.js" || a === "middleware.ts") return -1;
|
|
72
|
+
if (b === "middleware.js" || b === "middleware.ts") return 1;
|
|
73
|
+
return 0;
|
|
74
|
+
});
|
|
75
|
+
for (let file of files) {
|
|
76
|
+
const fullPath = path.join(dir, file);
|
|
77
|
+
if (file === "middleware.js" || file === "middleware.ts") {
|
|
78
|
+
const middleware: MiddlewareExport = await import(
|
|
79
|
+
pathToFileURL(fullPath).href
|
|
80
|
+
);
|
|
81
|
+
router.use(middleware.default);
|
|
82
|
+
} else {
|
|
83
|
+
const route: RouteExport = await import(
|
|
84
|
+
pathToFileURL(fullPath).href
|
|
85
|
+
);
|
|
86
|
+
if (route.GET) router.get("/", route.GET);
|
|
87
|
+
if (route.POST) router.post("/", route.POST);
|
|
88
|
+
if (route.PUT) router.put("/", route.PUT);
|
|
89
|
+
if (route.DELETE) router.delete("/", route.DELETE);
|
|
90
|
+
if (route.PATCH) router.patch("/", route.PATCH);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const subdirs = fs
|
|
94
|
+
.readdirSync(dir)
|
|
95
|
+
.filter((file) => fs.statSync(path.join(dir, file)).isDirectory());
|
|
96
|
+
|
|
97
|
+
for (let subdir of subdirs) {
|
|
98
|
+
const subdirPath = path.join(dir, subdir);
|
|
99
|
+
const subRouter = Router({ mergeParams: true });
|
|
100
|
+
const routerName =
|
|
101
|
+
subdir.startsWith("[") && subdir.endsWith("]")
|
|
102
|
+
? `:${subdir.slice(1, -1)}`
|
|
103
|
+
: subdir;
|
|
104
|
+
await this.handleRoute(subRouter, subdirPath);
|
|
105
|
+
router.use(`/${routerName}`, subRouter);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compileOnSave": true,
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "es2020",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"module": "nodenext",
|
|
7
|
+
"moduleResolution": "nodenext",
|
|
8
|
+
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"verbatimModuleSyntax": true,
|
|
12
|
+
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
|
|
17
|
+
"outDir": "./dist",
|
|
18
|
+
"rootDir": "./src"
|
|
19
|
+
},
|
|
20
|
+
"include": ["src"],
|
|
21
|
+
}
|