frontend-hamroun 1.2.25 → 1.2.26
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.d.ts +6 -1
- package/package.json +1 -1
- package/templates/fullstack-app/server.js +147 -5
- package/templates/ssr-template/readme.md +112 -15
- package/templates/ssr-template/server.ts +166 -14
package/dist/index.d.ts
CHANGED
@@ -8,5 +8,10 @@ export type { Context } from './context';
|
|
8
8
|
export type { VNode } from './types';
|
9
9
|
export type { Server, ServerConfig, User, DbConfig, MiddlewareFunction } from './server-types';
|
10
10
|
export declare const server: {
|
11
|
-
getServer(): Promise<
|
11
|
+
getServer(): Promise<typeof import("./server/index.js")>;
|
12
12
|
};
|
13
|
+
export { Database } from './server/database.js';
|
14
|
+
export { AuthService } from './server/auth.js';
|
15
|
+
export { ApiRouter } from './server/api-router.js';
|
16
|
+
export { requestLogger, errorHandler, notFoundHandler, rateLimit } from './server/middleware.js';
|
17
|
+
export { Server as serverModule } from './server/index.js';
|
package/package.json
CHANGED
@@ -2,6 +2,14 @@ import express from 'express';
|
|
2
2
|
import path from 'path';
|
3
3
|
import { fileURLToPath } from 'url';
|
4
4
|
import fs from 'fs';
|
5
|
+
import dotenv from 'dotenv';
|
6
|
+
import { createServer } from 'http';
|
7
|
+
import { Server as SocketServer } from 'socket.io';
|
8
|
+
import compression from 'compression';
|
9
|
+
import cors from 'cors';
|
10
|
+
|
11
|
+
// Load environment variables
|
12
|
+
dotenv.config();
|
5
13
|
|
6
14
|
// Get __dirname equivalent in ESM
|
7
15
|
const __filename = fileURLToPath(import.meta.url);
|
@@ -10,9 +18,29 @@ const __dirname = path.dirname(__filename);
|
|
10
18
|
// Create Express app
|
11
19
|
const app = express();
|
12
20
|
const PORT = process.env.PORT || 3000;
|
21
|
+
const httpServer = createServer(app);
|
22
|
+
|
23
|
+
// Create socket.io server
|
24
|
+
const io = new SocketServer(httpServer, {
|
25
|
+
cors: {
|
26
|
+
origin: process.env.NODE_ENV === 'production' ? false : '*',
|
27
|
+
methods: ['GET', 'POST']
|
28
|
+
}
|
29
|
+
});
|
13
30
|
|
14
|
-
// Middleware
|
31
|
+
// Middleware setup
|
15
32
|
app.use(express.json());
|
33
|
+
app.use(express.urlencoded({ extended: true }));
|
34
|
+
app.use(compression()); // Add compression for better performance
|
35
|
+
app.use(cors()); // Enable CORS for API access
|
36
|
+
|
37
|
+
// Add request logging in development mode
|
38
|
+
if (process.env.NODE_ENV !== 'production') {
|
39
|
+
app.use((req, res, next) => {
|
40
|
+
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
|
41
|
+
next();
|
42
|
+
});
|
43
|
+
}
|
16
44
|
|
17
45
|
// For production, serve the built files
|
18
46
|
if (process.env.NODE_ENV === 'production') {
|
@@ -22,7 +50,22 @@ if (process.env.NODE_ENV === 'production') {
|
|
22
50
|
console.log('Running in development mode - Vite handles the frontend');
|
23
51
|
}
|
24
52
|
|
25
|
-
//
|
53
|
+
// Global in-memory store for demo purposes
|
54
|
+
// In a real app, you would use a database
|
55
|
+
const store = {
|
56
|
+
users: [
|
57
|
+
{ id: 1, name: 'User 1', email: 'user1@example.com' },
|
58
|
+
{ id: 2, name: 'User 2', email: 'user2@example.com' },
|
59
|
+
{ id: 3, name: 'User 3', email: 'user3@example.com' }
|
60
|
+
],
|
61
|
+
posts: [
|
62
|
+
{ id: 1, title: 'Post 1', content: 'Content for post 1', authorId: 1 },
|
63
|
+
{ id: 2, title: 'Post 2', content: 'Content for post 2', authorId: 2 },
|
64
|
+
{ id: 3, title: 'Post 3', content: 'Content for post 3', authorId: 1 }
|
65
|
+
]
|
66
|
+
};
|
67
|
+
|
68
|
+
// API endpoints - Core data
|
26
69
|
app.get('/api/hello', (req, res) => {
|
27
70
|
res.json({
|
28
71
|
message: "Hello from the API!",
|
@@ -31,11 +74,100 @@ app.get('/api/hello', (req, res) => {
|
|
31
74
|
"Server-side rendering",
|
32
75
|
"API routes",
|
33
76
|
"Component-based UI",
|
34
|
-
"React-like development experience"
|
77
|
+
"React-like development experience",
|
78
|
+
"WebSocket integration",
|
79
|
+
"Database connectivity",
|
80
|
+
"Authentication"
|
35
81
|
]
|
36
82
|
});
|
37
83
|
});
|
38
84
|
|
85
|
+
// Dynamic API route loading
|
86
|
+
const apiDir = path.join(__dirname, 'api');
|
87
|
+
if (fs.existsSync(apiDir)) {
|
88
|
+
console.log('Loading API routes from directory...');
|
89
|
+
|
90
|
+
// Function to map file paths to API routes
|
91
|
+
const registerApiRoutes = async (dir, routePrefix = '') => {
|
92
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
93
|
+
|
94
|
+
for (const entry of entries) {
|
95
|
+
const fullPath = path.join(dir, entry.name);
|
96
|
+
|
97
|
+
if (entry.isDirectory()) {
|
98
|
+
// Process directories recursively
|
99
|
+
await registerApiRoutes(fullPath, `${routePrefix}/${entry.name}`);
|
100
|
+
} else if (entry.name.endsWith('.js') || entry.name.endsWith('.ts')) {
|
101
|
+
// Process API file
|
102
|
+
try {
|
103
|
+
const fileName = entry.name.replace(/\.[^/.]+$/, ""); // Remove extension
|
104
|
+
const routePath = fileName === 'index'
|
105
|
+
? routePrefix
|
106
|
+
: fileName.startsWith('[') && fileName.endsWith(']')
|
107
|
+
? `${routePrefix}/:${fileName.slice(1, -1)}` // Convert [param] to :param
|
108
|
+
: `${routePrefix}/${fileName}`;
|
109
|
+
|
110
|
+
// Import the route module
|
111
|
+
const module = await import(`file://${fullPath}`);
|
112
|
+
|
113
|
+
// Register handlers for HTTP methods
|
114
|
+
['get', 'post', 'put', 'delete', 'patch'].forEach(method => {
|
115
|
+
if (typeof module[method] === 'function') {
|
116
|
+
console.log(` Registered ${method.toUpperCase()} ${routePath}`);
|
117
|
+
app[method](`/api${routePath}`, module[method]);
|
118
|
+
}
|
119
|
+
});
|
120
|
+
|
121
|
+
// Special case for 'delete' which might be named 'delete_' in some files
|
122
|
+
if (typeof module['delete_'] === 'function') {
|
123
|
+
console.log(` Registered DELETE ${routePath}`);
|
124
|
+
app.delete(`/api${routePath}`, module['delete_']);
|
125
|
+
}
|
126
|
+
} catch (err) {
|
127
|
+
console.error(`Error loading API route ${fullPath}:`, err);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
131
|
+
};
|
132
|
+
|
133
|
+
// Start registering API routes
|
134
|
+
try {
|
135
|
+
await registerApiRoutes(apiDir);
|
136
|
+
console.log('API routes loaded successfully');
|
137
|
+
} catch (err) {
|
138
|
+
console.error('Error loading API routes:', err);
|
139
|
+
}
|
140
|
+
} else {
|
141
|
+
console.log('API directory not found, skipping API route loading');
|
142
|
+
}
|
143
|
+
|
144
|
+
// WebSocket setup
|
145
|
+
io.on('connection', (socket) => {
|
146
|
+
console.log('Client connected:', socket.id);
|
147
|
+
|
148
|
+
// Send welcome message
|
149
|
+
socket.emit('welcome', {
|
150
|
+
message: 'Connected to WebSocket server',
|
151
|
+
time: new Date().toISOString()
|
152
|
+
});
|
153
|
+
|
154
|
+
// Handle client messages
|
155
|
+
socket.on('message', (data) => {
|
156
|
+
console.log('Received message:', data);
|
157
|
+
// Broadcast to all clients
|
158
|
+
io.emit('broadcast', {
|
159
|
+
from: socket.id,
|
160
|
+
data,
|
161
|
+
time: new Date().toISOString()
|
162
|
+
});
|
163
|
+
});
|
164
|
+
|
165
|
+
// Handle disconnect
|
166
|
+
socket.on('disconnect', () => {
|
167
|
+
console.log('Client disconnected:', socket.id);
|
168
|
+
});
|
169
|
+
});
|
170
|
+
|
39
171
|
// In production or if using SSR, handle all requests
|
40
172
|
app.get('*', (req, res, next) => {
|
41
173
|
// Skip API routes
|
@@ -57,9 +189,19 @@ app.get('*', (req, res, next) => {
|
|
57
189
|
}
|
58
190
|
});
|
59
191
|
|
192
|
+
// Error handling middleware
|
193
|
+
app.use((err, req, res, next) => {
|
194
|
+
console.error('Server error:', err);
|
195
|
+
res.status(500).json({
|
196
|
+
error: process.env.NODE_ENV === 'production'
|
197
|
+
? 'Internal server error'
|
198
|
+
: err.message
|
199
|
+
});
|
200
|
+
});
|
201
|
+
|
60
202
|
// Start server
|
61
|
-
|
62
|
-
console.log(`
|
203
|
+
httpServer.listen(PORT, () => {
|
204
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
63
205
|
|
64
206
|
if (process.env.NODE_ENV !== 'production') {
|
65
207
|
console.log(`Frontend development server will run on http://localhost:5173`);
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# Frontend Hamroun SSR Template
|
2
2
|
|
3
|
-
This is a
|
3
|
+
This is a comprehensive server-side rendering (SSR) example using Frontend Hamroun.
|
4
4
|
|
5
5
|
## Getting Started
|
6
6
|
|
@@ -16,26 +16,123 @@ This is a simple server-side rendering (SSR) example using Frontend Hamroun.
|
|
16
16
|
|
17
17
|
3. Open your browser at http://localhost:3000
|
18
18
|
|
19
|
-
##
|
19
|
+
## Core Features
|
20
20
|
|
21
|
-
This
|
21
|
+
This template demonstrates:
|
22
22
|
|
23
|
-
|
24
|
-
-
|
25
|
-
-
|
23
|
+
### Server-Side Rendering
|
24
|
+
- Pre-rendering of components on the server
|
25
|
+
- Hydration of server-rendered content on the client
|
26
|
+
- Data fetching during server rendering
|
27
|
+
- AI-powered meta tag generation
|
26
28
|
|
27
|
-
|
29
|
+
### API Integration
|
30
|
+
- RESTful API endpoints
|
31
|
+
- Dynamic API routing
|
32
|
+
- API middleware for validation and security
|
33
|
+
- File-based API structure
|
28
34
|
|
29
|
-
|
35
|
+
### Database Integration
|
36
|
+
- MongoDB, MySQL, and PostgreSQL support
|
37
|
+
- ORM-like query interface
|
38
|
+
- Connection pooling
|
39
|
+
- Transaction support
|
30
40
|
|
31
|
-
|
41
|
+
### Authentication & Authorization
|
42
|
+
- JWT-based authentication
|
43
|
+
- Role-based access control
|
44
|
+
- Password hashing and validation
|
45
|
+
- Token refresh mechanism
|
32
46
|
|
33
|
-
|
47
|
+
### Performance Optimization
|
48
|
+
- Caching strategies
|
49
|
+
- Response compression
|
50
|
+
- Static asset optimization
|
51
|
+
- Efficient metadata handling
|
34
52
|
|
35
|
-
|
53
|
+
## Implementation Examples
|
36
54
|
|
37
|
-
|
38
|
-
2. Use a routing solution for more complex page structures
|
39
|
-
3. Set up database connections and more robust API endpoints
|
55
|
+
### Creating Components
|
40
56
|
|
41
|
-
|
57
|
+
Use the `jsx` or `createElement` function from 'frontend-hamroun':
|
58
|
+
|
59
|
+
```jsx
|
60
|
+
import { jsx } from 'frontend-hamroun';
|
61
|
+
|
62
|
+
export default function MyComponent(props) {
|
63
|
+
return jsx('div', { className: "container" }, [
|
64
|
+
jsx('h1', {}, "Hello World"),
|
65
|
+
jsx('p', {}, `Props value: ${props.value}`)
|
66
|
+
]);
|
67
|
+
}
|
68
|
+
```
|
69
|
+
|
70
|
+
### Creating API Routes
|
71
|
+
|
72
|
+
Create files in the `api` directory following this pattern:
|
73
|
+
|
74
|
+
```typescript
|
75
|
+
// api/users/index.ts
|
76
|
+
import { Request, Response } from 'express';
|
77
|
+
|
78
|
+
export const get = (req: Request, res: Response) => {
|
79
|
+
res.json({ users: [...] });
|
80
|
+
};
|
81
|
+
|
82
|
+
export const post = (req: Request, res: Response) => {
|
83
|
+
// Create user
|
84
|
+
res.status(201).json({ success: true });
|
85
|
+
};
|
86
|
+
```
|
87
|
+
|
88
|
+
### Database Usage
|
89
|
+
|
90
|
+
```typescript
|
91
|
+
import { Server } from 'frontend-hamroun/server';
|
92
|
+
|
93
|
+
const server = new Server({
|
94
|
+
db: {
|
95
|
+
url: process.env.DATABASE_URL,
|
96
|
+
type: 'mongodb' // or 'mysql', 'postgres'
|
97
|
+
}
|
98
|
+
});
|
99
|
+
|
100
|
+
// Get typed database instance
|
101
|
+
const db = server.getDatabase();
|
102
|
+
const users = await db.query('SELECT * FROM users'); // For SQL
|
103
|
+
const docs = await db.getMongoDb().collection('users').find().toArray(); // For MongoDB
|
104
|
+
```
|
105
|
+
|
106
|
+
### Authentication
|
107
|
+
|
108
|
+
```typescript
|
109
|
+
import { AuthService } from 'frontend-hamroun/server';
|
110
|
+
|
111
|
+
const auth = new AuthService({
|
112
|
+
secret: process.env.JWT_SECRET,
|
113
|
+
expiresIn: '24h'
|
114
|
+
});
|
115
|
+
|
116
|
+
// Protect routes
|
117
|
+
app.get('/api/protected', auth.requireAuth(), (req, res) => {
|
118
|
+
res.json({ message: "Authenticated!" });
|
119
|
+
});
|
120
|
+
|
121
|
+
// Create user & login
|
122
|
+
const hashedPassword = await auth.hashPassword(password);
|
123
|
+
const token = auth.generateToken(user);
|
124
|
+
```
|
125
|
+
|
126
|
+
### Advanced Middleware
|
127
|
+
|
128
|
+
```typescript
|
129
|
+
import { rateLimit, requestLogger, errorHandler } from 'frontend-hamroun/server';
|
130
|
+
|
131
|
+
app.use(requestLogger);
|
132
|
+
app.use('/api', rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
|
133
|
+
app.use(errorHandler);
|
134
|
+
```
|
135
|
+
|
136
|
+
## Next Steps
|
137
|
+
|
138
|
+
Explore the full API documentation for more advanced features and customization options.
|
@@ -2,6 +2,14 @@ import express from 'express';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
3
3
|
import { dirname, join } from 'path';
|
4
4
|
import { renderToString } from 'frontend-hamroun';
|
5
|
+
import { Database } from 'frontend-hamroun';
|
6
|
+
import { AuthService } from 'frontend-hamroun';
|
7
|
+
import { requestLogger, errorHandler, notFoundHandler, rateLimit } from 'frontend-hamroun';
|
8
|
+
import dotenv from 'dotenv';
|
9
|
+
import fetch from 'node-fetch';
|
10
|
+
|
11
|
+
// Load environment variables
|
12
|
+
dotenv.config();
|
5
13
|
|
6
14
|
// Get directory name in ESM
|
7
15
|
const __filename = fileURLToPath(import.meta.url);
|
@@ -11,6 +19,67 @@ const __dirname = dirname(__filename);
|
|
11
19
|
const app = express();
|
12
20
|
const port = process.env.PORT ? parseInt(process.env.PORT) : 3000;
|
13
21
|
|
22
|
+
// Add middleware
|
23
|
+
app.use(express.json());
|
24
|
+
app.use(express.urlencoded({ extended: true }));
|
25
|
+
app.use(requestLogger);
|
26
|
+
|
27
|
+
// Rate limiting for API routes
|
28
|
+
app.use('/api', rateLimit({
|
29
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
30
|
+
max: 100 // limit each IP to 100 requests per windowMs
|
31
|
+
}));
|
32
|
+
|
33
|
+
// Configure database if connection string is provided
|
34
|
+
let db = null;
|
35
|
+
if (process.env.DATABASE_URL) {
|
36
|
+
db = new Database({
|
37
|
+
url: process.env.DATABASE_URL,
|
38
|
+
type: (process.env.DATABASE_TYPE || 'mongodb') as 'mongodb' | 'mysql' | 'postgres'
|
39
|
+
});
|
40
|
+
|
41
|
+
// Connect to database
|
42
|
+
try {
|
43
|
+
await db.connect();
|
44
|
+
console.log('Database connected successfully');
|
45
|
+
} catch (error) {
|
46
|
+
console.error('Database connection failed:', error);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
// Configure auth if secret is provided
|
51
|
+
let auth = null;
|
52
|
+
if (process.env.JWT_SECRET) {
|
53
|
+
auth = new AuthService({
|
54
|
+
secret: process.env.JWT_SECRET,
|
55
|
+
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
|
56
|
+
});
|
57
|
+
|
58
|
+
// Add auth middleware
|
59
|
+
app.use(auth.initialize());
|
60
|
+
|
61
|
+
// Example protected route
|
62
|
+
app.get('/api/protected', auth.requireAuth(), (req, res) => {
|
63
|
+
res.json({ message: 'Protected route accessed successfully' });
|
64
|
+
});
|
65
|
+
|
66
|
+
// Example role-based protection
|
67
|
+
app.get('/api/admin', auth.requireRoles(['admin']), (req, res) => {
|
68
|
+
res.json({ message: 'Admin route accessed successfully' });
|
69
|
+
});
|
70
|
+
|
71
|
+
// Login route
|
72
|
+
app.post('/api/login', async (req, res) => {
|
73
|
+
const { username, password } = req.body;
|
74
|
+
|
75
|
+
// In a real app, fetch user from database
|
76
|
+
const user = { id: 1, username, roles: ['user'] };
|
77
|
+
const token = auth.generateToken(user);
|
78
|
+
|
79
|
+
res.json({ token, user: { id: user.id, username: user.username, roles: user.roles } });
|
80
|
+
});
|
81
|
+
}
|
82
|
+
|
14
83
|
// Serve static files from public directory
|
15
84
|
app.use(express.static(join(__dirname, 'public')));
|
16
85
|
|
@@ -23,6 +92,42 @@ app.get('/api/page-data', (req, res) => {
|
|
23
92
|
});
|
24
93
|
});
|
25
94
|
|
95
|
+
// Meta tag generation function (using local logic for simplicity)
|
96
|
+
async function generateMetaTags(pageContent) {
|
97
|
+
// Extract title from page content
|
98
|
+
const title = pageContent.split('\n')[0].replace(/[#*]/g, '').trim() ||
|
99
|
+
'Frontend Hamroun SSR Page';
|
100
|
+
|
101
|
+
// Generate description from content
|
102
|
+
const description = pageContent.substring(0, 150) + '...';
|
103
|
+
|
104
|
+
// Extract keywords
|
105
|
+
const keywords = pageContent
|
106
|
+
.toLowerCase()
|
107
|
+
.replace(/[^\w\s]/g, '')
|
108
|
+
.split(/\s+/)
|
109
|
+
.filter(w => w.length > 3)
|
110
|
+
.slice(0, 5)
|
111
|
+
.join(', ');
|
112
|
+
|
113
|
+
return {
|
114
|
+
title,
|
115
|
+
description,
|
116
|
+
keywords
|
117
|
+
};
|
118
|
+
}
|
119
|
+
|
120
|
+
// Helper function to check if file exists
|
121
|
+
async function fileExists(path) {
|
122
|
+
try {
|
123
|
+
const fs = await import('fs/promises');
|
124
|
+
await fs.access(path);
|
125
|
+
return true;
|
126
|
+
} catch {
|
127
|
+
return false;
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
26
131
|
// Implement basic SSR without relying on complex server functionality
|
27
132
|
app.get('*', async (req, res) => {
|
28
133
|
try {
|
@@ -60,6 +165,17 @@ app.get('*', async (req, res) => {
|
|
60
165
|
// Import the component
|
61
166
|
const { default: PageComponent } = await import(componentPath);
|
62
167
|
|
168
|
+
// Generate page content for meta tags
|
169
|
+
const pageContent = `
|
170
|
+
Frontend Hamroun SSR Page
|
171
|
+
This is a server-rendered page using the Frontend Hamroun framework.
|
172
|
+
Path: ${req.path}
|
173
|
+
Timestamp: ${new Date().toISOString()}
|
174
|
+
`;
|
175
|
+
|
176
|
+
// Generate meta tags
|
177
|
+
const metaTags = await generateMetaTags(pageContent);
|
178
|
+
|
63
179
|
// Render the component to string
|
64
180
|
const content = renderToString(PageComponent());
|
65
181
|
|
@@ -70,15 +186,36 @@ app.get('*', async (req, res) => {
|
|
70
186
|
<head>
|
71
187
|
<meta charset="UTF-8">
|
72
188
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
73
|
-
|
189
|
+
|
190
|
+
<!-- Generated Meta Tags -->
|
191
|
+
<title>${metaTags.title}</title>
|
192
|
+
<meta name="description" content="${metaTags.description}">
|
193
|
+
<meta name="keywords" content="${metaTags.keywords}">
|
194
|
+
|
195
|
+
<!-- Open Graph Meta Tags -->
|
196
|
+
<meta property="og:title" content="${metaTags.title}">
|
197
|
+
<meta property="og:description" content="${metaTags.description}">
|
198
|
+
<meta property="og:type" content="website">
|
199
|
+
<meta property="og:url" content="${req.protocol}://${req.get('host')}${req.originalUrl}">
|
200
|
+
|
74
201
|
<!-- Import Tailwind-like styles for quick styling -->
|
75
202
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@3.7.4/dist/full.css" rel="stylesheet" type="text/css" />
|
76
203
|
<script src="https://cdn.tailwindcss.com"></script>
|
204
|
+
|
77
205
|
<!-- Client-side script for hydration -->
|
78
206
|
<script type="module" src="/assets/client.js"></script>
|
79
207
|
</head>
|
80
208
|
<body>
|
81
209
|
<div id="app">${content}</div>
|
210
|
+
|
211
|
+
<!-- Add initial state for hydration -->
|
212
|
+
<script>
|
213
|
+
window.__INITIAL_STATE__ = ${JSON.stringify({
|
214
|
+
path: req.path,
|
215
|
+
timestamp: new Date().toISOString(),
|
216
|
+
metaTags
|
217
|
+
})};
|
218
|
+
</script>
|
82
219
|
</body>
|
83
220
|
</html>
|
84
221
|
`);
|
@@ -100,24 +237,39 @@ app.get('*', async (req, res) => {
|
|
100
237
|
}
|
101
238
|
});
|
102
239
|
|
103
|
-
//
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
240
|
+
// Add error handler middleware
|
241
|
+
app.use(errorHandler);
|
242
|
+
|
243
|
+
// Add not found handler for API routes that weren't caught
|
244
|
+
app.use(notFoundHandler);
|
245
|
+
|
246
|
+
// Graceful shutdown function to close database connections
|
247
|
+
function gracefulShutdown() {
|
248
|
+
console.log('Shutting down server...');
|
249
|
+
|
250
|
+
// Close database connection if it exists
|
251
|
+
if (db) {
|
252
|
+
db.disconnect()
|
253
|
+
.then(() => console.log('Database disconnected'))
|
254
|
+
.catch(err => console.error('Error disconnecting from database:', err))
|
255
|
+
.finally(() => process.exit(0));
|
256
|
+
} else {
|
257
|
+
process.exit(0);
|
111
258
|
}
|
112
259
|
}
|
113
260
|
|
114
261
|
// Start the server
|
115
|
-
app.listen(port, () => {
|
262
|
+
const server = app.listen(port, () => {
|
116
263
|
console.log(`Server running at http://localhost:${port}`);
|
264
|
+
console.log(`Available API endpoints:`);
|
265
|
+
console.log(` - GET /api/page-data`);
|
266
|
+
console.log(` - POST /api/login`);
|
267
|
+
if (process.env.JWT_SECRET) {
|
268
|
+
console.log(` - GET /api/protected (requires authentication)`);
|
269
|
+
console.log(` - GET /api/admin (requires admin role)`);
|
270
|
+
}
|
117
271
|
});
|
118
272
|
|
119
273
|
// Handle graceful shutdown
|
120
|
-
process.on('SIGINT',
|
121
|
-
|
122
|
-
process.exit(0);
|
123
|
-
});
|
274
|
+
process.on('SIGINT', gracefulShutdown);
|
275
|
+
process.on('SIGTERM', gracefulShutdown);
|