@dooboostore/simple-boot-http-server
Version:
back end http server frameworks
263 lines (208 loc) • 9.22 kB
Markdown
SIMPLE-BOOT-HTTP-SERVER
===
### A lightweight and powerful HTTP web server framework for Node.js.
 [](https://www.npmjs.com/package/@dooboostore/simple-boot-http-server) [](LICENSE.md)
- **Declarative Routing**: Use decorators (``, ``) to map URLs to classes and methods.
- **HTTP Method Mapping**: Decorators for all standard HTTP methods (``, ``, ``, ``, etc.).
- **Request/Response Handling**: A unified `RequestResponse` object to manage the HTTP transaction.
- **Middleware/Filters**: Intercept requests and responses for cross-cutting concerns like logging, authentication, or CORS.
- **Endpoints**: Define logic to be executed at different stages of the request lifecycle (e.g., on request start, close, or error).
- **Global Exception Handling**: Centralized error handling using `` advice classes.
- **Session Management**: Built-in session management with customizable storage providers.
- **HTTPS and HTTP/2 Support**: Configure the server to run over HTTPS.
### Dependencies
- **simple-boot**: The core dependency injection and application framework.
## 🚀 Quick Start
```
npm init /simple-boot-http-server projectname
cd projectname
npm start
```
# 😃 Examples
- [More examples](./examples)
## URL & Method Mapping
Simple Boot provides decorators for all standard HTTP methods to map incoming requests to your class methods. These are combined with `` from `/simple-boot` to define endpoints.
### Available Method Decorators
- ``
- ``
- ``
- ``
- ``
- ``
- ``
- ``
- ``
- ``: A generic decorator where you can specify the method as a string.
### Example Usage
```typescript
import { Sim, Router, Route, RouterModule } from '@dooboostore/simple-boot';
import {
GET, POST, PUT, DELETE,
Mimes,
RequestResponse,
ReqJsonBody
} from '@dooboostore/simple-boot-http-server';
export class ItemApi {
getAllItems() {
// Logic to get all items
return [{ id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }];
}
getItemById(routerModule: RouterModule) {
const itemId = routerModule.pathData?.id;
// Logic to get item by ID
return { id: itemId, name: `Item ${itemId}` };
}
createItem(body: ReqJsonBody) {
console.log('Creating new item:', body);
// Logic to create a new item
return { success: true, id: 3, ...body };
}
updateItem(routerModule: RouterModule, body: ReqJsonBody) {
const itemId = routerModule.pathData?.id;
console.log(`Updating item ${itemId}:`, body);
// Logic to update an item
return { success: true, id: itemId, ...body };
}
deleteItem(routerModule: RouterModule) {
const itemId = routerModule.pathData?.id;
console.log(`Deleting item ${itemId}`);
// Logic to delete an item
// No content returned for 204
}
}
```
### Decorator Options
The method decorators accept a `MappingConfig` object to control request and response properties.
```typescript
export type MappingConfig = {
method: HttpMethod | string;
description?: { name?: string; detail?: string; };
req?: {
contentType?: (Mimes | string)[];
accept?: (Mimes | string)[];
};
res?: {
status?: number;
header?: { [key: string]: string };
contentType?: Mimes | string;
}
resolver?: Resolver | ConstructorType<Resolver>;
}
```
## Filters
Filters allow you to intercept the request-response cycle. The `before` method runs before the route handler, and `after` runs after. Returning `false` from `before` will stop the chain.
```typescript
import { Filter, RequestResponse, SimpleBootHttpServer } from '@dooboostore/simple-boot-http-server';
export class LoggingFilter implements Filter {
async before(rr: RequestResponse, app: SimpleBootHttpServer): Promise<boolean> {
console.log(`[${new Date().toISOString()}] Received ${rr.reqMethod()} request for ${rr.reqUrl}`);
return true; // Continue the chain
}
async after(rr: RequestResponse, app: SimpleBootHttpServer, sw: boolean): Promise<boolean> {
console.log(`Request finished with status ${rr.resStatusCode()}`);
return true;
}
}
// In HttpServerOption
const option = new HttpServerOption({
filters: [LoggingFilter]
});
```
## Global Advice & Exception Handling
Define global exception handlers in an `Advice` class. This class is managed by `` and can catch errors thrown from anywhere in the application.
### Preventing `ERR_HTTP_HEADERS_SENT`
A common issue in asynchronous frameworks is the `ERR_HTTP_HEADERS_SENT` error. This occurs when an error is thrown *after* a response has already been sent by another part of the application (like a filter). The global exception handler then tries to send a *second* response, causing a crash.
To prevent this, **always check if a response has already been sent** before sending a new one from your exception handler. The `RequestResponse` object provides the `resIsDone()` method for this purpose.
```typescript
import { Sim, ExceptionHandler, Inject, ExceptionHandlerSituationType } from '@dooboostore/simple-boot';
import { RequestResponse, NotFoundError, HttpError, InternalServerError, Mimes, HttpStatus } from '@dooboostore/simple-boot-http-server';
export class GlobalErrorHandler {
handleNotFound(rr: RequestResponse, e: NotFoundError) {
// Critical check
if (rr.resIsDone()) {
console.error(`Not found error for URL: ${rr.reqUrl}, but response already sent.`, e);
return;
}
rr.resStatusCode(404).resSetHeader('X-Error-Message', 'Resource not found');
console.error(`Not found error for URL: ${rr.reqUrl}`, e);
rr.resEnd(JSON.stringify({ error: 'Resource not found' }));
}
async catch( e: any, rr: RequestResponse) {
console.error(`GlobalAdvice.catch ${rr.reqUrl}`, e);
// Critical check to prevent writing to an already-sent response
if (rr.resIsDone()) {
return;
}
rr.resStatusCode(HttpStatus.InternalServerError);
rr.resSetHeader('Content-Type', Mimes.ApplicationJson);
let data = '';
if (e instanceof HttpError) {
rr.resStatusCode(e.status);
data = JSON.stringify(e);
} else if (e instanceof Error){
const error = new InternalServerError();
error.data = {message: e.message, stack: e.stack}; // Be careful with stack in production
data = JSON.stringify(error);
} else {
const error = new InternalServerError();
error.data = e;
data = JSON.stringify(error);
}
await rr.resEnd(data);
}
}
// In HttpServerOption
const option = new HttpServerOption({
globalAdvice: GlobalErrorHandler,
noSuchRouteEndPointMappingThrow: (rr) => new NotFoundError({ message: `No route for ${rr.reqUrl}` }),
});
```
## Endpoints
Endpoints are hooks that run at specific points in the request lifecycle.
- **`requestEndPoints`**: Run as soon as a request is received.
- **`closeEndPoints`**: Run when the client connection is closed.
- **`errorEndPoints`**: Run if an error occurs on the response stream.
```typescript
import { EndPoint, RequestResponse, SimpleBootHttpServer } from '@dooboostore/simple-boot-http-server';
class ConnectionLogger implements EndPoint {
async endPoint(rr: RequestResponse, app: SimpleBootHttpServer) {
console.log(`Connection closed for ${rr.reqRemoteAddress}`);
}
}
// In HttpServerOption
const option = new HttpServerOption({
closeEndPoints: [ConnectionLogger]
});
```
## Session Management
The server has built-in session management. It automatically creates a session ID cookie and provides a `SessionManager` to store and retrieve session data.
```typescript
import { RequestResponse } from '@dooboostore/simple-boot-http-server';
// In a route handler
async function handleSession(rr: RequestResponse) {
const session = await rr.reqSession();
let visitCount = session.visitCount || 0;
visitCount++;
session.visitCount = visitCount;
return { message: `You have visited ${visitCount} times.` };
}
```