{"id":19562487,"url":"https://github.com/inpassor/ts-node-server","last_synced_at":"2025-04-27T00:31:48.358Z","repository":{"id":42934220,"uuid":"237600456","full_name":"Inpassor/ts-node-server","owner":"Inpassor","description":"Simple node.js HTTP / HTTPS server","archived":false,"fork":false,"pushed_at":"2023-07-18T20:46:52.000Z","size":633,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-10-05T22:17:55.229Z","etag":null,"topics":["http","https","node","server","simple"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Inpassor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-02-01T10:51:30.000Z","updated_at":"2022-11-02T15:28:18.000Z","dependencies_parsed_at":"2022-08-23T07:31:02.597Z","dependency_job_id":null,"html_url":"https://github.com/Inpassor/ts-node-server","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Inpassor%2Fts-node-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Inpassor%2Fts-node-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Inpassor%2Fts-node-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Inpassor%2Fts-node-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Inpassor","download_url":"https://codeload.github.com/Inpassor/ts-node-server/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224050675,"owners_count":17247380,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http","https","node","server","simple"],"created_at":"2024-11-11T05:14:44.449Z","updated_at":"2024-11-11T05:14:46.393Z","avatar_url":"https://github.com/Inpassor.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Simple node.js HTTP / HTTPS server\n\n[![](https://img.shields.io/npm/v/@inpassor/node-server.svg?style=flat)](https://www.npmjs.com/package/@inpassor/node-server)\n[![](https://img.shields.io/github/license/Inpassor/ts-node-server.svg?style=flat-square)](https://github.com/Inpassor/ts-node-server/blob/master/LICENSE)\n![](https://img.shields.io/npm/dt/@inpassor/node-server.svg?style=flat-square)\n\nA simple HTTP / HTTPS server written in pure node.js without Express.\n\nThis library is designed to be minimalistic, quick and powerful at once.\nIt includes two built-in middleware functions: **staticHandler** and **routeHandler**.\nThey act one after another. First, **staticHandler**, then **routeHandler**.\nYou can implement any number of middleware functions. They will be served first.\n\n**staticHandler** serves static files under a public directory.\nIt looks for directories/files by URI. If URI matches an existing directory,\n**staticHandler** looks for an index file within it.\nThe library determines a few MIME types by a file extension.\nYou can define additional MIME types in the **Server** config.\n\nIf URI does not match any public directory/file the Server runs **routeHandler**.\n\n**routeHandler** serves routes and runs user-implemented Components.\nYou can implement any REST method within Component.\nRoutes are defined in the **Server** config.\n\n## Installation\n\n```bash\nnpm install @inpassor/node-server --save\n```\n\n## Usage\n\n### Server [class]\n\n`constructor(config?: ServerConfig)`\n\nCreates a **Server** instance with a given config.\nIf config is omitted default options are used.\n\n**ServerConfig** is an object with any set of these options:\n\n- `protocol: 'http' | 'https'` (default: **'http'**) - a protocol to be used by a server.\n  Depending on it a corresponding server instance will be created:\n  **HttpServer** or **HttpsServer**.\n- `port: number` (default: **80**) - a port to be used by a server.\n- `options: HttpServerOptions | HttpsServerOptions` (default: **{}**) - options to pass\n  to **createServer** function.\n- `publicPath: string | string[]` (default: **'public'**) - a directory to be served by\n  **staticHandler**. Automatically resolves by **path.resolve** function.\n- `index: string` (default: **'index.html'**) - an index file name to be served by\n  **staticHandler**. If URI matches an existing directory\n  under **publicPath** directory, **staticHandler** is looking for this file\n  within this directory and renders it by a corresponding renderer.\n  A renderer is determined by an index file extension.\n- `mimeTypes: { [extension: string]: string }` (default: **{}**) - additional\n  MIME types by extension. For example:\n  ```\n  {\n      mp3: 'audio/mpeg',\n      pdf: 'application/pdf',\n      doc: 'application/msword',\n  }\n  ```\n- `headers: { [name: string]: string }` (default: **{}**) - list of headers for\n  all the server responses. For example:\n  ```\n  {\n      'Access-Control-Allow-Methods': 'OPTIONS, GET, POST',\n      'Access-Control-Allow-Credentials': 'true',\n      'Access-Control-Allow-Headers': 'content-type, authorization',\n  }\n  ```\n- `sameOrigin: boolean` (default: **false**) - when set to true adds headers\n  **Access-Control-Allow-Origin** equal to request **Origin** header\n  and **Vary** equal to 'Origin' to all the server responses.\n  If a request does not contain **Origin** header, headers **Access-Control-Allow-Origin**\n  and **Vary** are not added.\n- `handlers: Handler[]` (default: **[]**) - additional middleware functions.\n\n  **Handler** is a function\n  `(request: Request, response: Response, next: () =\u003e void): void`.\n\n  It accepts three arguments:\n\n  - `request: Request` - **Request** instance.\n  - `response: Response` - **Response** instance.\n  - `next: () =\u003e void` - A function that passes control to a next middleware function\n    if is called inside a handler function.\n\n  You can also call **Server.use** method to add middleware after **Server** instance\n  created.\n\n- `routes: Route[]` (default: **[]**) - routes to be served by **routeHandler**.\n\n  **Route** is an object:\n\n  ```\n  {\n      path: string;\n      component: typeof Component;\n      headers?: { [name: string]: string };\n  }\n  ```\n\n  - `path: string` - A path pattern. You can specify named path parameters\n    by enclosing parameters names in `\u003c...\u003e`.\n\n    For example: the path pattern `'shop/\u003ccategory\u003e/\u003citem\u003e'` matches the route\n    `shop/audio/speakers-101`. In this case there will be two path parameters:\n\n    ```typescript\n    {\n        category: 'audio',\n        item: 'speakers-101',\n    }\n    ```\n\n    By default value of named path parameter can be any set of these symbols:\n    **a-zA-Z0-9-\\_**.\n\n    You can specify path parameter type by adding to its name **'|n'** (for numbers)\n    or **'|l'** (for letters).\n\n    Example:\n\n    `\u003cid|n\u003e` - named parameter \"id\" expected to consist of numbers (**0-9**);\n\n    `\u003ccategory|l\u003e` - named parameter \"category\" expected to consist of latin letters (**a-zA-Z**).\n\n    A last named path parameter can be non-obligatory. In this case, we need\n    to \"hide\" the last slash inside the name and add **|?**: `\u003c/...|?\u003e`.\n    For example: `'shop/\u003ccategory\u003e\u003c/item|?\u003e'`\n\n    You can also specify a type of a non-obligatory last named path parameter: `\u003c/...|l?\u003e`\n    or `\u003c/...|n?\u003e`\n\n    You can use \"magic\" path: **'\\*'**, which matches any route.\n    A **Route** with this path should be defined after all the routes.\n\n  - `component: typeof Component` - A derivative class of **Component**.\n  - `headers: { [name: string]: string }` (default: **{}**) - Additional headers\n    for this route.\n\n- `renderers: { [extension: string]: Renderer }` (default: **{}**) - list of\n  render functions by extension. For example:\n\n  ```\n  {\n      ejs: ejsRender, // don't forget to import { render as ejsRender } from 'ejs';\n  }\n  ```\n\n  **Renderer** is a function\n  `(template: string, params?: Params) =\u003e string`.\n\n  It accepts one or two arguments:\n\n  - `template: string` - A template string.\n  - `params: Params` (default: **undefined**) - An object containing\n    data to be used by a render function.\n\n  Returns string - a result of render function to be sent to a client.\n\n- `bodyParsers: { [mimeType: string]: BodyParser }`\n  (default: **{ 'application/json': JSON.parse }**) - list of parser functions\n  by MIME type.\n\n  **BodyParser** is a function\n  `(body: string) =\u003e string`\n\n  It accepts one argument:\n\n  - `body: string` - Request body.\n\n  Returns parsed body and stores it to **Request.body**.\n\n- `maxBodySize: number` (default: **2097152** - 2Mb) - maximum size of Request body.\n\n#### Server.run [method]\n\n`run: () =\u003e HttpServer | HttpsServer`\n\nRuns an HttpServer | HttpsServer and returns its instance.\n\n#### Server.handle [method]\n\n`handle: (request: Request, response: Response) =\u003e void`\n\nA Server handler. Called by **Server.run** method automatically.\nIt can be used by an external server (for example, Firebase Cloud functions).\n\n#### Server.use [method]\n\n`use: (handler: Handler) =\u003e void`\n\nAdds a middleware to a **Server** instance.\n\n### Component [class]\n\nAll the user-implemented Component classes for **routeHandler** should be derivative of\n**Component** class.\n\nYou can implement any REST method within a Component class. All you need to do is\ncreate a method of a class with a name coinciding with a request method name (in lower case).\nCreate **all** method to serve all the request methods.\n\nFor example, this is DemoComponent implementing GET and POST methods:\n\n```typescript\nclass DemoComponent extends Component {\n  public get(): void {\n    this.response.renderFile([__dirname, 'demo-component.ejs'], {\n      title: 'Demo Component',\n    });\n  }\n\n  public post(): void {\n    this.response.send(200, 'This is the DemoComponent POST action');\n  }\n}\n```\n\n#### Component.app [property]\n\n`app: Server`\n\n#### Component.request [property]\n\n`request: Request`\n\n#### Component.response [property]\n\n`response: Response`\n\n### Request [class]\n\nA derivative class of **IncomingMessage**. Has a few additional properties:\n\n#### Request.app [property]\n\n`app: Server`\n\n#### Request.uri [property]\n\n`uri: string`\n\nCurrent route URI.\n\n#### Request.params [property]\n\n`params: { [name: string]: string }`\n\nA named route parameters list.\n\n#### Request.searchParams [property]\n\n`searchParams: URLSearchParams`\n\n#### Request.body [property]\n\n`body: string`\n\nA body of the request. Is parsed by **BodyParser** function if **bodyParsers** config\noption contain key equal to **Content-Type** request header.\n\n### Response [class]\n\nA derivative class of **ServerResponse**. Has a few additional properties and methods:\n\n#### Response.app [property]\n\n`app: Server`\n\n#### Response.request [property]\n\n`request: Request`\n\n#### Response.send [method]\n\n`send: (status: number, body?) =\u003e void`\n\nSends a response to a client.\n\nAccepts one or two arguments:\n\n- `status: number` - HTTP status code.\n- `body: string` (default: **undefined**) - A body of response.\n\n#### Response.sendJSON [method]\n\n`sendJSON: (data: string) =\u003e void`\n\nSends a response to a client in JSON format.\n\nAccepts one argument:\n\n- `data: string` - A data to be sent in JSON format in a body of response.\n\n#### Response.sendError [method]\n\n`sendError: (error) =\u003e void`\n\nSends an error response to a client.\n\nAccepts one argument:\n\n- `error: unknown` - Error object. The library tries to get HTTP status code\n  and error message automatically. Basically, error object should be as follows:\n  ```\n  {\n      code: number;\n      message: string;\n  }\n  ```\n\n#### Response.render [method]\n\n`render: (template: string | Buffer, extension: string, params?: Params) =\u003e void`\n\nRenders a **template** by a renderer, determined by **extension**, and responds to a client\nwith a body, containing a result of a render function.\n\nAccepts two or three arguments:\n\n- `template: string | Buffer` - A template **string** or **Buffer**.\n  If a template is of **Buffer** type, it's converted to **string**.\n- `extension: string` - A renderer will be determined by this **extension**.\n  For example, **'ejs'** will be rendered by EJS renderer.\n- `params: Params` (default: **undefined**) - An object containing\n  data to be used by a render function.\n\n#### Response.renderFile [method]\n\n`renderFile: (pathSegments: string | string[], params?: Params) =\u003e void`\n\nRenders a file by a renderer, determined by a file extension, and responds to a client\nwith a body, containing a result of a render function.\n\nAccepts one or two arguments:\n\n- `pathSegments: string | string[]` - A file name to be rendered.\n  Automatically resolves by **path.resolve** function.\n- `params: Params` (default: **undefined**) - An object containing\n  data to be used by a render function.\n\n### Helpers\n\nThe library has a few helper functions:\n\n#### formatBytes [function]\n\n`formatBytes: (bytes: number, decimals = 2) =\u003e string`\n\n#### getCodeFromError [function]\n\n`getCodeFromError: (error) =\u003e number`\n\n#### getMessageFromError [function]\n\n`getMessageFromError: (error) =\u003e string`\n\n#### resolvePath [function]\n\n`resolvePath: (...pathSegments) =\u003e string`\n\n#### httpStatusList [object]\n\n`httpStatusList: { [code: number]: string }`\n\n#### mimeTypes [object]\n\n`mimeTypes: { [extension: string]: string }`\n\n#### isHttpServerOptions [type guard]\n\n`isHttpServerOptions: (arg) =\u003e arg is ServerOptions`\n\n#### isHttpsServerOptions [type guard]\n\n`isHttpsServerOptions: (arg) =\u003e arg is ServerOptions`\n\n#### isServerConfig [type guard]\n\n`isServerConfig: (arg) =\u003e arg is ServerConfig`\n\n#### Logger [class]\n\n## Examples\n\n### Stand-alone\n\n```typescript\nimport { Server, Component, ServerConfig } from '@inpassor/node-server';\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { render as ejsRender } from 'ejs';\n\nclass ErrorComponent extends Component {\n  public all(): void {\n    this.response.sendError({\n      code: 405,\n    });\n  }\n}\n\nclass DemoComponent extends Component {\n  public get(): void {\n    console.log(this.request.params);\n    this.response.renderFile([__dirname, 'demo-component.ejs'], {\n      title: 'Demo Component',\n    });\n  }\n\n  public post(): void {\n    console.log(this.request.params);\n    this.response.send(200, 'This is the DemoComponent POST action');\n  }\n}\n\nconst config: ServerConfig = {\n  protocol: 'https', // 'http|https', default: 'http'\n  port: 8080, // default: 80\n  options: {\n    // ServerOptions for HTTP or HTTPS node.js function createServer, default: {}\n    key: readFileSync(resolve(__dirname, 'certificate.key.pem')),\n    cert: readFileSync(resolve(__dirname, 'certificate.crt.pem')),\n    ca: readFileSync(resolve(__dirname, 'certificate.fullchain.pem')),\n  },\n  publicPath: 'public', // path to public files, default: 'public'\n  index: 'index.html', // index file name, default: 'index.html'\n  mimeTypes: {\n    // additional MIME types\n    mp3: 'audio/mpeg',\n    pdf: 'application/pdf',\n    doc: 'application/msword',\n  },\n  headers: {\n    // list of headers for all the server responses, default: {}\n    'Access-Control-Allow-Methods': 'OPTIONS, GET, POST',\n    'Access-Control-Allow-Credentials': 'true',\n    'Access-Control-Allow-Headers': 'content-type, authorization',\n  },\n  sameOrigin: true, // when set to true adds headers 'Access-Control-Allow-Origin' equal to\n  // request Origin header and 'Vary' equal to 'Origin' to all the server responses\n  handlers: [], // additional middleware functions\n  // (you can also call Server.use method to add middleware after Server instance created)\n  routes: [\n    // routes to be served by routeHandler\n    {\n      path: 'demo\u003c/arg|?\u003e',\n      component: DemoComponent,\n    },\n    {\n      path: '*',\n      component: ErrorComponent,\n    },\n  ],\n  renderers: {\n    // list of render functions\n    ejs: ejsRender,\n  },\n};\n\nconst server = new Server(config);\n\n// Add middleware\nserver.use((request, response, next) =\u003e {\n  // TODO: some middleware work\n\n  // call next function to pass work to next middleware\n  // next();\n\n  // or send a response to a client, otherwise, the server will hang till timeout\n  // use Response.send method in order to send all the needed headers defined in the config\n  response.send(200, 'Some content');\n});\n\nserver.run();\n```\n\nWe had created a Server instance with ejs renderer and two components:\nDemoComponent, having GET and POST methods,\nand ErrorComponent, serving all the routes (which did not match any previous route)\nand all the request methods.\n\nThe route **/demo[/arg]** will be served by DemoComponent.\n\nAll the other routes will be served first under **publicPath** directory, then\nErrorComponent will act.\n\n### socket.io\n\n```typescript\nimport { Server, ServerConfig } from '@inpassor/node-server';\nimport * as socketIO from 'socket.io';\n\nconst config: ServerConfig = {}; // define your own ServerConfig here\n\nconst server = new Server(config);\n\nconst serverInstance = server.run(); // instance of HTTP or HTTPS node.js Server\n\nconst io = socketIO(serverInstance, {\n  handlePreflightRequest: (request, response) =\u003e {\n    response.writeHead(204, {\n      'Access-Control-Allow-Methods': 'OPTIONS, GET',\n      'Access-Control-Allow-Credentials': 'true',\n      'Access-Control-Allow-Headers': 'content-type, authorization',\n      'Access-Control-Allow-Origin': request.headers.origin,\n      Vary: 'Origin',\n    });\n    response.end();\n  },\n});\n```\n\n### Firebase Cloud functions\n\nThere is no need for HTTP or HTTPS node.js server instance since Firebase Cloud functions\ncreate its own server. We just need to pass **Server.handle** method to Firebase.\n\n#### Common usage\n\n```typescript\nimport { RuntimeOptions, HttpsFunction, runWith } from 'firebase-functions';\nimport { Server, ServerConfig } from '@inpassor/node-server';\n\nconst firebaseApplication = (\n  config: ServerConfig,\n  runtimeOptions?: RuntimeOptions\n): HttpsFunction =\u003e {\n  const server = new Server(config);\n  return runWith(runtimeOptions).https.onRequest(server.handle.bind(server));\n};\n\nconst config: ServerConfig = {}; // define your own ServerConfig here\n\nexport const firebaseFunction = firebaseApplication(config, {\n  timeoutSeconds: 10,\n  memory: '128MB',\n});\n```\n\n#### Asynchronous Server config\n\n```typescript\nimport { RuntimeOptions, HttpsFunction, runWith } from 'firebase-functions';\nimport { Server, ServerConfig } from '@inpassor/node-server';\n\nconst firebaseApplication = (\n  getConfig: ServerConfig | Promise\u003cServerConfig\u003e,\n  runtimeOptions?: RuntimeOptions\n): HttpsFunction =\u003e {\n  return runWith(runtimeOptions).https.onRequest(async (request, response) =\u003e {\n    await new Promise((resolve, reject) =\u003e {\n      Promise.resolve(getConfig).then(\n        (config): void =\u003e {\n          const server = new Server(config);\n          resolve(server.handle.call(server, request, response));\n        },\n        (error) =\u003e reject(error)\n      );\n    });\n  });\n};\n\n// Some asynchronous get config function\nconst getConfig = (): Promise\u003cServerConfig\u003e =\u003e {\n  const config: ServerConfig = {}; // define your own ServerConfig here\n  return Promise.resolve(config);\n};\n\nexport const firebaseFunction = firebaseApplication(getConfig(), {\n  timeoutSeconds: 10,\n  memory: '128MB',\n});\n```\n\nYou can also use the library\n[@inpassor/firebase-application](https://github.com/Inpassor/ts-firebase-application)\nwhich wraps node-server.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finpassor%2Fts-node-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finpassor%2Fts-node-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finpassor%2Fts-node-server/lists"}