diff --git a/packages/core/micro-server/README.md b/packages/core/micro-server/README.md index 9aa2bdd36..4c7dffa78 100644 --- a/packages/core/micro-server/README.md +++ b/packages/core/micro-server/README.md @@ -1,3 +1,54 @@ # @alwatr/micro-server Elegant powerful nodejs server for microservice use cases, written in tiny TypeScript module. + +## Usage + +### Create server + +```typescript +import {AlwatrMicroServer} from '@alwatr/micro-server'; + +import type {AlwatrConnection} from '@alwatr/micro-server'; + +const app = new AlwatrMicroServer(8000); + +app.route('all', '/', async (connection: AlwatrConnection) => { + connection.reply({ + ok: true, + statusCode: 200, + data: { + app: 'Alwatr Microservice', + message: 'Hello ;)', + }, + }); +}); +``` + +### Middleware + +#### CORS Helper + +Read about [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). + +```typescript +app.route('all', '/', async (connection) => { + connection.reply( + { + ok: true, + statusCode: 200, + data: { + app: 'Alwatr Microservice', + message: 'Hello World', + }, + }, + { + corsHelper: { + allowOrigin: 'https://developer.mozilla.org', // That should not end with "/" + allowMethod: '*', + maxAge: 5 * 60, // 5 Minute + }, + } + ); +}); +``` diff --git a/packages/core/micro-server/src/micro-server.ts b/packages/core/micro-server/src/micro-server.ts index 46f8f5c07..0da458cc8 100644 --- a/packages/core/micro-server/src/micro-server.ts +++ b/packages/core/micro-server/src/micro-server.ts @@ -2,7 +2,9 @@ import {createServer} from 'http'; import {alwatrRegisteredList, createLogger} from '@alwatr/logger'; -import type {Methods, ReplyContent} from './type.js'; +import {corsHelper} from './middleware/cors-helpers.js'; + +import type {Methods, ReplyContent, ResponseOptions} from './type.js'; import type {IncomingMessage, ServerResponse} from 'http'; alwatrRegisteredList.push({ @@ -14,7 +16,7 @@ export class AlwatrMicroServer { protected logger = createLogger(`micro-server:${this.port}`); protected server = createServer(this.handleRequest); - constructor(protected port: number, autoListen = true) { + constructor(protected port: number, autoListen = true, public options?: ResponseOptions) { this.logger.logMethodArgs('new', {port, listen: autoListen}); this.server = createServer(this.handleRequest.bind(this)); @@ -90,7 +92,7 @@ export class AlwatrMicroServer { return; } - const connection = new AlwatrConnection(incomingMessage, serverResponse); + const connection = new AlwatrConnection(incomingMessage, serverResponse, this.options); const route = connection.url.pathname; const method = connection.incomingMessage.method?.toLowerCase(); @@ -143,7 +145,11 @@ export class AlwatrConnection { readonly body = this._getRequestBody(); - constructor(public incomingMessage: IncomingMessage, public serverResponse: ServerResponse) { + constructor( + public incomingMessage: IncomingMessage, + public serverResponse: ServerResponse, + public options?: ResponseOptions, + ) { this.logger.logMethodArgs('new', {method: incomingMessage.method, url: incomingMessage.url}); } @@ -210,6 +216,10 @@ export class AlwatrConnection { ); } + if (this.options?.corsHelper !== undefined) { + corsHelper(this.serverResponse, this.options?.corsHelper); + } + this.serverResponse.writeHead(content.statusCode ?? 200, { 'Content-Length': body.length, 'Content-Type': 'application/json', diff --git a/packages/core/micro-server/src/middleware/cors-helpers.ts b/packages/core/micro-server/src/middleware/cors-helpers.ts new file mode 100644 index 000000000..16ec93049 --- /dev/null +++ b/packages/core/micro-server/src/middleware/cors-helpers.ts @@ -0,0 +1,33 @@ +import {CorsHelperHeader} from '../type'; + +import type {ServerResponse} from 'http'; + +/** + * Set CORS helper header. + * https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS + */ +export function corsHelper(response: ServerResponse, header: CorsHelperHeader): void { + if (header.allowOrigin !== undefined) { + response.setHeader('Access-Control-Allow-Origin', header.allowOrigin); + } + + if (header.allowMethods !== undefined) { + response.setHeader('Access-Control-Allow-Methods', header.allowMethods); + } + + if (header.allowHeaders !== undefined) { + response.setHeader('Access-Control-Allow-Headers', header.allowHeaders); + } + + if (header.exposeHeaders !== undefined) { + response.setHeader('Access-Control-Expose-Headers', header.exposeHeaders); + } + + if (header.allowCredentials !== undefined) { + response.setHeader('Access-Control-Allow-Credentials', String(header.allowCredentials)); + } + + if (header.maxAge !== undefined) { + response.setHeader('Access-Control-Max-Age', header.maxAge); + } +} diff --git a/packages/core/micro-server/src/type.ts b/packages/core/micro-server/src/type.ts index de1a69617..9c9b861de 100644 --- a/packages/core/micro-server/src/type.ts +++ b/packages/core/micro-server/src/type.ts @@ -11,6 +11,19 @@ interface ReplySuccessContent { data: Record; } +export interface ResponseOptions { + corsHelper?: CorsHelperHeader; +} + +export interface CorsHelperHeader { + allowOrigin: string; + allowMethods?: string; + allowHeaders?: string; + exposeHeaders?: string; + allowCredentials?: boolean; + maxAge?: number; +} + export type ReplyContent = ReplyFailedContent | ReplySuccessContent; export type Methods = ('all' | 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options') & string; diff --git a/packages/starter/microservice/src/app.ts b/packages/starter/microservice/src/app.ts index 97dd88780..595ec4d7b 100644 --- a/packages/starter/microservice/src/app.ts +++ b/packages/starter/microservice/src/app.ts @@ -2,4 +2,10 @@ import {AlwatrMicroServer} from '@alwatr/micro-server'; import {config} from './config.js'; -export const app = new AlwatrMicroServer(config.port); +export const app = new AlwatrMicroServer(config.port, undefined, { + corsHelper: { + allowOrigin: '*', + allowMethods: '*', + maxAge: 5 * 60, // 5 min + }, +}); diff --git a/packages/starter/microservice/src/route/echo.ts b/packages/starter/microservice/src/route/echo.ts index 06560de10..b1f54c31d 100644 --- a/packages/starter/microservice/src/route/echo.ts +++ b/packages/starter/microservice/src/route/echo.ts @@ -1,6 +1,8 @@ import {app} from '../app.js'; -app.route('post', '/echo', async (connection) => { +import type {AlwatrConnection} from '@alwatr/micro-server'; + +app.route('post', '/echo', async (connection: AlwatrConnection) => { const jsonBody = await connection.requireJsonBody(); if (jsonBody == null) return; diff --git a/packages/starter/microservice/src/route/home.ts b/packages/starter/microservice/src/route/home.ts index c85ba9c32..05f79412b 100644 --- a/packages/starter/microservice/src/route/home.ts +++ b/packages/starter/microservice/src/route/home.ts @@ -1,6 +1,8 @@ import {app} from '../app.js'; -app.route('all', '/', async (connection) => { +import type {AlwatrConnection} from '@alwatr/micro-server'; + +app.route('all', '/', async (connection: AlwatrConnection) => { connection.reply({ ok: true, data: {