@owlmeans/server-api 0.1.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/LICENSE +21 -0
- package/README.md +613 -0
- package/build/.gitkeep +0 -0
- package/build/consts.d.ts +5 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +5 -0
- package/build/consts.js.map +1 -0
- package/build/errors.d.ts +14 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +27 -0
- package/build/errors.js.map +1 -0
- package/build/helper.d.ts +13 -0
- package/build/helper.d.ts.map +1 -0
- package/build/helper.js +53 -0
- package/build/helper.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +5 -0
- package/build/index.js.map +1 -0
- package/build/server.d.ts +7 -0
- package/build/server.d.ts.map +1 -0
- package/build/server.js +146 -0
- package/build/server.js.map +1 -0
- package/build/types.d.ts +21 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/utils/context.d.ts +4 -0
- package/build/utils/context.d.ts.map +1 -0
- package/build/utils/context.js +5 -0
- package/build/utils/context.js.map +1 -0
- package/build/utils/error.d.ts +3 -0
- package/build/utils/error.d.ts.map +1 -0
- package/build/utils/error.js +16 -0
- package/build/utils/error.js.map +1 -0
- package/build/utils/guards.d.ts +9 -0
- package/build/utils/guards.d.ts.map +1 -0
- package/build/utils/guards.js +57 -0
- package/build/utils/guards.js.map +1 -0
- package/build/utils/index.d.ts +7 -0
- package/build/utils/index.d.ts.map +1 -0
- package/build/utils/index.js +7 -0
- package/build/utils/index.js.map +1 -0
- package/build/utils/payload.d.ts +10 -0
- package/build/utils/payload.d.ts.map +1 -0
- package/build/utils/payload.js +55 -0
- package/build/utils/payload.js.map +1 -0
- package/build/utils/server.d.ts +10 -0
- package/build/utils/server.d.ts.map +1 -0
- package/build/utils/server.js +56 -0
- package/build/utils/server.js.map +1 -0
- package/package.json +62 -0
- package/src/consts.ts +7 -0
- package/src/errors.ts +35 -0
- package/src/helper.ts +89 -0
- package/src/index.ts +6 -0
- package/src/server.ts +183 -0
- package/src/types.ts +26 -0
- package/src/utils/context.ts +9 -0
- package/src/utils/error.ts +18 -0
- package/src/utils/guards.ts +75 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/payload.ts +62 -0
- package/src/utils/server.ts +70 -0
- package/tsconfig.json +15 -0
- package/tsconfig.tsbuildinfo +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 OwlMeans Common — Fullstack typescript framework
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
# @owlmeans/server-api
|
|
2
|
+
|
|
3
|
+
Server-side API framework for OwlMeans Common applications. This package provides a comprehensive HTTP server implementation built on Fastify, with seamless integration into the OwlMeans module system, authentication, validation, and error handling.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `@owlmeans/server-api` package is a high-level server framework that extends the OwlMeans ecosystem with HTTP API capabilities. It provides:
|
|
8
|
+
|
|
9
|
+
- **Fastify-based HTTP Server**: High-performance HTTP server with comprehensive middleware
|
|
10
|
+
- **Module Integration**: Seamless integration with OwlMeans modules for route handling
|
|
11
|
+
- **Authentication & Authorization**: Built-in support for authentication guards and gates
|
|
12
|
+
- **Request/Response Handling**: Structured request/response processing with validation
|
|
13
|
+
- **File Upload Support**: Multipart file upload handling with configurable limits
|
|
14
|
+
- **Error Management**: Comprehensive error handling with resilient error system
|
|
15
|
+
- **Security Features**: CORS, Helmet, and security middleware integration
|
|
16
|
+
- **Context Propagation**: Request-scoped context management throughout the request lifecycle
|
|
17
|
+
|
|
18
|
+
This package is part of the OwlMeans "quadra" pattern as a server-side implementation, complementing client-side packages and providing the backend foundation for fullstack applications.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @owlmeans/server-api
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Dependencies
|
|
27
|
+
|
|
28
|
+
This package requires and integrates with:
|
|
29
|
+
- `@owlmeans/server-context`: Server context management
|
|
30
|
+
- `@owlmeans/server-module`: Server-side module system
|
|
31
|
+
- `@owlmeans/auth-common`: Authentication middleware
|
|
32
|
+
- `@owlmeans/module`: Core module system
|
|
33
|
+
- `@owlmeans/route`: Routing infrastructure
|
|
34
|
+
- `@owlmeans/api`: API utilities and constants
|
|
35
|
+
- `fastify`: High-performance HTTP server
|
|
36
|
+
|
|
37
|
+
## Core Concepts
|
|
38
|
+
|
|
39
|
+
### API Server Service
|
|
40
|
+
|
|
41
|
+
The API server is implemented as an OwlMeans service that manages a Fastify instance, handles module registration, and provides lifecycle management.
|
|
42
|
+
|
|
43
|
+
### Module-based Routing
|
|
44
|
+
|
|
45
|
+
Routes are defined through OwlMeans modules, which are automatically registered with the Fastify server and processed according to their configuration.
|
|
46
|
+
|
|
47
|
+
### Context Propagation
|
|
48
|
+
|
|
49
|
+
Each request receives a context that contains authentication information, services, and can be modified by intermediate modules throughout the request lifecycle.
|
|
50
|
+
|
|
51
|
+
### Request/Response Abstraction
|
|
52
|
+
|
|
53
|
+
The framework provides abstracted request/response objects that hide Fastify-specific details while providing access to the underlying objects when needed.
|
|
54
|
+
|
|
55
|
+
## API Reference
|
|
56
|
+
|
|
57
|
+
### Types
|
|
58
|
+
|
|
59
|
+
#### `ApiServer`
|
|
60
|
+
Main server service interface that manages the Fastify instance.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
interface ApiServer extends InitializedService {
|
|
64
|
+
server: FastifyInstance
|
|
65
|
+
layers: [Layer.System]
|
|
66
|
+
listen(): Promise<void>
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Properties:**
|
|
71
|
+
- `server`: The underlying Fastify instance
|
|
72
|
+
- `layers`: Service operates at System layer
|
|
73
|
+
|
|
74
|
+
**Methods:**
|
|
75
|
+
- `listen(): Promise<void>`: Starts the HTTP server and listens for connections
|
|
76
|
+
|
|
77
|
+
#### `Request`
|
|
78
|
+
Type alias for Fastify request objects.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
interface Request extends FastifyRequest {}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### `Response`
|
|
85
|
+
Type alias for Fastify reply objects.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
interface Response extends FastifyReply {}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### `Config`
|
|
92
|
+
Server configuration interface.
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
interface Config extends ServerConfig {}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### `Context<C extends Config>`
|
|
99
|
+
Server context interface with API server capabilities.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
interface Context<C extends Config = Config> extends ServerContext<C>, ApiServerAppend {}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### `ApiServerAppend`
|
|
106
|
+
Interface for contexts that provide API server access.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
interface ApiServerAppend {
|
|
110
|
+
getApiServer(): ApiServer
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Factory Functions
|
|
115
|
+
|
|
116
|
+
#### `createApiServer(alias: string): ApiServer`
|
|
117
|
+
|
|
118
|
+
Creates an API server service instance with comprehensive middleware and configuration.
|
|
119
|
+
|
|
120
|
+
**Parameters:**
|
|
121
|
+
- `alias`: Service alias for registration
|
|
122
|
+
|
|
123
|
+
**Returns:** ApiServer instance
|
|
124
|
+
|
|
125
|
+
**Features:**
|
|
126
|
+
- Fastify server with logging
|
|
127
|
+
- CORS support with configurable origins
|
|
128
|
+
- Security headers via Helmet
|
|
129
|
+
- File upload support with multipart
|
|
130
|
+
- Raw body parsing
|
|
131
|
+
- AJV validation with format support
|
|
132
|
+
- Module-based routing
|
|
133
|
+
- Authentication integration
|
|
134
|
+
- Error handling
|
|
135
|
+
|
|
136
|
+
**Example:**
|
|
137
|
+
```typescript
|
|
138
|
+
import { createApiServer } from '@owlmeans/server-api'
|
|
139
|
+
|
|
140
|
+
const apiServer = createApiServer('main-api')
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Helper Functions
|
|
144
|
+
|
|
145
|
+
#### `handleBody<T>(handler: (payload: T, ctx: BasicContext, req: AbstractRequest) => Promise<any>): RefedModuleHandler`
|
|
146
|
+
|
|
147
|
+
Creates a module handler that processes request body data.
|
|
148
|
+
|
|
149
|
+
**Parameters:**
|
|
150
|
+
- `handler`: Function that processes the request body
|
|
151
|
+
|
|
152
|
+
**Returns:** Module handler function
|
|
153
|
+
|
|
154
|
+
**Example:**
|
|
155
|
+
```typescript
|
|
156
|
+
import { handleBody } from '@owlmeans/server-api'
|
|
157
|
+
|
|
158
|
+
const createUserHandler = handleBody<CreateUserRequest>(async (payload, ctx, req) => {
|
|
159
|
+
const userService = ctx.service('user')
|
|
160
|
+
return await userService.create(payload)
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### `handleParams<T>(handler: (payload: T, ctx: BasicContext, req: AbstractRequest) => Promise<any>): RefedModuleHandler`
|
|
165
|
+
|
|
166
|
+
Creates a module handler that processes URL parameters.
|
|
167
|
+
|
|
168
|
+
**Parameters:**
|
|
169
|
+
- `handler`: Function that processes the URL parameters
|
|
170
|
+
|
|
171
|
+
**Returns:** Module handler function
|
|
172
|
+
|
|
173
|
+
**Example:**
|
|
174
|
+
```typescript
|
|
175
|
+
import { handleParams } from '@owlmeans/server-api'
|
|
176
|
+
|
|
177
|
+
const getUserHandler = handleParams<{ id: string }>(async (params, ctx) => {
|
|
178
|
+
const userService = ctx.service('user')
|
|
179
|
+
return await userService.get(params.id)
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### `handleRequest(handler: (req: AbstractRequest, ctx: BasicContext, res?: AbstractResponse) => Promise<any>): RefedModuleHandler`
|
|
184
|
+
|
|
185
|
+
Creates a module handler that processes the full request object.
|
|
186
|
+
|
|
187
|
+
**Parameters:**
|
|
188
|
+
- `handler`: Function that processes the request
|
|
189
|
+
|
|
190
|
+
**Returns:** Module handler function
|
|
191
|
+
|
|
192
|
+
**Example:**
|
|
193
|
+
```typescript
|
|
194
|
+
import { handleRequest } from '@owlmeans/server-api'
|
|
195
|
+
|
|
196
|
+
const customHandler = handleRequest(async (req, ctx, res) => {
|
|
197
|
+
// Custom request processing logic
|
|
198
|
+
return { message: 'Custom response' }
|
|
199
|
+
})
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### `handleIntermediate(handler: (req: AbstractRequest, ctx: BasicContext) => Promise<BasicContext | null>): RefedModuleHandler`
|
|
203
|
+
|
|
204
|
+
Creates an intermediate module handler that can modify the request context.
|
|
205
|
+
|
|
206
|
+
**Parameters:**
|
|
207
|
+
- `handler`: Function that processes request and potentially modifies context
|
|
208
|
+
|
|
209
|
+
**Returns:** Module handler function
|
|
210
|
+
|
|
211
|
+
**Example:**
|
|
212
|
+
```typescript
|
|
213
|
+
import { handleIntermediate } from '@owlmeans/server-api'
|
|
214
|
+
|
|
215
|
+
const authMiddleware = handleIntermediate(async (req, ctx) => {
|
|
216
|
+
// Add authentication data to context
|
|
217
|
+
const modifiedContext = await addAuthToContext(ctx, req)
|
|
218
|
+
return modifiedContext
|
|
219
|
+
})
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### `extractUploadedFile(req: AbstractRequest): Promise<UploadedFile | undefined>`
|
|
223
|
+
|
|
224
|
+
Extracts an uploaded file from a multipart request.
|
|
225
|
+
|
|
226
|
+
**Parameters:**
|
|
227
|
+
- `req`: Abstract request object
|
|
228
|
+
|
|
229
|
+
**Returns:** Promise resolving to uploaded file or undefined
|
|
230
|
+
|
|
231
|
+
**Example:**
|
|
232
|
+
```typescript
|
|
233
|
+
import { extractUploadedFile } from '@owlmeans/server-api'
|
|
234
|
+
|
|
235
|
+
const uploadHandler = handleRequest(async (req, ctx) => {
|
|
236
|
+
const file = await extractUploadedFile(req)
|
|
237
|
+
if (file) {
|
|
238
|
+
// Process uploaded file
|
|
239
|
+
return { filename: file.filename, size: file.file.bytesRead }
|
|
240
|
+
}
|
|
241
|
+
throw new NoFileError()
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Error Types
|
|
246
|
+
|
|
247
|
+
The package provides comprehensive error classes for API-specific failures:
|
|
248
|
+
|
|
249
|
+
#### `AuthFailedError`
|
|
250
|
+
Error for authentication failures.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
class AuthFailedError extends ApiError {
|
|
254
|
+
constructor(message: string = 'error')
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
#### `AccessError`
|
|
259
|
+
Error for authorization/access failures.
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
class AccessError extends ApiError {
|
|
263
|
+
constructor(message: string = 'error')
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
#### `NoFileError`
|
|
268
|
+
Error when an expected file upload is missing.
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
class NoFileError extends ApiError {
|
|
272
|
+
constructor()
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Constants
|
|
277
|
+
|
|
278
|
+
#### Server Configuration
|
|
279
|
+
```typescript
|
|
280
|
+
const DEFAULT_ALIAS = 'api-server' // Default service alias
|
|
281
|
+
const PORT = 80 // Default HTTP port
|
|
282
|
+
const CLOSED_HOST = '127.0.0.1' // Localhost binding
|
|
283
|
+
const OPENED_HOST = '0.0.0.0' // Open binding for external access
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Utility Functions
|
|
287
|
+
|
|
288
|
+
Available in the `/utils` subpackage:
|
|
289
|
+
|
|
290
|
+
#### Request/Response Utilities
|
|
291
|
+
- `provideRequest(alias, req, provision?)`: Creates AbstractRequest from Fastify request
|
|
292
|
+
- `executeResponse(response, reply, throwOnError?)`: Executes AbstractResponse with Fastify reply
|
|
293
|
+
|
|
294
|
+
#### Server Utilities
|
|
295
|
+
- `canServeModule(context, module)`: Determines if a module can be served by the API server
|
|
296
|
+
- `createServerHandler(module, location)`: Creates Fastify route handler from OwlMeans module
|
|
297
|
+
|
|
298
|
+
#### Guard Utilities
|
|
299
|
+
- `authorize(context, module, req, reply)`: Handles authentication and authorization for modules
|
|
300
|
+
|
|
301
|
+
## Usage Examples
|
|
302
|
+
|
|
303
|
+
### Basic API Server Setup
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { createApiServer } from '@owlmeans/server-api'
|
|
307
|
+
import { makeServerContext } from '@owlmeans/server-context'
|
|
308
|
+
import { AppType, Layer } from '@owlmeans/context'
|
|
309
|
+
|
|
310
|
+
// Create server context
|
|
311
|
+
const context = makeServerContext({
|
|
312
|
+
service: 'my-api',
|
|
313
|
+
type: AppType.Backend,
|
|
314
|
+
layer: Layer.Service,
|
|
315
|
+
services: {
|
|
316
|
+
'my-api': {
|
|
317
|
+
host: 'localhost',
|
|
318
|
+
port: 3000,
|
|
319
|
+
opened: true
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
// Create and register API server
|
|
325
|
+
const apiServer = createApiServer('main-api')
|
|
326
|
+
context.registerService(apiServer)
|
|
327
|
+
|
|
328
|
+
// Initialize and start server
|
|
329
|
+
await context.configure().init()
|
|
330
|
+
await apiServer.listen()
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Module-based API Endpoints
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { module } from '@owlmeans/module'
|
|
337
|
+
import { route, backend, RouteMethod } from '@owlmeans/route'
|
|
338
|
+
import { handleBody, handleParams } from '@owlmeans/server-api'
|
|
339
|
+
|
|
340
|
+
// Create API modules
|
|
341
|
+
const getUserModule = module(
|
|
342
|
+
route('get-user', '/api/users/:id', backend(null, RouteMethod.GET)),
|
|
343
|
+
{
|
|
344
|
+
handle: handleParams<{ id: string }>(async (params, ctx) => {
|
|
345
|
+
const userService = ctx.service('user')
|
|
346
|
+
return await userService.get(params.id)
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
const createUserModule = module(
|
|
352
|
+
route('create-user', '/api/users', backend(null, RouteMethod.POST)),
|
|
353
|
+
{
|
|
354
|
+
handle: handleBody<CreateUserRequest>(async (payload, ctx) => {
|
|
355
|
+
const userService = ctx.service('user')
|
|
356
|
+
return await userService.create(payload)
|
|
357
|
+
}),
|
|
358
|
+
filter: {
|
|
359
|
+
body: {
|
|
360
|
+
type: 'object',
|
|
361
|
+
properties: {
|
|
362
|
+
name: { type: 'string' },
|
|
363
|
+
email: { type: 'string', format: 'email' }
|
|
364
|
+
},
|
|
365
|
+
required: ['name', 'email']
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
// Register modules with context
|
|
372
|
+
context.registerModule(getUserModule)
|
|
373
|
+
context.registerModule(createUserModule)
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Authentication and Authorization
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
import { guard, gate } from '@owlmeans/module'
|
|
380
|
+
|
|
381
|
+
// Protected endpoint with authentication guard
|
|
382
|
+
const protectedModule = module(
|
|
383
|
+
route('protected', '/api/admin/users', backend(null, RouteMethod.GET)),
|
|
384
|
+
{
|
|
385
|
+
...guard('auth'), // Requires authentication
|
|
386
|
+
...gate('admin', ['users']), // Requires admin gate with users permission
|
|
387
|
+
handle: handleRequest(async (req, ctx) => {
|
|
388
|
+
// Only authenticated admin users can access this
|
|
389
|
+
return { message: 'Admin access granted' }
|
|
390
|
+
})
|
|
391
|
+
}
|
|
392
|
+
)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### File Upload Handling
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import { extractUploadedFile, NoFileError } from '@owlmeans/server-api'
|
|
399
|
+
|
|
400
|
+
const uploadModule = module(
|
|
401
|
+
route('upload', '/api/upload', backend(null, RouteMethod.POST)),
|
|
402
|
+
{
|
|
403
|
+
handle: handleRequest(async (req, ctx) => {
|
|
404
|
+
const file = await extractUploadedFile(req)
|
|
405
|
+
if (!file) {
|
|
406
|
+
throw new NoFileError()
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Process the uploaded file
|
|
410
|
+
const fileData = await file.toBuffer()
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
filename: file.filename,
|
|
414
|
+
size: fileData.length,
|
|
415
|
+
mimetype: file.mimetype
|
|
416
|
+
}
|
|
417
|
+
})
|
|
418
|
+
}
|
|
419
|
+
)
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Intermediate Middleware
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
import { handleIntermediate } from '@owlmeans/server-api'
|
|
426
|
+
|
|
427
|
+
// Logging middleware
|
|
428
|
+
const loggingModule = module(
|
|
429
|
+
route('logging', '/api/*'),
|
|
430
|
+
{
|
|
431
|
+
sticky: true, // Apply to all routes
|
|
432
|
+
handle: handleIntermediate(async (req, ctx) => {
|
|
433
|
+
console.log(`${req.method} ${req.path}`)
|
|
434
|
+
return ctx // Return context unchanged
|
|
435
|
+
})
|
|
436
|
+
}
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
// Authentication middleware
|
|
440
|
+
const authModule = module(
|
|
441
|
+
route('auth-middleware', '/api/auth/*'),
|
|
442
|
+
{
|
|
443
|
+
sticky: true,
|
|
444
|
+
handle: handleIntermediate(async (req, ctx) => {
|
|
445
|
+
// Extract and validate auth token
|
|
446
|
+
const authToken = req.headers.authorization
|
|
447
|
+
if (authToken) {
|
|
448
|
+
const authService = ctx.service('auth')
|
|
449
|
+
const auth = await authService.validate(authToken)
|
|
450
|
+
// Add auth to request for downstream handlers
|
|
451
|
+
req.auth = auth
|
|
452
|
+
}
|
|
453
|
+
return ctx
|
|
454
|
+
})
|
|
455
|
+
}
|
|
456
|
+
)
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Error Handling
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
import { AuthFailedError, AccessError } from '@owlmeans/server-api'
|
|
463
|
+
|
|
464
|
+
const secureModule = module(
|
|
465
|
+
route('secure', '/api/secure', backend()),
|
|
466
|
+
{
|
|
467
|
+
handle: handleRequest(async (req, ctx) => {
|
|
468
|
+
// Check authentication
|
|
469
|
+
if (!req.auth) {
|
|
470
|
+
throw new AuthFailedError('Authentication required')
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Check authorization
|
|
474
|
+
if (req.auth.role !== 'admin') {
|
|
475
|
+
throw new AccessError('Admin access required')
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return { message: 'Secure data' }
|
|
479
|
+
})
|
|
480
|
+
}
|
|
481
|
+
)
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Custom Server Configuration
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
import { createApiServer } from '@owlmeans/server-api'
|
|
488
|
+
|
|
489
|
+
const apiServer = createApiServer('custom-api')
|
|
490
|
+
|
|
491
|
+
// Access underlying Fastify instance for custom configuration
|
|
492
|
+
apiServer.server.register(async (fastify) => {
|
|
493
|
+
// Custom Fastify plugins or configuration
|
|
494
|
+
fastify.addHook('preHandler', async (request, reply) => {
|
|
495
|
+
// Custom pre-handler logic
|
|
496
|
+
})
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
// Custom listen configuration
|
|
500
|
+
const context = makeServerContext({
|
|
501
|
+
services: {
|
|
502
|
+
'custom-api': {
|
|
503
|
+
host: '0.0.0.0',
|
|
504
|
+
port: 8080,
|
|
505
|
+
opened: true,
|
|
506
|
+
internalPort: 8080 // Use different internal port
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
})
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
## Advanced Features
|
|
513
|
+
|
|
514
|
+
### Context Modification
|
|
515
|
+
|
|
516
|
+
Intermediate modules can modify the request context, allowing for dynamic service injection and request preprocessing:
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
const contextModifier = handleIntermediate(async (req, ctx) => {
|
|
520
|
+
// Dynamically add services based on request
|
|
521
|
+
if (req.headers['x-tenant-id']) {
|
|
522
|
+
const tenantCtx = await createTenantContext(req.headers['x-tenant-id'])
|
|
523
|
+
return tenantCtx
|
|
524
|
+
}
|
|
525
|
+
return ctx
|
|
526
|
+
})
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Request Lifecycle
|
|
530
|
+
|
|
531
|
+
1. **Pre-handler Hook**: Processes intermediate modules in order
|
|
532
|
+
2. **Authentication**: Validates authentication guards
|
|
533
|
+
3. **Authorization**: Checks authorization gates
|
|
534
|
+
4. **Validation**: Validates request schema (body, params, query)
|
|
535
|
+
5. **Handler Execution**: Executes module handler
|
|
536
|
+
6. **Response Processing**: Formats and sends response
|
|
537
|
+
|
|
538
|
+
### Server Configuration
|
|
539
|
+
|
|
540
|
+
The server automatically configures based on context service settings:
|
|
541
|
+
|
|
542
|
+
- `host`: Server host binding
|
|
543
|
+
- `port`: Server port
|
|
544
|
+
- `internalPort`: Alternative port for internal binding
|
|
545
|
+
- `opened`: Whether to bind to external interfaces (0.0.0.0) or localhost only
|
|
546
|
+
|
|
547
|
+
## Integration with OwlMeans Ecosystem
|
|
548
|
+
|
|
549
|
+
### Module Integration
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
import { modules } from '@owlmeans/server-module'
|
|
553
|
+
|
|
554
|
+
// Server modules are automatically processed by the API server
|
|
555
|
+
context.registerModules(modules)
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### Authentication Integration
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
import { makeAuthService } from '@owlmeans/server-auth'
|
|
562
|
+
|
|
563
|
+
const authService = makeAuthService()
|
|
564
|
+
context.registerService(authService)
|
|
565
|
+
|
|
566
|
+
// Auth service is automatically used by authentication guards
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### Resource Integration
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
import { createDbService } from '@owlmeans/mongo-resource'
|
|
573
|
+
|
|
574
|
+
const dbService = createDbService('mongo', config)
|
|
575
|
+
context.registerService(dbService)
|
|
576
|
+
|
|
577
|
+
// Database services are available in module handlers
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
## Performance Considerations
|
|
581
|
+
|
|
582
|
+
- **Fastify Framework**: High-performance HTTP server
|
|
583
|
+
- **Context Reuse**: Request contexts are reused where possible
|
|
584
|
+
- **Validation Caching**: AJV schemas are compiled once and cached
|
|
585
|
+
- **Middleware Pipeline**: Efficient middleware processing with early termination
|
|
586
|
+
- **File Upload Limits**: Configurable file size and count limits
|
|
587
|
+
|
|
588
|
+
## Security Features
|
|
589
|
+
|
|
590
|
+
- **CORS Support**: Configurable cross-origin resource sharing
|
|
591
|
+
- **Helmet Integration**: Security headers middleware
|
|
592
|
+
- **Authentication Guards**: Built-in authentication checking
|
|
593
|
+
- **Authorization Gates**: Fine-grained permission checking
|
|
594
|
+
- **Input Validation**: Comprehensive request validation
|
|
595
|
+
- **Error Handling**: Secure error message handling
|
|
596
|
+
|
|
597
|
+
## Best Practices
|
|
598
|
+
|
|
599
|
+
1. **Module Organization**: Group related endpoints into logical modules
|
|
600
|
+
2. **Error Handling**: Use appropriate error types for different failure modes
|
|
601
|
+
3. **Validation**: Always validate input data using schema validation
|
|
602
|
+
4. **Authentication**: Use authentication guards for protected endpoints
|
|
603
|
+
5. **Authorization**: Implement fine-grained authorization with gates
|
|
604
|
+
6. **File Uploads**: Configure appropriate file size limits
|
|
605
|
+
7. **Context Management**: Use intermediate modules for cross-cutting concerns
|
|
606
|
+
|
|
607
|
+
## Related Packages
|
|
608
|
+
|
|
609
|
+
- [`@owlmeans/server-context`](../server-context) - Server context management
|
|
610
|
+
- [`@owlmeans/server-module`](../server-module) - Server module system
|
|
611
|
+
- [`@owlmeans/auth-common`](../auth-common) - Authentication middleware
|
|
612
|
+
- [`@owlmeans/api`](../api) - API utilities and constants
|
|
613
|
+
- [`@owlmeans/module`](../module) - Core module system
|
package/build/.gitkeep
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,aAAa,eAAe,CAAA;AAEzC,eAAO,MAAM,IAAI,KAAK,CAAA;AAEtB,eAAO,MAAM,WAAW,cAAc,CAAA;AACtC,eAAO,MAAM,WAAW,YAAY,CAAA"}
|
package/build/consts.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,aAAa,GAAG,YAAY,CAAA;AAEzC,MAAM,CAAC,MAAM,IAAI,GAAG,EAAE,CAAA;AAEtB,MAAM,CAAC,MAAM,WAAW,GAAG,WAAW,CAAA;AACtC,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ApiError } from '@owlmeans/api';
|
|
2
|
+
export declare class AuthFailedError extends ApiError {
|
|
3
|
+
static typeName: string;
|
|
4
|
+
constructor(message?: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class AccessError extends ApiError {
|
|
7
|
+
static typeName: string;
|
|
8
|
+
constructor(message?: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class NoFileError extends ApiError {
|
|
11
|
+
static typeName: string;
|
|
12
|
+
constructor();
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAGxC,qBAAa,eAAgB,SAAQ,QAAQ;IAC3C,OAAuB,QAAQ,SAAoB;gBAEvC,OAAO,GAAE,MAAgB;CAItC;AAED,qBAAa,WAAY,SAAQ,QAAQ;IACvC,OAAuB,QAAQ,SAAsC;gBAEzD,OAAO,GAAE,MAAgB;CAItC;AAED,qBAAa,WAAY,SAAQ,QAAQ;IACvC,OAAuB,QAAQ,SAAgB;;CAMhD"}
|
package/build/errors.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ApiError } from '@owlmeans/api';
|
|
2
|
+
import { ResilientError } from '@owlmeans/error';
|
|
3
|
+
export class AuthFailedError extends ApiError {
|
|
4
|
+
static typeName = 'AuthFailedError';
|
|
5
|
+
constructor(message = 'error') {
|
|
6
|
+
super(`auth:${message}`);
|
|
7
|
+
this.type = AuthFailedError.typeName;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export class AccessError extends ApiError {
|
|
11
|
+
static typeName = `Access${AuthFailedError.typeName}`;
|
|
12
|
+
constructor(message = 'error') {
|
|
13
|
+
super(`access:${message}`);
|
|
14
|
+
this.type = AccessError.typeName;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class NoFileError extends ApiError {
|
|
18
|
+
static typeName = 'NoFileError';
|
|
19
|
+
constructor() {
|
|
20
|
+
super('no file');
|
|
21
|
+
this.type = NoFileError.typeName;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
ResilientError.registerErrorClass(AuthFailedError);
|
|
25
|
+
ResilientError.registerErrorClass(AccessError);
|
|
26
|
+
ResilientError.registerErrorClass(NoFileError);
|
|
27
|
+
//# sourceMappingURL=errors.js.map
|