{"id":20165726,"url":"https://github.com/functionalland/functional-http-server","last_synced_at":"2025-07-11T00:09:06.433Z","repository":{"id":62421837,"uuid":"312570831","full_name":"functionalland/functional-http-server","owner":"functionalland","description":"A simple HTTP server inspired by Express and in tune with Functional Programming principles in JavaScript for Deno.","archived":false,"fork":false,"pushed_at":"2020-12-28T16:53:46.000Z","size":32,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-11T04:08:44.406Z","etag":null,"topics":["category-theory","deno","denoland","express","functional","functional-programming","functor","http-server","koa","monoid","oak","task"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/functionalland.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-11-13T12:33:59.000Z","updated_at":"2025-02-05T05:57:04.000Z","dependencies_parsed_at":"2022-11-01T17:31:59.828Z","dependency_job_id":null,"html_url":"https://github.com/functionalland/functional-http-server","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/functionalland/functional-http-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionalland%2Ffunctional-http-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionalland%2Ffunctional-http-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionalland%2Ffunctional-http-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionalland%2Ffunctional-http-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/functionalland","download_url":"https://codeload.github.com/functionalland/functional-http-server/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionalland%2Ffunctional-http-server/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264696460,"owners_count":23650936,"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":["category-theory","deno","denoland","express","functional","functional-programming","functor","http-server","koa","monoid","oak","task"],"created_at":"2024-11-14T00:38:54.359Z","updated_at":"2025-07-11T00:09:06.415Z","avatar_url":"https://github.com/functionalland.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"./.github/fl-logo.svg\" alt=\"Functional HTTP Server\" width=\"450\" /\u003e\n\nA simple HTTP server inspired by Express and in tune with Functional Programming principles in JavaScript for Deno.\n\n[![deno land](http://img.shields.io/badge/available%20on-deno.land/x-lightgrey.svg?logo=deno\u0026labelColor=black)](https://deno.land/x/functional_http_server@v0.3.1)\n[![deno version](https://img.shields.io/badge/deno-^1.6.1-lightgrey?logo=deno)](https://github.com/denoland/deno)\n[![GitHub release](https://img.shields.io/github/v/release/sebastienfilion/functional-http-server)](https://github.com/sebastienfilion/functional-http-server/releases)\n[![GitHub licence](https://img.shields.io/github/license/sebastienfilion/functional-http-server)](https://github.com/sebastienfilion/functional-http-server/blob/v0.3.1/LICENSE)\n[![Discord Chat](https://img.shields.io/discord/790708610023555093.svg)](https://discord.gg/)\n\n  * [Simple HTTP server](#simple-http-server)\n  * [Routing](#routing)\n  * [Server](#server)\n  * [Utilities](#utilities)\n\n## Usage\n\nFunctional HTTP Server is optimized to write elegant and powerful point-free functions. This example uses the Ramda \nlibrary - for simplification - but you should be able to use any library that implements the Fantasy-land \nspecifications.\n\nThis example showcase how to create an endpoint handler for `POST /hoge` that writes to a local file and to Redis \nsimultaneously the content of the request's body and, replies with `201`.\n\n```js\nimport Task from \"https://deno.land/x/functional@v1.3.2/library/Task.js\";\nimport {\n  decodeRaw,\n  encodeText,\n  evert,\n  safeExtract\n} from \"https://deno.land/x/functional@v1.3.2/library/utilities.js\";\nimport File from \"https://deno.land/x/functional_io@v1.1.0/library/File.js\";\nimport Request from \"https://deno.land/x/functional_io@v1.1.0/library/Request.js\";\nimport Response from \"https://deno.land/x/functional_io@v1.1.0/library/Response.js\";\nimport { fetch } from \"https://deno.land/x/functional_io@v1.1.0/library/browser_safe.js\";\nimport { writeFile } from \"https://deno.land/x/functional_io@v1.1.0/library/fs.js\";\nimport RedisRequest from \"https://deno.land/x/functional_redis@v0.2.0/library/RedisRequest.js\";\nimport { $$rawPlaceholder } from \"https://deno.land/x/functional_redis@v0.2.0/library/Symbol.js\";\nimport { executeRedisCommandWithSession } from \"https://deno.land/x/functional_redis@v0.2.0/library/client.js\";\n\nimport { handlers, route } from \"https://deno.land/x/functional_http_server@v0.3.1/library/route.js\";\nimport startHTTPServer from \"https://deno.land/x/functional_http_server@v0.3.1/library/server.js\";\n\nstartHTTPServer(\n  { port: 8080 },\n  route(\n    handlers.post(\n      \"/hoge\",\n      compose(\n        map(_ =\u003e Response.Created({ 'content-type': \"text/plain\" }, encodeText(\"Created!\"))),\n        converge(\n          (...tasks) =\u003e evert(Task, tasks),\n          [\n            compose(\n              executeRedisCommandWithSession({ port: 6379 }),\n              concat(RedisRequest(\"SET\", new Uint8Array([]), [ \"hoge\", $$rawPlaceholder ]))\n            ),\n            compose(\n              writeFile({}),\n              concat(File.fromPath(`${Deno.cwd()}/hoge`))\n            )\n          ]\n        )\n      )\n    )\n  )\n);\n\nconst container = await fetch(\n  Request(\n    {\n      headers: {\n        'accept': 'text/plain',\n        'content-type': 'text/plain'\n      },\n      method: 'POST',\n      url: 'http://localhost:8080/hoge'\n    },\n    encodeText(\"Hello, Hoge!\")\n  )\n).run()\n\nconst response = safeExtract(\"Failed to unpack the response\", container);\n\nassert(Response.Success.is(response));\nassertEquals(response.headers.status, 201);\n\nserver.close();\n```\n\n## Simple HTTP server\n\nThe fastest way to start a HTTP server is to use the `startHTTPServer` function.\nThe function takes two arguments; the first argument is the options, and the second is a unary\nfunction that takes a `Request` and return a `Task` of a `Response`.\n\n```js\nimport Task from \"https://deno.land/x/functional@v1.3.2/library/Task.js\";\nimport Response from \"https://deno.land/x/functional_io@v1.1.0/library/Response.js\";\nimport startHTTPServer from \"https://deno.land/x/functional_http_server@v0.3.1/library/server.js\";\n\nstartHTTPServer({ port: 8080 }, request =\u003e Task.of(Response.OK({}, request.raw)));\n```\n\nYou can test this simple server by executing it your file\n\n```bash\n$ deno run --allow-net server.js\n```\n\n```bash\n$ curl localhost:8080 -d \"Hello, Hoge!\"\n\u003e Hello, Hoge!\n```\n\n---\n\n## Routing\n\nThe main routing tool that comes bundled with this library is conveniently called `route`.\nIt takes a non-zero number of arguments which are defined by a pair of functions.\nThe first function of the pair is used to assert whether or not to execute the second function.\nThe assertion function takes a `Request` and return a `Boolean`, the handling function takes a `Request` and\nmust return a `Task` of a `Response`.\n\n```js\nimport Task from \"https://deno.land/x/functional@v1.3.2/library/Task.js\";\nimport { encodeText } from \"https://deno.land/x/functional@v1.3.2/library/utilities.js\";\nimport Response from \"https://deno.land/x/functional_io@v1.1.0/library/Response.js\";\nimport { route } from \"https://deno.land/x/functional_http_server@v0.3.1/library/route.js\";\n\nstartHTTPServer(\n  { port: 8080 },\n  route(\n    [\n      request =\u003e request.headers.method === 'GET',\n      _ =\u003e Task.of(Response.OK({ 'content-type': 'text/plain' }, encodeText(\"Hello, Hoge!\")))\n    ]\n  );\n);\n```\n\n### Routing handlers\n\nBecause the pattern is common, this library also offers a collection of handler that automatically creates\nthe assertion function. Each handler takes a `String` or a `RegExp` and a unary function.\n\n```js\nimport Task from \"https://deno.land/x/functional@v1.3.2/library/Task.js\";\nimport { encodeText } from \"https://deno.land/x/functional@v1.3.2/library/utilities.js\";\nimport Response from \"https://deno.land/x/functional_io@v1.1.0/library/Response.js\";\nimport { handlers, route } from \"https://deno.land/x/functional_http_server@v0.3.1/library/route.js\";\n\nstartHTTPServer(\n  { port: 8080 },\n  route(\n    handlers.get('/', _ =\u003e Task.of(Response.OK({ 'content-type': 'text/plain' }, encodeText(\"Hello, Hoge!\"))))\n  )\n);\n```\n\n#### handlers`.delete`\n`String|RegExp → (Request → Task Response) → Task Response`\n\nThis function takes a string or a regex and a unary function that takes a `Request` and return a task of a\n`Response`. The handler will apply the unary function to a HTTP requests that uses the `DELETE` method if the path\nequals or match the first argument.\n\n```js\nimport { handlers } from \"https://deno.land/x/functional_http_server@v0.3.1/library/route.js\";\n\nstartHTTPServer({ port: 8080 }, handlers.delete(/\\/hoge\\/(?\u003cID\u003e[a-z]+)$/, handleDestroyHoge));\n```\n\n#### handlers`.get`\n`String|RegExp → (Request → Task Response) → Task Response`\n\nThis function takes a string or a regex and a unary function that takes a `Request` and return a task of a\n`Response`. The handler will apply the unary function to a HTTP requests that uses the `GET` method if the path\nequals or match the first argument.\n\n```js\nimport { handlers } from \"https://deno.land/x/functional_http_server@v0.3.1/library/route.js\";\n\nstartHTTPServer({ port: 8080 }, handlers.get(/\\/hoge\\/(?\u003cID\u003e[a-z]+)$/, handleRetrieveHoge));\n```\n\n#### handlers`.patch`\n`String|RegExp → (Request → Task Response) → Task Response`\n\nThis function takes a string or a regex and a unary function that takes a `Request` and return a task of a\n`Response`. The handler will apply the unary function to a HTTP requests that uses the `PATCH` method if the path\nequals or match the first argument.\n\n```js\nimport { handlers } from \"https://deno.land/x/functional_http_server@v0.3.1/library/route.js\";\n\nstartHTTPServer({ port: 8080 }, handlers.patch(/\\/hoge\\/(?\u003cID\u003e[a-z]+)$/, handleUpdateHoge));\n```\n\n#### handlers`.post`\n`String|RegExp → (Request → Task Response) → Task Response`\n\nThis function takes a string or a regex and a unary function that takes a `Request` and return a task of a\n`Response`. The handler will apply the unary function to a HTTP requests that uses the `POST` method if the path\nequals or match the first argument.\n\n```js\nimport { handlers } from \"https://deno.land/x/functional_http_server@v0.3.1/library/route.js\";\n\nstartHTTPServer({ port: 8080 }, handlers.post('/hoge', handleCreateHoge));\n```\n\n#### handlers`.put`\n`String|RegExp → (Request → Task Response) → Task Response`\n\nThis function takes a string or a regex and a unary function that takes a `Request` and return a task of a\n`Response`. The handler will apply the unary function to a HTTP requests that uses the `PUT` method if the path\nequals or match the first argument.\n\n```js\nimport { handlers } from \"https://deno.land/x/functional_http_server@v0.3.1/library/route.js\";\n\nstartHTTPServer({ port: 8080 }, handlers.put(/\\/hoge\\/(?\u003cID\u003e[a-z]+)$/, handleUpdateHoge));\n```\n\n#### `route`\n`([ (Request → Boolean), (Request → Task Response) ],...) → Task Response`\n\nThis functions takes an arbitrary amount of array pairs of functions and return a task of a `Response`. The first\nfunction of the pair is a predicate, it takes a `Request` and returns a `Boolean`. The second function of the pair\nis a unary function that takes a `Request` and return a task of a `Response`; it will be executed only if the first\nfunction returns `true`.\n\n```js\nimport { route } from \"https://deno.land/x/functional_http_server@v0.3.1/library/route.js\";\n\nstartHTTPServer(\n  { port: 8080 },\n  route(\n    [\n      request =\u003e request.headers.method === \"POST\" \u0026\u0026 request.headers.url === \"/hoge\",\n      request =\u003e Task.of(Response.Created({}, encodeText(\"Created\")))\n    ]\n  )\n);\n```\n\nThe handler can be easily composed using the spread operator.\n\n```js\nstartHTTPServer(\n  { port: 8080 },\n  route(\n    ...hogeRouteHandlers,\n    ...piyoRouteHandlers,\n    ...fugaRouteHandlers\n  )\n);\n```\n\nThe handler will be short-circuited if it's not passed a `Request`. This makes it easy to write a\nfunction to preflight a request.\n\nFor example, if you needed to discard any request that doesn't accept `application/json`, you could\ndo the following.\n\n```js\nimport { compose } from \"https://deno.land/x/ramda@v0.27.2/mod.ts\";\n\nstartHTTPServer(\n  { port: 8080 },\n  compose(\n    route(...routes),\n    request =\u003e request.headers.accept !== 'application/json'\n      ? Task.of(Response.BadRequest({}, new Uint8Array([])))\n      : request\n  )\n);\n```\n\n---\n\n## Server\n\n### `stream`\n`(Request → Task Response) → AsyncIterator → _|_`\n\nThis function takes a unaryFunction -- which itself takes a\n[`Request`](https://github.com/sebastienfilion/functional-io#request) and, returns a Task of a\n[`Response`](https://github.com/sebastienfilion/functional-io#Response) -- and, an Async Iterator of a\n[Deno HTTP request](https://deno.land/std@0.82.0/http). The function doesn't resolve to a value.\n\n### `startHTTPServer`\n`Object → (Request → Response) → Listener`\n\nThis function takes an object of options and, a unary function -- which itself takes a\n[`Request`](https://github.com/sebastienfilion/functional-io#request) and, returns a Task of a\n[`Response`](https://github.com/sebastienfilion/functional-io#Response). The function will return a server instance\nthat can be closed (`server.close()`). [See the Deno server library](https://deno.land/std@0.82.0/http) for reference.\n\n```js\nimport Task from \"https://deno.land/x/functional@v1.3.2/library/Task.js\";\nimport Response from \"https://deno.land/x/functional_io@v1.1.0/library/Response.js\";\nimport startHTTPServer from \"https://deno.land/x/functional_http_server@v0.3.1/library/server.js\";\n\nstartHTTPServer({ port: 8080 }, request =\u003e Task.of(Response.OK({}, request.raw)));\n```\n\n---\n\n## Utilities\n\n### `parseBody`\n`Request → a`\n\nThis function takes a `Request` and return the most appropriate parsing of the body;\nan object if the content-type of the request is `application/json` or, a string if the content type of the request is\n`text/*`.\n\n```js\nimport { parseBody } from \"https://deno.land/x/functional_http_server@v0.3.1/library/utilities.js\";\n\nassertEquals(\n  parseBody(\n    Request({ 'content-type': 'application/json' }, encodeText(JSON.stringify({ piyo: 'piyo' })))\n  ),\n  { piyo: 'piyo' }\n);\n```\n\n### `parseQueryString`\n`Request → Record`\n\nThis function takes a `Request` and return an record of the query string.\n\n```js\nimport { parseQueryString } from \"https://deno.land/x/functional_http_server@v0.3.1/library/utilities.js\";\n\nassertEquals(\n  parseQueryString(Request({ url: '/?hoge=hoge' }, new Uint8Array([]))),\n  { hoge: 'hoge' }\n);\n```\n\n---\n\n## Contributing\n\nWe appreciate your help! Please, [read the guidelines](./CONTRIBUTING.md).\n\n## License\n\nCopyright © 2020 - Sebastien Filion\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the\nrights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit\npersons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the\nSoftware.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\nWARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffunctionalland%2Ffunctional-http-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffunctionalland%2Ffunctional-http-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffunctionalland%2Ffunctional-http-server/lists"}