{"id":17321803,"url":"https://github.com/avoidwork/woodland","last_synced_at":"2025-04-14T16:06:47.265Z","repository":{"id":39897840,"uuid":"62262430","full_name":"avoidwork/woodland","owner":"avoidwork","description":"Lightweight HTTP framework with automatic headers","archived":false,"fork":false,"pushed_at":"2024-04-11T01:30:41.000Z","size":1527,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-04-14T00:38:31.664Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/avoidwork.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null},"funding":{"github":["avoidwork"]}},"created_at":"2016-06-29T22:38:03.000Z","updated_at":"2024-04-15T02:34:33.552Z","dependencies_parsed_at":"2024-02-02T02:27:26.275Z","dependency_job_id":"9b764ace-e49f-4214-8278-8d3717750ce8","html_url":"https://github.com/avoidwork/woodland","commit_stats":{"total_commits":593,"total_committers":3,"mean_commits":"197.66666666666666","dds":"0.033726812816188834","last_synced_commit":"7441f6e3575297617496849ffb44c35a5d2e9117"},"previous_names":[],"tags_count":336,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avoidwork%2Fwoodland","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avoidwork%2Fwoodland/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avoidwork%2Fwoodland/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avoidwork%2Fwoodland/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/avoidwork","download_url":"https://codeload.github.com/avoidwork/woodland/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":220246710,"owners_count":16617734,"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":[],"created_at":"2024-10-15T13:39:41.265Z","updated_at":"2024-10-18T04:53:27.550Z","avatar_url":"https://github.com/avoidwork.png","language":"JavaScript","funding_links":["https://github.com/sponsors/avoidwork"],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"https://avoidwork.github.io/woodland/logo.svg\" width=\"108\" /\u003e\n\n# Woodland\n\nLightweight HTTP framework with automatic headers. Routes can use parameter syntax, i.e. `/users/:id`, or `RegExp` syntax. Route parameters are not sanitized. If 2+ routes with parameters match a request the first route will be used to extract parameters. All HTTP methods are supported.\n\n`CORS` (Cross Origin Resource Sharing) is automatically handled, and indicated with `cors` Boolean on the `request` Object for middleware.\n\nMiddleware arguments can be `req, res, next` or `error, req, res, next`. If no `Error` handling middleware is registered woodland will handle it.\n\n## Using the factory\n\n```javascript\nimport {createServer} from \"node:http\";\nimport {woodland} from \"woodland\";\n\nconst app = woodland({\n  defaultHeaders: {\n    \"cache-control\": \"public, max-age=3600\",\n    \"content-type\": \"text/plain\"\n  },\n  time: true\n});\n\napp.get(\"/\", (req, res) =\u003e res.send(\"Custom greeting at '/:user', try it out!\"));\napp.get(\"/:user\", (req, res) =\u003e res.send(`Hello ${req.params.user}!`));\ncreateServer(app.route).listen(8000);\n```\n\n## Using the Class\n\n```javascript\nimport {Woodland} from \"woodland\";\nclass MyFramework extends Woodland {};\n```\n\n## Testing\n\nWoodland has \u003e99% code coverage with its tests.\n\n```console\n--------------|---------|----------|---------|---------|-------------------\nFile          | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s\n--------------|---------|----------|---------|---------|-------------------\nAll files     |   99.79 |     74.1 |   98.59 |     100 |                   \n woodland.cjs |   99.79 |     74.1 |   98.59 |     100 | ...\n--------------|---------|----------|---------|---------|-------------------\n```\n\n## Benchmark\nPlease benchmark `woodland` on your target hardware to understand the overhead which is expected to be \u003c15% with etags disabled, or \u003c25% with etags enabled. E.g. if `http` can handle 50k req/s, then `woodland` should handle 43k req/s.\n\n1. Clone repository from [GitHub](https://github.com/avoidwork/woodland).\n1. Install dependencies with `npm` or `yarn`.\n1. Execute `benchmark` script with `npm` or `yarn`.\n\nResults with node.js 20.8.0 \u0026 an Intel i9-12900HX (mobile) on Windows 11, with etags disabled.\n\n```console\n\u003e node benchmark.js\n\nhttp\n┌─────────┬──────┬──────┬───────┬───────┬──────────┬──────────┬───────┐\n│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%   │ Avg      │ Stdev    │ Max   │\n├─────────┼──────┼──────┼───────┼───────┼──────────┼──────────┼───────┤\n│ Latency │ 1 ms │ 8 ms │ 42 ms │ 47 ms │ 10.81 ms │ 10.26 ms │ 88 ms │\n└─────────┴──────┴──────┴───────┴───────┴──────────┴──────────┴───────┘\n┌───────────┬─────────┬─────────┬───────┬───────┬─────────┬─────────┬─────────┐\n│ Stat      │ 1%      │ 2.5%    │ 50%   │ 97.5% │ Avg     │ Stdev   │ Min     │\n├───────────┼─────────┼─────────┼───────┼───────┼─────────┼─────────┼─────────┤\n│ Req/Sec   │ 75967   │ 75967   │ 88703 │ 93823 │ 88409.6 │ 4152.76 │ 75952   │\n├───────────┼─────────┼─────────┼───────┼───────┼─────────┼─────────┼─────────┤\n│ Bytes/Sec │ 15.4 MB │ 15.4 MB │ 18 MB │ 19 MB │ 17.9 MB │ 841 kB  │ 15.4 MB │\n└───────────┴─────────┴─────────┴───────┴───────┴─────────┴─────────┴─────────┘\n\nwoodland\n┌─────────┬──────┬──────┬───────┬───────┬──────────┬──────────┬────────┐\n│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%   │ Avg      │ Stdev    │ Max    │\n├─────────┼──────┼──────┼───────┼───────┼──────────┼──────────┼────────┤\n│ Latency │ 2 ms │ 9 ms │ 57 ms │ 67 ms │ 12.82 ms │ 13.04 ms │ 119 ms │\n└─────────┴──────┴──────┴───────┴───────┴──────────┴──────────┴────────┘\n┌───────────┬─────────┬─────────┬─────────┬─────────┬──────────┬─────────┬─────────┐\n│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg      │ Stdev   │ Min     │\n├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤\n│ Req/Sec   │ 66687   │ 66687   │ 75263   │ 76095   │ 75041.61 │ 1482.92 │ 66667   │\n├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤\n│ Bytes/Sec │ 14.1 MB │ 14.1 MB │ 15.9 MB │ 16.1 MB │ 15.8 MB  │ 312 kB  │ 14.1 MB │\n└───────────┴─────────┴─────────┴─────────┴─────────┴──────────┴─────────┴─────────┘\n\n```\n\n## API\n### constructor ({...})\nReturns a woodland instance. Enable directory browsing \u0026 traversal with `autoindex`. Create an automatic `x-response-time` response header with `time` \u0026 `digit`. Customize `etag` response header with `seed`.\n\n#### Configuration\n\n```json\n{\n  \"autoindex\": false,\n  \"cacheSize\": 1000,\n  \"cacheTTL\": 300000,\n  \"charset\": \"utf-8\",\n  \"corsExpose\": \"\",\n  \"defaultHeaders\": {},\n  \"digit\": 3,\n  \"etags\": true,\n  \"indexes\": [\n    \"index.htm\",\n    \"index.html\"\n  ],\n  \"logging\": {\n    \"enabled\": true,\n    \"format\": \"%h %l %u %t \\\"%r\\\" %\u003es %b\",\n    \"level\": \"info\"\n  },\n  \"origins\": [\n    \"*\"\n  ],\n  \"silent\": false,\n  \"time\": false\n}\n```\n\n### allowed (method, uri, override = false)\nCalls `routes()` and returns a `Boolean` to indicate if `method` is allowed for `uri`.\n\n### allows (uri, override = false)\nReturns a `String` for the `Allow` header. Caches value, \u0026 will update cache if `override` is `true`.\n\n### always (path, fn)\nRegisters middleware for a route for all HTTP methods; runs first. `path` is a regular expression (as a string), and if not passed it defaults to `/.*`.\n\nExecute `ignore(fn)` if you do not want the middleware included for calculating the `Allow` header.\n\n### ignore (fn)\nIgnores `fn` for calculating the return of `allows()`.\n\n### decorate (req, res)\nDecorates `allow, body, cors, host, ip, params, \u0026 parsed` on `req` and `error(status[, body, headers]), header(key, value), json(body[, status, headers]), locals{} \u0026 redirect(url[, perm = false])` on `res`.\n\n### delete ([path = \"/.*\",] ...fn)\nRegisters middleware for a route. `path` is a regular expression (as a string), and if not passed it defaults to `/.*`.\n\n### etag (...args)\nReturns a String to be used as an etag response header value.\n\n### files (root = \"/\", folder = process.cwd())\nServe static files on disk.\n\n### get ([path = \"/.*\",] ...fn)\nRegisters middleware for a route. `path` is a regular expression (as a string), and if not passed it defaults to `/.*`. See `always()` if you want the middleware to be used for all HTTP methods.\n\n### list (method = \"get\", type = \"array\")\nReturns an `Array` or `Object` of routes for the specified method.\n\n### log (msg = \"\", level = \"debug\")\nLogs to `stdout` or `stderr` depending on the `level`, \u0026 what the minimum log level is set to.\n\n### patch ([path = \"/.*\",] ...fn)\nRegisters middleware for a route. `path` is a regular expression (as a string), and if not passed it defaults to `/.*`.\n\n### post ([path = \"/.*\",] ...fn)\nRegisters middleware for a route. `path` is a regular expression (as a string), and if not passed it defaults to `/.*`.\n\n### put ([path = \"/.*\",] ...fn)\nRegisters middleware for a route. `path` is a regular expression (as a string), and if not passed it defaults to `/.*`.\n\n### onDone (req, res, body, headers)\n**Override** to customize final handler. Must terminate response.\n\n### onReady (req, res, body, status, headers)\n**Override** to customize response `body`, `status`, or `headers`. Must call `onSend()`.\n\n### onSend (req, res, body, status, headers)\n**Override** to customize response `body`, `status`, or `headers`. Must return `[body, status, headers]`!\n\n### options ([path = \"/.*\",] ...fn)\nRegisters middleware for a route. `path` is a regular expression (as a string), and if not passed it defaults to `/.*`.\n\n### route (req, res)\nFunction for `http.createServer()` or `https.createServer()`.\n\n### routes (uri, method, override = false)\nReturns an `Array` of middleware for the request. Caches value, \u0026 will update cache if `override` is `true`.\n\n### stream (req, res, file = {charset: \"\",  etag: \"\", path: \"\", stats: {mtime: datetime, size: int}})\nStreams a file to the client.\n\n### use ([path = \"/.*\",] ...fn[, method = \"GET\"])\nRegisters middleware for a route. `path` is a regular expression (as a string), and if not passed it defaults to `/.*`. See `always()` if you want the middleware to be used for all HTTP methods.\n\nAll HTTP methods are available on the prototype (partial application of the third argument), e.g. `get([path,] ...fn)` \u0026 `options([path,] ...fn)`.\n\n## Command Line Interface (CLI)\nWhen woodland is installed as a global module you can serve the contents of a folder by executing `woodland` in a shell. Optional parameters are `--ip=127.0.0.1` \u0026 `--port=8000`.\n\n```console\nNode.js v20.8.0\nPS C:\\Users\\jason\\Projects\u003e npm install -g woodland\n\nchanged 6 packages in 1s\nPS C:\\Users\\jason\\Projects\u003e woodland\nid=woodland, hostname=localhost, ip=127.0.0.1, port=8000\n127.0.0.1 -  [7/Oct/2023:15:18:18 -0400] \"GET / HTTP/1.1\" 200 1327\n127.0.0.1 -  [7/Oct/2023:15:18:26 -0400] \"GET /woodland/ HTTP/1.1\" 200 2167\n127.0.0.1 -  [7/Oct/2023:15:18:29 -0400] \"GET /woodland/dist/ HTTP/1.1\" 200 913\n127.0.0.1 -  [7/Oct/2023:15:18:32 -0400] \"GET /woodland/dist/woodland.js HTTP/1.1\" 200 26385\n127.0.0.1 -  [7/Oct/2023:15:18:47 -0400] \"GET /woodland/benchmark.js HTTP/1.1\" 200 1657\n127.0.0.1 -  [7/Oct/2023:15:18:58 -0400] \"GET /woodland/sample.js HTTP/1.1\" 200 845\n127.0.0.1 -  [7/Oct/2023:15:19:07 -0400] \"GET /woodland/sample.js HTTP/1.1\" 304 0\n```\n\n## Event Handlers\nEvent Emitter syntax for the following events:\n\n### connect (req, res)\nExecutes after the connection has been decorated, but before the middleware executes.\n\n```javascript\napp.on(\"connect\", (req, res) =\u003e res.header(\"x-custom-header\", \"abc-def\"));\n```\n\n### error (req, res, err)\nExecutes after the response has been sent.\n\n```javascript\napp.on(\"error\", (req, res, err) =\u003e { /* log err */ });\n```\n\n### finish (req, res)\nExecutes after the response has been sent.\n\n```javascript\napp.on(\"finish\", (req, res) =\u003e { /* telemetry */ });\n```\n\n### stream (req, res)\nExecutes after the response has been streamed.\n\n```javascript\napp.on(\"stream\", (req, res, err) =\u003e { /* telemetry */ });\n```\n\n## Helpers\n`req` \u0026 `res` are decorated with helper functions to simplify responding.\n\n### req.exit()\nExit the middleware chain if the route is un-protected.\n\n### res.error(status[, body, headers])\nSends an error response.\n\n### res.header(key, value)\nShorthand of `res.setHeader()`.\n\n### res.json(body, [status = 200, headers])\nSends a JSON response.\n\n### res.redirect(uri[, perm = false])\nSends a redirection response.\n\n### res.send(body, [status = 200, headers = {}])\nSends a response. `Range` header is ignored on `stream` responses.\n\n### res.set(headers = {})\nShorthand of `res.setHeaders()` which accepts `Object`, `Map`, or `Headers` instances.\n\n### res.status(arg)\nSets the response `statusCode` property.\n\n## Logging\nWoodland defaults to [Common Log Format](https://en.wikipedia.org/wiki/Common_Log_Format) but supports [Common Log Format with Virtual Host](https://httpd.apache.org/docs/trunk/mod/mod_log_config.html), \u0026 [NCSA extended/combined log format](https://httpd.apache.org/docs/trunk/mod/mod_log_config.html) with an `info` level by default. You can change the `stdout` output by changing `logging.format` with valid placeholders.\n\nYou can disable woodland's logging by configuration with `{logging: {enabled: false}}`.\n\n## License\nCopyright (c) 2024 Jason Mulligan\n\nLicensed under the BSD-3 license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favoidwork%2Fwoodland","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Favoidwork%2Fwoodland","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favoidwork%2Fwoodland/lists"}