@misterzik/espressojs 3.2.6 → 3.3.1
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/CHANGELOG.md +115 -0
- package/CONTRIBUTING.md +96 -0
- package/ENHANCEMENTS.md +262 -0
- package/MIGRATION.md +204 -0
- package/QUICKSTART.md +213 -0
- package/README.md +368 -62
- package/docs/MULTIPLE-APIS.md +451 -0
- package/examples/README.md +89 -0
- package/examples/basic-api.js +116 -0
- package/examples/mongodb-example.js +149 -0
- package/examples/multiple-apis.js +149 -0
- package/index.js +112 -13
- package/package.json +13 -7
- package/routes/index.js +52 -15
- package/server/middleware/errorHandler.js +73 -0
- package/server/middleware/healthCheck.js +71 -0
- package/server/middleware/security.js +60 -0
- package/server/utils/apiManager.js +110 -0
- package/server/utils/config.utils.js +56 -5
- package/server/utils/configValidator.js +90 -0
- package/server/utils/espresso-cli.js +141 -55
- package/server/utils/logger.js +75 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* EspressoJS - MongoDB Integration Example
|
|
3
|
+
* This file demonstrates how to use MongoDB with EspressoJS
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const express = require('express');
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
const mongoose = require('mongoose');
|
|
9
|
+
const { asyncHandler, AppError } = require('../server/middleware/errorHandler');
|
|
10
|
+
|
|
11
|
+
// Example Schema
|
|
12
|
+
const UserSchema = new mongoose.Schema({
|
|
13
|
+
name: {
|
|
14
|
+
type: String,
|
|
15
|
+
required: [true, 'Name is required'],
|
|
16
|
+
trim: true,
|
|
17
|
+
maxlength: [50, 'Name cannot exceed 50 characters']
|
|
18
|
+
},
|
|
19
|
+
email: {
|
|
20
|
+
type: String,
|
|
21
|
+
required: [true, 'Email is required'],
|
|
22
|
+
unique: true,
|
|
23
|
+
lowercase: true,
|
|
24
|
+
match: [/^\S+@\S+\.\S+$/, 'Please provide a valid email']
|
|
25
|
+
},
|
|
26
|
+
age: {
|
|
27
|
+
type: Number,
|
|
28
|
+
min: [0, 'Age must be positive']
|
|
29
|
+
},
|
|
30
|
+
createdAt: {
|
|
31
|
+
type: Date,
|
|
32
|
+
default: Date.now
|
|
33
|
+
},
|
|
34
|
+
updatedAt: {
|
|
35
|
+
type: Date,
|
|
36
|
+
default: Date.now
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Update timestamp on save
|
|
41
|
+
UserSchema.pre('save', function(next) {
|
|
42
|
+
this.updatedAt = Date.now();
|
|
43
|
+
next();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const User = mongoose.model('User', UserSchema);
|
|
47
|
+
|
|
48
|
+
// GET all users with pagination
|
|
49
|
+
router.get('/users', asyncHandler(async (req, res) => {
|
|
50
|
+
const page = parseInt(req.query.page) || 1;
|
|
51
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
52
|
+
const skip = (page - 1) * limit;
|
|
53
|
+
|
|
54
|
+
const users = await User.find()
|
|
55
|
+
.select('-__v')
|
|
56
|
+
.limit(limit)
|
|
57
|
+
.skip(skip)
|
|
58
|
+
.sort({ createdAt: -1 });
|
|
59
|
+
|
|
60
|
+
const total = await User.countDocuments();
|
|
61
|
+
|
|
62
|
+
res.status(200).json({
|
|
63
|
+
status: 'success',
|
|
64
|
+
results: users.length,
|
|
65
|
+
pagination: {
|
|
66
|
+
page,
|
|
67
|
+
limit,
|
|
68
|
+
total,
|
|
69
|
+
pages: Math.ceil(total / limit)
|
|
70
|
+
},
|
|
71
|
+
data: { users }
|
|
72
|
+
});
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
// GET user by ID
|
|
76
|
+
router.get('/users/:id', asyncHandler(async (req, res) => {
|
|
77
|
+
const user = await User.findById(req.params.id).select('-__v');
|
|
78
|
+
|
|
79
|
+
if (!user) {
|
|
80
|
+
throw new AppError('User not found', 404);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
res.status(200).json({
|
|
84
|
+
status: 'success',
|
|
85
|
+
data: { user }
|
|
86
|
+
});
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
// POST create new user
|
|
90
|
+
router.post('/users', asyncHandler(async (req, res) => {
|
|
91
|
+
const user = await User.create(req.body);
|
|
92
|
+
|
|
93
|
+
res.status(201).json({
|
|
94
|
+
status: 'success',
|
|
95
|
+
data: { user }
|
|
96
|
+
});
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
// PUT update user
|
|
100
|
+
router.put('/users/:id', asyncHandler(async (req, res) => {
|
|
101
|
+
const user = await User.findByIdAndUpdate(
|
|
102
|
+
req.params.id,
|
|
103
|
+
req.body,
|
|
104
|
+
{
|
|
105
|
+
new: true,
|
|
106
|
+
runValidators: true
|
|
107
|
+
}
|
|
108
|
+
).select('-__v');
|
|
109
|
+
|
|
110
|
+
if (!user) {
|
|
111
|
+
throw new AppError('User not found', 404);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
res.status(200).json({
|
|
115
|
+
status: 'success',
|
|
116
|
+
data: { user }
|
|
117
|
+
});
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
// DELETE user
|
|
121
|
+
router.delete('/users/:id', asyncHandler(async (req, res) => {
|
|
122
|
+
const user = await User.findByIdAndDelete(req.params.id);
|
|
123
|
+
|
|
124
|
+
if (!user) {
|
|
125
|
+
throw new AppError('User not found', 404);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
res.status(204).send();
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
// GET users by query
|
|
132
|
+
router.get('/users/search/:query', asyncHandler(async (req, res) => {
|
|
133
|
+
const { query } = req.params;
|
|
134
|
+
|
|
135
|
+
const users = await User.find({
|
|
136
|
+
$or: [
|
|
137
|
+
{ name: { $regex: query, $options: 'i' } },
|
|
138
|
+
{ email: { $regex: query, $options: 'i' } }
|
|
139
|
+
]
|
|
140
|
+
}).select('-__v');
|
|
141
|
+
|
|
142
|
+
res.status(200).json({
|
|
143
|
+
status: 'success',
|
|
144
|
+
results: users.length,
|
|
145
|
+
data: { users }
|
|
146
|
+
});
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
module.exports = router;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* EspressoJS - Multiple API Endpoints Example
|
|
3
|
+
* This example demonstrates how to use multiple API configurations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const express = require('express');
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
const { asyncHandler, AppError } = require('../server/middleware/errorHandler');
|
|
9
|
+
const { apiManager } = require('../index');
|
|
10
|
+
|
|
11
|
+
// Example 1: Using the API Manager directly
|
|
12
|
+
router.get('/news', asyncHandler(async (req, res) => {
|
|
13
|
+
// Make a request to api2 endpoint
|
|
14
|
+
const newsData = await apiManager.request('api2', '');
|
|
15
|
+
|
|
16
|
+
res.status(200).json({
|
|
17
|
+
status: 'success',
|
|
18
|
+
source: 'api2',
|
|
19
|
+
data: newsData
|
|
20
|
+
});
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
// Example 2: Using multiple APIs in a single endpoint
|
|
24
|
+
router.get('/combined-data', asyncHandler(async (req, res) => {
|
|
25
|
+
// Check which APIs are available
|
|
26
|
+
const availableAPIs = apiManager.getAllAPIs();
|
|
27
|
+
|
|
28
|
+
// Make parallel requests to multiple APIs
|
|
29
|
+
const results = await Promise.allSettled([
|
|
30
|
+
apiManager.request('api', '/endpoint1'),
|
|
31
|
+
apiManager.request('api2', ''),
|
|
32
|
+
apiManager.request('api3', '/data')
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const data = {
|
|
36
|
+
api1: results[0].status === 'fulfilled' ? results[0].value : null,
|
|
37
|
+
api2: results[1].status === 'fulfilled' ? results[1].value : null,
|
|
38
|
+
api3: results[2].status === 'fulfilled' ? results[2].value : null,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
res.status(200).json({
|
|
42
|
+
status: 'success',
|
|
43
|
+
availableAPIs: Object.keys(availableAPIs),
|
|
44
|
+
data
|
|
45
|
+
});
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
// Example 3: Using specific API with custom options
|
|
49
|
+
router.get('/custom-request', asyncHandler(async (req, res) => {
|
|
50
|
+
const data = await apiManager.request('api3', '/custom-endpoint', {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
data: { query: 'example' },
|
|
53
|
+
headers: {
|
|
54
|
+
'Custom-Header': 'value'
|
|
55
|
+
},
|
|
56
|
+
timeout: 5000,
|
|
57
|
+
retries: 2
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
res.status(200).json({
|
|
61
|
+
status: 'success',
|
|
62
|
+
data
|
|
63
|
+
});
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
// Example 4: Creating a custom Axios instance for an API
|
|
67
|
+
router.get('/axios-instance', asyncHandler(async (req, res) => {
|
|
68
|
+
// Create a custom axios instance for api2
|
|
69
|
+
const api2Client = apiManager.createAxiosInstance('api2');
|
|
70
|
+
|
|
71
|
+
// Use it like regular axios
|
|
72
|
+
const response = await api2Client.get('/specific-endpoint');
|
|
73
|
+
|
|
74
|
+
res.status(200).json({
|
|
75
|
+
status: 'success',
|
|
76
|
+
data: response.data
|
|
77
|
+
});
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
// Example 5: Conditional API usage based on availability
|
|
81
|
+
router.get('/smart-fetch', asyncHandler(async (req, res) => {
|
|
82
|
+
let data;
|
|
83
|
+
|
|
84
|
+
// Try primary API first, fallback to secondary
|
|
85
|
+
if (apiManager.hasAPI('api')) {
|
|
86
|
+
try {
|
|
87
|
+
data = await apiManager.request('api', '/data');
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// Fallback to api3 if api fails
|
|
90
|
+
if (apiManager.hasAPI('api3')) {
|
|
91
|
+
data = await apiManager.request('api3', '/data');
|
|
92
|
+
} else {
|
|
93
|
+
throw new AppError('No API endpoints available', 503);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} else if (apiManager.hasAPI('api3')) {
|
|
97
|
+
data = await apiManager.request('api3', '/data');
|
|
98
|
+
} else {
|
|
99
|
+
throw new AppError('No API endpoints configured', 503);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
res.status(200).json({
|
|
103
|
+
status: 'success',
|
|
104
|
+
data
|
|
105
|
+
});
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
// Example 6: List all configured APIs
|
|
109
|
+
router.get('/api-info', (req, res) => {
|
|
110
|
+
const apis = apiManager.getAllAPIs();
|
|
111
|
+
|
|
112
|
+
const apiInfo = Object.entries(apis).map(([name, config]) => ({
|
|
113
|
+
name,
|
|
114
|
+
baseURL: config.baseURL,
|
|
115
|
+
method: config.method,
|
|
116
|
+
timeout: config.timeout,
|
|
117
|
+
retries: config.retries
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
res.status(200).json({
|
|
121
|
+
status: 'success',
|
|
122
|
+
count: apiInfo.length,
|
|
123
|
+
apis: apiInfo
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Example 7: Proxy endpoint for any API
|
|
128
|
+
router.get('/proxy/:apiName/*', asyncHandler(async (req, res) => {
|
|
129
|
+
const { apiName } = req.params;
|
|
130
|
+
const endpoint = req.params[0];
|
|
131
|
+
|
|
132
|
+
if (!apiManager.hasAPI(apiName)) {
|
|
133
|
+
throw new AppError(`API '${apiName}' not found`, 404);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const data = await apiManager.request(apiName, `/${endpoint}`, {
|
|
137
|
+
method: req.method,
|
|
138
|
+
params: req.query
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
res.status(200).json({
|
|
142
|
+
status: 'success',
|
|
143
|
+
api: apiName,
|
|
144
|
+
endpoint,
|
|
145
|
+
data
|
|
146
|
+
});
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
module.exports = router;
|
package/index.js
CHANGED
|
@@ -17,7 +17,11 @@ const {
|
|
|
17
17
|
readConfigFile,
|
|
18
18
|
setCustomCacheControl,
|
|
19
19
|
} = require("./server/utils/config.utils");
|
|
20
|
+
const { validateEnvVariables } = require("./server/utils/configValidator");
|
|
21
|
+
const logger = require("./server/utils/logger");
|
|
22
|
+
const APIManager = require("./server/utils/apiManager");
|
|
20
23
|
const configData = readConfigFile();
|
|
24
|
+
const apiManager = new APIManager(configData);
|
|
21
25
|
|
|
22
26
|
const Path = require("path");
|
|
23
27
|
const Cors = require("cors");
|
|
@@ -25,17 +29,35 @@ const Compression = require("compression");
|
|
|
25
29
|
const Favicon = require("serve-favicon");
|
|
26
30
|
const Static = require("serve-static");
|
|
27
31
|
const mongoose = require("mongoose");
|
|
32
|
+
const morgan = require("morgan");
|
|
28
33
|
const Routes = require("./routes/index");
|
|
29
34
|
|
|
35
|
+
const {
|
|
36
|
+
helmetConfig,
|
|
37
|
+
rateLimiter,
|
|
38
|
+
} = require("./server/middleware/security");
|
|
39
|
+
const {
|
|
40
|
+
errorHandler,
|
|
41
|
+
notFoundHandler,
|
|
42
|
+
} = require("./server/middleware/errorHandler");
|
|
43
|
+
const {
|
|
44
|
+
healthCheck,
|
|
45
|
+
readinessCheck,
|
|
46
|
+
livenessCheck,
|
|
47
|
+
} = require("./server/middleware/healthCheck");
|
|
48
|
+
|
|
30
49
|
const Port = configData.port || cfg.port;
|
|
31
|
-
const mongoConfig = configData.
|
|
50
|
+
const mongoConfig = configData.mongoDB;
|
|
32
51
|
const rootDir = process.cwd();
|
|
52
|
+
const publicDir = configData.publicDirectory || "/public";
|
|
53
|
+
|
|
54
|
+
validateEnvVariables();
|
|
33
55
|
|
|
34
|
-
if (configData.
|
|
56
|
+
if (configData.mongoDB.enabled) {
|
|
35
57
|
const {
|
|
36
|
-
uri: mongoUri = configData.
|
|
37
|
-
port: mongoPort = configData.
|
|
38
|
-
db: mongoDb = configData.
|
|
58
|
+
uri: mongoUri = configData.mongoDB.uri || "",
|
|
59
|
+
port: mongoPort = configData.mongoDB.port || "",
|
|
60
|
+
db: mongoDb = configData.mongoDB.instance || "",
|
|
39
61
|
} = mongoConfig;
|
|
40
62
|
const hasPort = mongoPort ? `:${mongoPort}/` : "/";
|
|
41
63
|
const url = `mongodb+srv://${
|
|
@@ -55,26 +77,103 @@ if (configData.mongo_isEnabled) {
|
|
|
55
77
|
useNewUrlParser: true,
|
|
56
78
|
promiseLibrary: require("bluebird"),
|
|
57
79
|
})
|
|
58
|
-
.then(() =>
|
|
59
|
-
.catch((err) =>
|
|
80
|
+
.then(() => logger.info(":: DB Connection successful ::"))
|
|
81
|
+
.catch((err) => logger.error(`DB Connection error: ${err.message}`));
|
|
60
82
|
}
|
|
61
83
|
|
|
84
|
+
app.use(helmetConfig());
|
|
62
85
|
app.use(Compression());
|
|
63
86
|
app.use(Cors());
|
|
64
|
-
app.use(express.urlencoded({ extended: false }));
|
|
65
|
-
app.use(express.json());
|
|
66
|
-
|
|
87
|
+
app.use(express.urlencoded({ extended: false, limit: "10mb" }));
|
|
88
|
+
app.use(express.json({ limit: "10mb" }));
|
|
89
|
+
|
|
90
|
+
const morganFormat = process.env.NODE_ENV === "production" ? "combined" : "dev";
|
|
91
|
+
app.use(
|
|
92
|
+
morgan(morganFormat, {
|
|
93
|
+
stream: {
|
|
94
|
+
write: (message) => logger.http(message.trim()),
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
app.use(rateLimiter);
|
|
100
|
+
|
|
101
|
+
app.get("/health", healthCheck);
|
|
102
|
+
app.get("/ready", readinessCheck);
|
|
103
|
+
app.get("/alive", livenessCheck);
|
|
104
|
+
|
|
105
|
+
app.use(Favicon(Path.join(rootDir, publicDir, "favicon.ico")));
|
|
67
106
|
app.use(
|
|
68
|
-
Static(Path.join(rootDir,
|
|
107
|
+
Static(Path.join(rootDir, publicDir), {
|
|
69
108
|
maxAge: "1d",
|
|
70
109
|
setHeaders: setCustomCacheControl,
|
|
71
110
|
etag: true,
|
|
72
111
|
extensions: "error.html",
|
|
73
112
|
})
|
|
74
113
|
);
|
|
114
|
+
|
|
75
115
|
Routes(app);
|
|
76
|
-
|
|
77
|
-
|
|
116
|
+
|
|
117
|
+
app.use(notFoundHandler);
|
|
118
|
+
app.use(errorHandler);
|
|
119
|
+
|
|
120
|
+
let server;
|
|
121
|
+
|
|
122
|
+
const startServer = () => {
|
|
123
|
+
server = app.listen(Port, () => {
|
|
124
|
+
logger.info(`
|
|
125
|
+
╔═══════════════════════════════════════════════════════╗
|
|
126
|
+
║ ESPRESSO.JS ║
|
|
127
|
+
║ Express Boilerplate Server ║
|
|
128
|
+
╠═══════════════════════════════════════════════════════╣
|
|
129
|
+
║ Environment: ${configData.instance.padEnd(39)} ║
|
|
130
|
+
║ Port: ${Port.toString().padEnd(39)} ║
|
|
131
|
+
║ URL: http://localhost:${Port.toString().padEnd(27)} ║
|
|
132
|
+
║ MongoDB: ${(configData.mongoDB.enabled ? "Enabled" : "Disabled").padEnd(39)} ║
|
|
133
|
+
║ API: ${(configData.api.enabled ? "Enabled" : "Disabled").padEnd(39)} ║
|
|
134
|
+
╚═══════════════════════════════════════════════════════╝
|
|
135
|
+
`);
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const gracefulShutdown = (signal) => {
|
|
140
|
+
logger.info(`${signal} received. Starting graceful shutdown...`);
|
|
141
|
+
|
|
142
|
+
server.close(() => {
|
|
143
|
+
logger.info("HTTP server closed");
|
|
144
|
+
|
|
145
|
+
if (mongoose.connection.readyState === 1) {
|
|
146
|
+
mongoose.connection.close(false, () => {
|
|
147
|
+
logger.info("MongoDB connection closed");
|
|
148
|
+
process.exit(0);
|
|
149
|
+
});
|
|
150
|
+
} else {
|
|
151
|
+
process.exit(0);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
logger.error("Forced shutdown after timeout");
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}, 10000);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
162
|
+
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
163
|
+
|
|
164
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
165
|
+
logger.error(`Unhandled Rejection at: ${promise}, reason: ${reason}`);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
process.on("uncaughtException", (error) => {
|
|
169
|
+
logger.error(`Uncaught Exception: ${error.message}`);
|
|
170
|
+
gracefulShutdown("UNCAUGHT_EXCEPTION");
|
|
78
171
|
});
|
|
79
172
|
|
|
173
|
+
if (require.main === module) {
|
|
174
|
+
startServer();
|
|
175
|
+
}
|
|
176
|
+
|
|
80
177
|
module.exports = app;
|
|
178
|
+
module.exports.apiManager = apiManager;
|
|
179
|
+
module.exports.config = configData;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@misterzik/espressojs",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.1",
|
|
4
4
|
"description": "EspressoJS Introducing Espresso.JS, your ultimate Express configuration starting point and boilerplate. With its simplicity and lack of opinionation, EspressoJS offers plug-and-play configurations built on top of Express.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -36,17 +36,23 @@
|
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://github.com/misterzik/Espresso.js#readme",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"axios": "^1.
|
|
39
|
+
"axios": "^1.6.0",
|
|
40
40
|
"bluebird": "^3.7.2",
|
|
41
|
+
"compression": "^1.7.4",
|
|
42
|
+
"cors": "^2.8.5",
|
|
41
43
|
"cross-env": "^7.0.3",
|
|
42
|
-
"express": "^4.18.2",
|
|
43
|
-
"mongoose": "^7.3.1",
|
|
44
44
|
"dotenv": "^16.3.1",
|
|
45
|
-
"
|
|
45
|
+
"express": "^4.18.2",
|
|
46
|
+
"express-rate-limit": "^7.1.5",
|
|
47
|
+
"express-validator": "^7.0.1",
|
|
46
48
|
"finalhandler": "^1.2.0",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
+
"helmet": "^7.1.0",
|
|
50
|
+
"joi": "^17.11.0",
|
|
51
|
+
"mongoose": "^8.0.3",
|
|
52
|
+
"morgan": "^1.10.0",
|
|
49
53
|
"serve-favicon": "^2.5.0",
|
|
54
|
+
"serve-static": "^1.15.0",
|
|
55
|
+
"winston": "^3.11.0",
|
|
50
56
|
"yargs": "^17.7.2"
|
|
51
57
|
},
|
|
52
58
|
"devDependencies": {
|
package/routes/index.js
CHANGED
|
@@ -10,27 +10,64 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const path = require("path");
|
|
13
|
+
const fs = require("fs");
|
|
13
14
|
const configuration = require("../server");
|
|
15
|
+
const logger = require("../server/utils/logger");
|
|
16
|
+
const { asyncHandler } = require("../server/middleware/errorHandler");
|
|
14
17
|
const rootDir = process.cwd();
|
|
15
|
-
const api = require(path.join(rootDir, "routes", "api.js"));
|
|
16
|
-
const db = require(path.join(rootDir, "routes", "db.js"));
|
|
17
18
|
|
|
18
19
|
module.exports = (app) => {
|
|
19
|
-
app.get(
|
|
20
|
-
|
|
21
|
-
res
|
|
22
|
-
|
|
20
|
+
app.get(
|
|
21
|
+
"/",
|
|
22
|
+
asyncHandler(async (req, res) => {
|
|
23
|
+
const filePath = path.join(rootDir, "public", "index.html");
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(filePath)) {
|
|
26
|
+
logger.warn("index.html not found, sending default response");
|
|
27
|
+
return res.status(200).json({
|
|
28
|
+
message: "Welcome to EspressoJS",
|
|
29
|
+
version: "3.2.6",
|
|
30
|
+
documentation: "https://github.com/misterzik/Espresso.js",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
res.sendFile(filePath);
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
|
|
23
38
|
if (configuration.api_isEnabled === true) {
|
|
24
|
-
|
|
39
|
+
try {
|
|
40
|
+
const apiRoutes = require(path.join(rootDir, "routes", "api.js"));
|
|
41
|
+
app.use("/api", apiRoutes);
|
|
42
|
+
logger.info("API routes loaded successfully");
|
|
43
|
+
} catch (error) {
|
|
44
|
+
logger.warn(`API routes not found or failed to load: ${error.message}`);
|
|
45
|
+
}
|
|
25
46
|
}
|
|
47
|
+
|
|
26
48
|
if (configuration.mongo_isEnabled === true) {
|
|
27
|
-
|
|
49
|
+
try {
|
|
50
|
+
const dbRoutes = require(path.join(rootDir, "routes", "db.js"));
|
|
51
|
+
app.use("/api/db", dbRoutes);
|
|
52
|
+
logger.info("Database routes loaded successfully");
|
|
53
|
+
} catch (error) {
|
|
54
|
+
logger.warn(`Database routes not found or failed to load: ${error.message}`);
|
|
55
|
+
}
|
|
28
56
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
57
|
+
|
|
58
|
+
app.get(
|
|
59
|
+
"/*",
|
|
60
|
+
asyncHandler(async (req, res) => {
|
|
61
|
+
const filePath = path.join(rootDir, "public", "index.html");
|
|
62
|
+
|
|
63
|
+
if (!fs.existsSync(filePath)) {
|
|
64
|
+
return res.status(404).json({
|
|
65
|
+
status: "error",
|
|
66
|
+
message: "Page not found",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
res.sendFile(filePath);
|
|
71
|
+
})
|
|
72
|
+
);
|
|
36
73
|
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* _| _| _| _| _|_|_|
|
|
3
|
+
* _| _| _|_| _|_| _| _|
|
|
4
|
+
* _| _| _| _| _| _| _|
|
|
5
|
+
* _| _| _| _| _| _|
|
|
6
|
+
* _| _| _| _|_|_|
|
|
7
|
+
* EspressoJS - Error Handler Middleware
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const logger = require("../utils/logger");
|
|
11
|
+
|
|
12
|
+
class AppError extends Error {
|
|
13
|
+
constructor(message, statusCode) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.statusCode = statusCode;
|
|
16
|
+
this.status = `${statusCode}`.startsWith("4") ? "fail" : "error";
|
|
17
|
+
this.isOperational = true;
|
|
18
|
+
|
|
19
|
+
Error.captureStackTrace(this, this.constructor);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const errorHandler = (err, req, res, next) => {
|
|
24
|
+
err.statusCode = err.statusCode || 500;
|
|
25
|
+
err.status = err.status || "error";
|
|
26
|
+
|
|
27
|
+
logger.error(
|
|
28
|
+
`${err.statusCode} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
if (process.env.NODE_ENV === "development") {
|
|
32
|
+
res.status(err.statusCode).json({
|
|
33
|
+
status: err.status,
|
|
34
|
+
error: err,
|
|
35
|
+
message: err.message,
|
|
36
|
+
stack: err.stack,
|
|
37
|
+
});
|
|
38
|
+
} else {
|
|
39
|
+
if (err.isOperational) {
|
|
40
|
+
res.status(err.statusCode).json({
|
|
41
|
+
status: err.status,
|
|
42
|
+
message: err.message,
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
logger.error("ERROR 💥", err);
|
|
46
|
+
res.status(500).json({
|
|
47
|
+
status: "error",
|
|
48
|
+
message: "Something went wrong!",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const notFoundHandler = (req, res, next) => {
|
|
55
|
+
const err = new AppError(
|
|
56
|
+
`Cannot find ${req.originalUrl} on this server!`,
|
|
57
|
+
404
|
|
58
|
+
);
|
|
59
|
+
next(err);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const asyncHandler = (fn) => {
|
|
63
|
+
return (req, res, next) => {
|
|
64
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
AppError,
|
|
70
|
+
errorHandler,
|
|
71
|
+
notFoundHandler,
|
|
72
|
+
asyncHandler,
|
|
73
|
+
};
|