instaserve 1.1.3 → 1.1.6
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/README.md +65 -1
- package/instaserve +15 -4
- package/module.mjs +65 -13
- package/package.json +1 -1
- package/test.js +28 -0
package/README.md
CHANGED
|
@@ -107,6 +107,39 @@ export default {
|
|
|
107
107
|
}
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
+
### Method-Specific Routes
|
|
111
|
+
|
|
112
|
+
Routes can be defined with HTTP method prefixes to handle different methods on the same path. Supported methods: `GET`, `POST`, `PUT`, `DELETE`.
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
export default {
|
|
116
|
+
// Method-specific routes
|
|
117
|
+
'POST /users': (req, res, data) => {
|
|
118
|
+
return { message: 'Create user', data }
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
'GET /users': (req, res, data) => {
|
|
122
|
+
return { message: 'Get users' }
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
'PUT /users': (req, res, data) => {
|
|
126
|
+
return { message: 'Update user', data }
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
'DELETE /users': (req, res, data) => {
|
|
130
|
+
return { message: 'Delete user', data }
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// Path-only routes still work (backward compatible)
|
|
134
|
+
// These match any HTTP method
|
|
135
|
+
hello: (req, res, data) => {
|
|
136
|
+
return { message: 'Hello World' }
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Method-specific routes take precedence over path-only routes. If no method-specific route matches, the server falls back to path-only route matching.
|
|
142
|
+
|
|
110
143
|
### Special Routes (Middleware)
|
|
111
144
|
|
|
112
145
|
Routes starting with `_` are middleware functions that run on **every request** before the main route handler. They are useful for:
|
|
@@ -152,6 +185,25 @@ Each route function receives:
|
|
|
152
185
|
- URL query parameters
|
|
153
186
|
- Form data
|
|
154
187
|
|
|
188
|
+
### Returning Status Codes
|
|
189
|
+
|
|
190
|
+
Routes can return a 3-digit number (100-999) to set the HTTP status code with an empty response body:
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
export default {
|
|
194
|
+
'GET /notfound': () => 404,
|
|
195
|
+
'GET /unauthorized': () => 401,
|
|
196
|
+
'GET /forbidden': () => 403,
|
|
197
|
+
'GET /teapot': () => 418, // I'm a teapot
|
|
198
|
+
'GET /created': () => 201
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Routes can also return:
|
|
203
|
+
- **Strings** - Sent as plain text response
|
|
204
|
+
- **Objects** - Automatically serialized as JSON
|
|
205
|
+
- **Status codes** - 3-digit numbers (100-999) set HTTP status with empty body
|
|
206
|
+
|
|
155
207
|
### Example Routes File
|
|
156
208
|
|
|
157
209
|
```javascript
|
|
@@ -163,7 +215,19 @@ export default {
|
|
|
163
215
|
return false // Continue to next route
|
|
164
216
|
},
|
|
165
217
|
|
|
166
|
-
//
|
|
218
|
+
// Method-specific routes
|
|
219
|
+
'POST /api/users': (req, res, data) => {
|
|
220
|
+
return { status: 'created', data }
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
'GET /api/users': (req, res, data) => {
|
|
224
|
+
return { status: 'ok', users: [] }
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
'GET /api/notfound': () => 404,
|
|
228
|
+
'GET /api/unauthorized': () => 401,
|
|
229
|
+
|
|
230
|
+
// Path-only route (matches any method)
|
|
167
231
|
api: (req, res, data) => {
|
|
168
232
|
return { status: 'ok', data }
|
|
169
233
|
},
|
package/instaserve
CHANGED
|
@@ -27,9 +27,9 @@ const args = process.argv.slice(2)
|
|
|
27
27
|
|
|
28
28
|
// Handle generate-routes command
|
|
29
29
|
if (args[0] === 'generate-routes') {
|
|
30
|
-
const routesFile = './routes.js'
|
|
30
|
+
const routesFile = path.resolve(process.cwd(), './routes.js')
|
|
31
31
|
if (fs.existsSync(routesFile)) {
|
|
32
|
-
console.error(chalk.red(`Error:
|
|
32
|
+
console.error(chalk.red(`Error: routes.js already exists`))
|
|
33
33
|
process.exit(1)
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -59,12 +59,21 @@ if (args[0] === 'generate-routes') {
|
|
|
59
59
|
|
|
60
60
|
api: (req, res, data) => {
|
|
61
61
|
return { message: 'API endpoint', data }
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
// Method-specific routes (POST, GET, PUT, DELETE)
|
|
65
|
+
'POST /users': (req, res, data) => {
|
|
66
|
+
return { message: 'Create user', data }
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
'GET /users': (req, res, data) => {
|
|
70
|
+
return { message: 'Get users' }
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
73
|
`
|
|
65
74
|
|
|
66
75
|
fs.writeFileSync(routesFile, sampleRoutes)
|
|
67
|
-
console.log(chalk.green(`✓ Created
|
|
76
|
+
console.log(chalk.green(`✓ Created routes.js`))
|
|
68
77
|
process.exit(0)
|
|
69
78
|
}
|
|
70
79
|
|
|
@@ -87,6 +96,7 @@ for (let i = 0; i < args.length; i++) {
|
|
|
87
96
|
|
|
88
97
|
// Load routes file
|
|
89
98
|
let routes = {}
|
|
99
|
+
let routesFilePath = null
|
|
90
100
|
const routesFileParam = params.api || './routes.js'
|
|
91
101
|
const routesFileSpecified = !!params.api
|
|
92
102
|
|
|
@@ -117,10 +127,11 @@ if (fs.existsSync(routesFile)) {
|
|
|
117
127
|
process.exit(1)
|
|
118
128
|
}
|
|
119
129
|
}
|
|
130
|
+
routesFilePath = routesFile
|
|
120
131
|
} catch (e) {
|
|
121
132
|
console.error(chalk.red(`Error: Could not load routes file "${routesFileParam}": ${e.message}`))
|
|
122
133
|
process.exit(1)
|
|
123
134
|
}
|
|
124
135
|
}
|
|
125
136
|
|
|
126
|
-
server(routes)
|
|
137
|
+
server(routes, params.port ? parseInt(params.port) : undefined, params.ip, routesFilePath)
|
package/module.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import http from 'node:http'
|
|
2
2
|
import https from 'node:https'
|
|
3
3
|
import fs from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
4
5
|
|
|
5
6
|
const args = process.argv.slice(2)
|
|
6
7
|
const params = {}
|
|
@@ -18,9 +19,9 @@ for (let i = 0; i < args.length; i++) {
|
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
function public_file(r, s) {
|
|
22
|
+
function public_file(r, s, publicDir) {
|
|
22
23
|
if (r.url == '/') r.url = '/index.html'
|
|
23
|
-
const fn =
|
|
24
|
+
const fn = path.resolve(publicDir, r.url.replace(/\.\./g, '').replace(/^\//, ''))
|
|
24
25
|
if (fs.existsSync(fn)) {
|
|
25
26
|
const content = fs.readFileSync(fn, 'utf-8')
|
|
26
27
|
if (fn.match(/.js$/)) {
|
|
@@ -34,8 +35,11 @@ function public_file(r, s) {
|
|
|
34
35
|
return false // Indicate no file was served
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
export default async function (routes, port = params.port || 3000, ip = params.ip || '127.0.0.1') {
|
|
38
|
-
const
|
|
38
|
+
export default async function (routes, port = params.port || 3000, ip = params.ip || '127.0.0.1', routesFilePath = null) {
|
|
39
|
+
const publicDirParam = params.public || './public'
|
|
40
|
+
const publicDir = path.isAbsolute(publicDirParam)
|
|
41
|
+
? publicDirParam
|
|
42
|
+
: path.resolve(process.cwd(), publicDirParam)
|
|
39
43
|
if (publicDir.includes('..')) {
|
|
40
44
|
throw new Error('Public directory path cannot contain ".."')
|
|
41
45
|
}
|
|
@@ -47,6 +51,21 @@ export default async function (routes, port = params.port || 3000, ip = params.i
|
|
|
47
51
|
let sdata = '', rrurl = r.url || ''
|
|
48
52
|
let responseSent = false
|
|
49
53
|
|
|
54
|
+
// Helper to check if value is a 3-digit HTTP status code
|
|
55
|
+
const isStatusCode = (val) => {
|
|
56
|
+
return typeof val === 'number' && val >= 100 && val <= 999 && Math.floor(val) === val
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Helper to send response, handling status codes
|
|
60
|
+
const sendResponse = (result) => {
|
|
61
|
+
if (isStatusCode(result)) {
|
|
62
|
+
s.writeHead(result)
|
|
63
|
+
s.end()
|
|
64
|
+
} else {
|
|
65
|
+
s.end(typeof result === 'string' ? result : JSON.stringify(result))
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
50
69
|
r.on('data', (s) => sdata += s.toString().trim())
|
|
51
70
|
r.on('end', (x) => {
|
|
52
71
|
try {
|
|
@@ -64,7 +83,7 @@ export default async function (routes, port = params.port || 3000, ip = params.i
|
|
|
64
83
|
const result = routes[k](r, s, data)
|
|
65
84
|
if (result && !responseSent) {
|
|
66
85
|
responseSent = true
|
|
67
|
-
|
|
86
|
+
sendResponse(result)
|
|
68
87
|
}
|
|
69
88
|
return result
|
|
70
89
|
})
|
|
@@ -73,17 +92,47 @@ export default async function (routes, port = params.port || 3000, ip = params.i
|
|
|
73
92
|
if(responseSent || s.writableEnded) return
|
|
74
93
|
|
|
75
94
|
// Try to serve public file
|
|
76
|
-
if(public_file(r, s)) {
|
|
95
|
+
if(public_file(r, s, publicDir)) {
|
|
77
96
|
responseSent = true
|
|
78
97
|
return
|
|
79
98
|
}
|
|
80
99
|
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
100
|
+
const urlParts = rrurl.split('/')
|
|
101
|
+
const url = urlParts.length > 1 ? urlParts[1].split('?')[0] : ''
|
|
102
|
+
const method = (r.method || 'GET').toUpperCase()
|
|
103
|
+
|
|
104
|
+
// Try method-specific route first (e.g., "POST /endp", "GET /")
|
|
105
|
+
const methodRoute = url ? `${method} /${url}` : `${method} /`
|
|
106
|
+
let routeHandler = routes[methodRoute]
|
|
107
|
+
|
|
108
|
+
// If no exact match, check if any method-specific route exists for this path
|
|
109
|
+
if (!routeHandler) {
|
|
110
|
+
const methods = ['GET', 'POST', 'PUT', 'DELETE']
|
|
111
|
+
const pathRoute = url ? `/${url}` : `/`
|
|
112
|
+
const hasMethodSpecificRoute = methods.some(m => {
|
|
113
|
+
const checkRoute = url ? `${m} /${url}` : `${m} /`
|
|
114
|
+
return routes[checkRoute] !== undefined
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
// If method-specific route exists but for different method, return 405
|
|
118
|
+
if (hasMethodSpecificRoute) {
|
|
119
|
+
if (!responseSent && !s.writableEnded) {
|
|
120
|
+
responseSent = true
|
|
121
|
+
s.writeHead(405)
|
|
122
|
+
s.end()
|
|
123
|
+
}
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Fall back to path-only route (backward compatible)
|
|
128
|
+
routeHandler = routes[url]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (routeHandler) {
|
|
132
|
+
const resp = routeHandler(r, s, data)
|
|
84
133
|
if (!responseSent && !s.writableEnded) {
|
|
85
134
|
responseSent = true
|
|
86
|
-
|
|
135
|
+
sendResponse(resp)
|
|
87
136
|
}
|
|
88
137
|
return
|
|
89
138
|
}
|
|
@@ -105,8 +154,8 @@ export default async function (routes, port = params.port || 3000, ip = params.i
|
|
|
105
154
|
|
|
106
155
|
let server
|
|
107
156
|
if (params.secure) {
|
|
108
|
-
const certPath = './cert.pem'
|
|
109
|
-
const keyPath = './key.pem'
|
|
157
|
+
const certPath = path.resolve(process.cwd(), './cert.pem')
|
|
158
|
+
const keyPath = path.resolve(process.cwd(), './key.pem')
|
|
110
159
|
|
|
111
160
|
if (!fs.existsSync(certPath) || !fs.existsSync(keyPath)) {
|
|
112
161
|
throw new Error('Certificate files not found. Run ./generate-certs.sh first.')
|
|
@@ -125,7 +174,10 @@ export default async function (routes, port = params.port || 3000, ip = params.i
|
|
|
125
174
|
server.listen(port || 3000, ip || '')
|
|
126
175
|
|
|
127
176
|
const protocol = params.secure ? 'https' : 'http'
|
|
128
|
-
|
|
177
|
+
const routesInfo = Object.keys(routes).length > 0
|
|
178
|
+
? (routesFilePath ? `using routes: ${Object.keys(routes)} (${routesFilePath})` : `using routes: ${Object.keys(routes)}`)
|
|
179
|
+
: 'not using routes'
|
|
180
|
+
console.log(`started on: ${protocol}://${(process.env.ip || ip)}:${(process.env.port || port)}, public: ${publicDir}, ${routesInfo}`)
|
|
129
181
|
|
|
130
182
|
return {
|
|
131
183
|
routes: routes,
|
package/package.json
CHANGED
package/test.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
// Middleware functions (prefixed with _) run on every request
|
|
3
|
+
// Return false to continue processing, or a value to use as response
|
|
4
|
+
|
|
5
|
+
// Example: Log all requests
|
|
6
|
+
_log: (req, res, data) => {
|
|
7
|
+
console.log(`${req.method} ${req.url}`)
|
|
8
|
+
return false // Continue to next middleware or route
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
// Example: Basic authentication (commented out)
|
|
12
|
+
// _auth: (req, res, data) => {
|
|
13
|
+
// if (!data.token) {
|
|
14
|
+
// res.writeHead(401)
|
|
15
|
+
// return 'Unauthorized'
|
|
16
|
+
// }
|
|
17
|
+
// return false // Continue if authorized
|
|
18
|
+
// },
|
|
19
|
+
|
|
20
|
+
// Regular route handlers
|
|
21
|
+
hello: (req, res, data) => {
|
|
22
|
+
return { message: 'Hello World' }
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
api: (req, res, data) => {
|
|
26
|
+
return { message: 'API endpoint', data }
|
|
27
|
+
}
|
|
28
|
+
}
|