1
1
// src/app.js
2
2
import express from 'express' ;
3
+ import helmet from 'helmet' ;
4
+ import compression from 'compression' ;
5
+ import cors from 'cors' ;
6
+ import path from 'path' ;
7
+ import config from './config/config.js' ;
8
+ import routes from './routes/index.js' ;
3
9
import { securityMiddleware } from './middleware/security.js' ;
4
- import { loggingMiddleware } from './middleware/logging.js' ;
5
- import apiRoutes from './routes/api.js' ;
6
- import { register } from './metrics/prometheus.js' ;
10
+ import { requestLogger , errorLogger } from './middleware/logging.js' ;
11
+ import { errorHandler , notFoundHandler } from './middleware/errorHandler.js' ;
12
+ import databaseManager from './config/database.js' ;
13
+ import prometheusMetrics from './metrics/prometheus.js' ;
14
+ import metricCollectors from './metrics/collectors.js' ;
7
15
8
- const app = express ( ) ;
16
+ /**
17
+ * CloudPanel API Application
18
+ * Main application setup and initialization.
19
+ * Configures Express server with middleware, routes, and error handling.
20
+ */
21
+ class Application {
22
+ constructor ( ) {
23
+ this . app = express ( ) ;
24
+ this . initialized = false ;
25
+ }
9
26
10
- // Apply middleware
11
- app . use ( securityMiddleware ) ;
12
- app . use ( loggingMiddleware ) ;
13
- app . use ( express . json ( ) ) ;
27
+ /**
28
+ * Initializes the application and all its dependencies
29
+ * Handles startup in the correct order with proper error handling
30
+ */
31
+ async initialize ( ) {
32
+ if ( this . initialized ) {
33
+ return ;
34
+ }
14
35
15
- // API routes
16
- app . use ( '/api/v1' , apiRoutes ) ;
36
+ try {
37
+ // Initialize database first
38
+ await databaseManager . initialize ( ) ;
39
+ console . log ( 'Database initialized successfully' ) ;
17
40
18
- // Metrics endpoint
19
- app . get ( '/metrics' , async ( req , res ) => {
20
- res . setHeader ( 'Content-Type' , register . contentType ) ;
21
- res . send ( await register . metrics ( ) ) ;
22
- } ) ;
41
+ // Initialize metrics collection
42
+ await prometheusMetrics . initialize ( ) ;
43
+ console . log ( 'Prometheus metrics initialized' ) ;
23
44
24
- export default app ;
45
+ // Configure basic middleware
46
+ this . configureMiddleware ( ) ;
47
+ console . log ( 'Middleware configured' ) ;
48
+
49
+ // Configure routes
50
+ this . configureRoutes ( ) ;
51
+ console . log ( 'Routes configured' ) ;
52
+
53
+ // Configure error handling
54
+ this . configureErrorHandling ( ) ;
55
+ console . log ( 'Error handling configured' ) ;
56
+
57
+ // Start metric collectors
58
+ metricCollectors . startCollectors ( ) ;
59
+ console . log ( 'Metric collectors started' ) ;
60
+
61
+ this . initialized = true ;
62
+ console . log ( 'Application initialization completed' ) ;
63
+ } catch ( error ) {
64
+ console . error ( 'Application initialization failed:' , error ) ;
65
+ throw error ;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Configures Express middleware in the correct order
71
+ * Sets up security, parsing, and utility middleware
72
+ */
73
+ configureMiddleware ( ) {
74
+ // Security middleware
75
+ this . app . use ( helmet ( ) ) ;
76
+ this . app . use ( cors ( config . security . cors ) ) ;
77
+ this . app . use ( securityMiddleware ) ;
78
+
79
+ // Request parsing
80
+ this . app . use ( express . json ( { limit : '10mb' } ) ) ;
81
+ this . app . use ( express . urlencoded ( { extended : true , limit : '10mb' } ) ) ;
82
+
83
+ // Compression
84
+ this . app . use ( compression ( ) ) ;
85
+
86
+ // Request logging
87
+ this . app . use ( requestLogger ) ;
88
+
89
+ // Serve static files if configured
90
+ if ( config . server . serveStatic ) {
91
+ this . app . use ( '/static' , express . static ( path . join ( config . paths . root , 'public' ) ) ) ;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Configures application routes and API endpoints
97
+ * Sets up metrics endpoint and API routes
98
+ */
99
+ configureRoutes ( ) {
100
+ // Health check endpoint
101
+ this . app . get ( '/health' , async ( req , res ) => {
102
+ try {
103
+ await databaseManager . db . get ( 'SELECT 1' ) ;
104
+ res . json ( { status : 'healthy' , timestamp : new Date ( ) . toISOString ( ) } ) ;
105
+ } catch ( error ) {
106
+ res . status ( 503 ) . json ( {
107
+ status : 'unhealthy' ,
108
+ error : 'Database connection failed' ,
109
+ timestamp : new Date ( ) . toISOString ( )
110
+ } ) ;
111
+ }
112
+ } ) ;
113
+
114
+ // Metrics endpoint
115
+ this . app . get ( '/metrics' , async ( req , res ) => {
116
+ try {
117
+ const metrics = await prometheusMetrics . getMetrics ( ) ;
118
+ res . set ( 'Content-Type' , 'text/plain' ) ;
119
+ res . send ( metrics ) ;
120
+ } catch ( error ) {
121
+ res . status ( 500 ) . json ( { error : 'Failed to collect metrics' } ) ;
122
+ }
123
+ } ) ;
124
+
125
+ // API routes
126
+ this . app . use ( '/api' , routes ) ;
127
+
128
+ // Catch 404
129
+ this . app . use ( notFoundHandler ) ;
130
+ }
131
+
132
+ /**
133
+ * Configures error handling middleware
134
+ * Sets up logging and error response formatting
135
+ */
136
+ configureErrorHandling ( ) {
137
+ this . app . use ( errorLogger ) ;
138
+ this . app . use ( errorHandler ) ;
139
+
140
+ // Handle unhandled rejections
141
+ process . on ( 'unhandledRejection' , ( reason , promise ) => {
142
+ console . error ( 'Unhandled Rejection at:' , promise , 'reason:' , reason ) ;
143
+ } ) ;
144
+
145
+ // Handle uncaught exceptions
146
+ process . on ( 'uncaughtException' , ( error ) => {
147
+ console . error ( 'Uncaught Exception:' , error ) ;
148
+ this . shutdown ( 1 ) ;
149
+ } ) ;
150
+ }
151
+
152
+ /**
153
+ * Starts the application server
154
+ * @returns {Promise<void> }
155
+ */
156
+ async start ( ) {
157
+ if ( ! this . initialized ) {
158
+ await this . initialize ( ) ;
159
+ }
160
+
161
+ const { port, host } = config . server ;
162
+
163
+ return new Promise ( ( resolve ) => {
164
+ this . server = this . app . listen ( port , host , ( ) => {
165
+ console . log ( `Server running at http://${ host } :${ port } ` ) ;
166
+ resolve ( ) ;
167
+ } ) ;
168
+
169
+ // Configure server timeouts
170
+ this . server . timeout = 30000 ; // 30 seconds
171
+ this . server . keepAliveTimeout = 65000 ; // 65 seconds
172
+ } ) ;
173
+ }
174
+
175
+ /**
176
+ * Performs graceful shutdown of the application
177
+ * @param {number } [code=0] - Exit code
178
+ * @returns {Promise<void> }
179
+ */
180
+ async shutdown ( code = 0 ) {
181
+ console . log ( 'Shutting down application...' ) ;
182
+
183
+ try {
184
+ // Stop metric collectors
185
+ metricCollectors . stopCollectors ( ) ;
186
+ console . log ( 'Metric collectors stopped' ) ;
187
+
188
+ // Close database connections
189
+ await databaseManager . close ( ) ;
190
+ console . log ( 'Database connections closed' ) ;
191
+
192
+ // Close server
193
+ if ( this . server ) {
194
+ await new Promise ( ( resolve ) => {
195
+ this . server . close ( resolve ) ;
196
+ } ) ;
197
+ console . log ( 'Server stopped' ) ;
198
+ }
199
+
200
+ console . log ( 'Shutdown completed' ) ;
201
+ process . exit ( code ) ;
202
+ } catch ( error ) {
203
+ console . error ( 'Error during shutdown:' , error ) ;
204
+ process . exit ( 1 ) ;
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Gets Express application instance
210
+ * Useful for testing and custom configurations
211
+ * @returns {express.Application }
212
+ */
213
+ getApp ( ) {
214
+ return this . app ;
215
+ }
216
+ }
217
+
218
+ // Create and export application instance
219
+ const application = new Application ( ) ;
220
+
221
+ // Handle shutdown signals
222
+ process . on ( 'SIGTERM' , ( ) => application . shutdown ( ) ) ;
223
+ process . on ( 'SIGINT' , ( ) => application . shutdown ( ) ) ;
224
+
225
+ export default application ;
226
+
227
+ // If this is the main module, start the application
228
+ if ( import . meta. url === `file://${ process . argv [ 1 ] } ` ) {
229
+ application . start ( ) . catch ( ( error ) => {
230
+ console . error ( 'Failed to start application:' , error ) ;
231
+ process . exit ( 1 ) ;
232
+ } ) ;
233
+ }
0 commit comments