{"id":19551403,"url":"https://github.com/qihexiang/freesia","last_synced_at":"2026-05-09T02:33:32.807Z","repository":{"id":57177872,"uuid":"354186453","full_name":"qihexiang/freesia","owner":"qihexiang","description":"A TypeScript HTTP library for Deno and Node.js","archived":false,"fork":false,"pushed_at":"2022-12-01T14:01:10.000Z","size":1141,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-11T12:25:13.424Z","etag":null,"topics":["deno","fp","freesia","nodejs","typescript","web"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/qihexiang.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":"2021-04-03T03:04:45.000Z","updated_at":"2022-03-03T14:02:24.000Z","dependencies_parsed_at":"2023-01-22T14:01:01.691Z","dependency_job_id":null,"html_url":"https://github.com/qihexiang/freesia","commit_stats":null,"previous_names":["qihexiang/anelsonia","qihexiang/anelsonia-web"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/qihexiang/freesia","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qihexiang%2Ffreesia","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qihexiang%2Ffreesia/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qihexiang%2Ffreesia/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qihexiang%2Ffreesia/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/qihexiang","download_url":"https://codeload.github.com/qihexiang/freesia/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qihexiang%2Ffreesia/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32804943,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"online","status_checked_at":"2026-05-09T02:00:06.633Z","response_time":123,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["deno","fp","freesia","nodejs","typescript","web"],"created_at":"2024-11-11T04:13:47.512Z","updated_at":"2026-05-09T02:33:32.773Z","avatar_url":"https://github.com/qihexiang.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Freesia: a TypeScript library for building Node.js HTTP servers.\n\nFreesia is a library for building Node.js HTTP servers, it provides a way to describe you HTTP handling process in a functional way.\n\n## Installing\n\nInstall freesia into your project by a package manager, like NPM:\n\n```sh\nnpm install freesia\n```\n\nThis package contains built-in TypeScript declarations, you don't need any other packages to support it. But in practice, it's recommended to install `@types/node` as a developing dependency.\n\nThis package is also a dual-module package, you can use it in a CommonJS or a ESM project.\n\nIn CommonJS\n\n```js\nconst { shimHTTP } = require(\"freesia\");\n```\n\nIn ESM and TypeScript\n\n```js\nimport { shimHTTP } from \"freesia\";\n```\n\n## Hello World\n\n```js\n// app.js\nimport { shimHTTP, response } from \"freesia\";\nimport { createServer } from \"http\";\n\nconst main = async (req) =\u003e response(\"hello, world\");\ncreateServer(shimHTTP(main)).listen(8080);\n```\n\nRun it with `node app.js`, and then visit \u003chttp://localhost:8080\u003e, you'll see the `\"hello, world\"` message.\n\nIn this example, we find that `shimHTTP` function transform a function into a http request handler, the function we give is the `EntryPoint` of a Freesia app. `EntryPoint` is the type of function which parameter is the request body from `http`/`https`/`http2` module, and return a `Respond\u003cstring|Uint8Array|Readable\u003e` tuple.\n\n## Concepts\n\n### Bootstrap process\n\nHttp servers are always designed in event-driven architecture, which means the code we write is in fact a \"bootstrap code\": it will be executed directly, create port listener, prepare resources(for example, connect to a database), define handler functions and bind them to events.\n\nIn a bootstrap process, the handler function won't be called, they are used as values which tell the server what to do when requests come in.\n\n### Request handling process\n\nAfter the bootstrap process finished, the server will keep running and waiting requests come in. If a request comes in, it will call the handler function to deal with this request. [Freesia hooks](#hooks) and router functions created by [Routing](#routing) functions can be called only in request handling process.\n\n## Respond\n\n`Respond\u003cT\u003e` is a tuple that can define a response, it includes 3 parts: a body of type `T`, status and http headers.\n\n### Respond body\n\nThe first element of the tuple is the body of the response, it can be any type specified by template type `T`, or `undefined` if you'd like to response nothing.\n\n### Respond status\n\nThe second element of the tuple is the status of the response, it can be a http status code, for example `200`, or a status code with custom status text like `[200, \"Success\"]`.\n\n### Respond http headers\n\nElements after the second element are http headers, headers can be defined as `Record\u003cstring, string | string[]\u003e`. For example, such two patterns have the same result:\n\n```ts\nconst res1: Respond\u003cstring\u003e = [\n    \"hello, world\",\n    200,\n    { \"Content-Type\": \"text/plain\", \"Content-Length\": \"12\" },\n];\nconst res2: Respond\u003cstring\u003e = [\n    \"hello, world\",\n    200,\n    { \"Content-Type\": \"text/plain\" },\n    { \"Content-Length\": \"12\" },\n];\n```\n\nElement with higher index will have higher priority when header names duplicated. For example:\n\n```ts\n[\n    \"hello, world\",\n    200,\n    { \"Content-Type\": \"text/html\", \"Content-Length\": \"12\" },\n    { \"Content-Type\": \"text/plain\" },\n];\n```\n\nThis Respond will be response with header `Content-Type: text/plain`.\n\n### response function\n\nIt's not difficult to create a `Respond\u003cT\u003e` manually, but function `response` provide an another way.\n\nFollow examples can create a `Respond\u003cT\u003e`:\n\n```ts\nresponse(\"hello, world\"); // [ 'hello, world', 200 ]\nresponse(undefined); // [ undefined, 204 ]\nresponse(\"hello, world\", 200, { \"Content-Type\": \"text/plain\" }); // [ 'hello, world', 200, { 'Content-Type': 'text/plain' } ]\n```\n\n`response` method can attach http headers to an existed `Respond`:\n\n```ts\nconst res = response(\"hello, world\"); // [ 'hello, world', 200 ]\nresponse(res, { \"Content-Type\": \"text/plain\", \"Content-Length\": \"12\" });\n// [\n//   'hello, world',\n//   200,\n//   { 'Content-Type': 'text/plain', 'Content-Length': '12' }\n// ]\n```\n\nIt also provide a way to rebuild another Respond from existed Respond:\n\n```ts\nconst res = response\u003c{ message: string }\u003e({ message: `hello, world` }, 200);\nresponse(res, (body, status, headers) =\u003e {\n    return response(JSON.stringify(body), status, ...headers, {\n        \"Content-Type\": \"application/json\",\n    });\n});\n// [\n//   '{\"message\":\"hello, world\"}',\n//   200,\n//   { 'Content-Type': 'application/json' }\n// ]\n```\n\nIt's useful in `EntryPoint` function because the body type must be binary-like (`string | Uint8Array | Readable`).\n\nRebuild with asynchronous rebuilders will give a Promised Respond.\n\n```ts\nconst res = response(\"./README.md\", 200);\nconst resAsync = response(res, async (body, status, headers) =\u003e {\n    return response(\n        await fs.promises.readFile(url, { encoding: \"utf-8\" }),\n        status,\n        ...headers,\n        { \"Content-Type\": getType(url) }\n    );\n}); // Promise\u003c[string, 200, { \"Content-Type\": \"text/markdown\" }]\u003e\n```\n\n## Routing\n\nRequest pathname is one of the most important thing in a request url that tell the server what the client need. The server side application need to binding a handler function to each path pattern. The most simple example of a routing looks like this:\n\n```ts\nfunction main() {\n    const { pathname } = useURL();\n    let response: Respond\u003cstring | Uint8Array | Readable\u003e;\n    if (pathname.match(/^(\\/api)/)) {\n        response = apiHandler();\n    } else if (pathname.match(/^(\\/(login|logout))/)) {\n        response = userSessionHandler();\n    } else if (pathname.match(/^(\\/favicon.ico)$/)) {\n        response = faviconHandler();\n    } else if (pathname.match(/^(\\/public)/)) {\n        response = staticFileHandler();\n    } else {\n        response = renderHandler();\n    }\n    return response;\n}\n```\n\nIn this example, we use `if else` block to get right value of response by calling corresponding handler, with freesia routing functions, we can do this more elegant.\n\n### Define routes separately\n\n`createRoute(pattern, handler, flags)` can define a route. For example:\n\n\u003e `flags` is the RegExp flags, default value is `\"i\"`\n\n```ts\nconst helloRt = createRoute(\n    \"/hello/:\u003clang\u003e/:\u003cusername\u003e\",\n    ({ lang, username }) =\u003e\n        response(`${i18n(\"hello\", lang)}, ${username}`, 200, {\n            \"Content-Type\": \"text/plain\",\n        })\n);\nconst res = helloRt(useURL().pathname) ?? response(\"No route matched\", 404);\n```\n\nIn pattern, there are 4 ways to describe a route parameter:\n\n-   `:\u003cparamName\u003e`: match a string between two `/`, for example, `/hello/:\u003cusername\u003e` can match `/hello/freesia` and `/hello/freesia/` but can't match `/hello/freesia/13`\n-   `:{paramName}`: match a string greedily, for example, `/hello/:{username}/` can match `/hello/freesia/` and `/hello/js/freesia/` (`username` is `\"js/freesia\"`).\n-   `:[paramName]`: like `:{paramName}`, but can catch 0 characters string, for example `/hello/:[username]` can match `/hello/`, while `username` is `\"\"`.\n-   `:(paramName)`: like `:[paramName]`, can match `/` before the parameter, for example `/hello/:(username)` can match `/hello`, while `username` is `undefined`.\n\n`createRoute` will return a route function that receive a string as parameter, if this string matched the pattern, the matched parameters will passed to the handler, and return a value, if no matched, the route function won't call the handler but return `null` directly. You can hub many route functions together with `??` operator as they will return null if not matched, just like this:\n\n```ts\nconst res =\n    helloRt(pathname) ??\n    goodbyeRt(pathname) ??\n    updateInfoRt(pathname) ??\n    disableUserRt(pathname) ??\n    response(\"No route matched\", 404);\n```\n\nWith `createSwitcher`, you can hub many route functins together:\n\n```ts\nconst switcher = createSwitcher(\n    helloRt,\n    goodbyeRt,\n    updateInfoRt,\n    disableUserRt\n);\nconst res = switcher(pathname) ?? response(\"No route matched\", 404);\n```\n\nThis requires all routes functions has the same return type, or specify a template type for `createSwitcher\u003cT\u003e` that capitable with all route functions.\n\n### Define routes at one place\n\n`createSwRt` function provides a way to create routes and hub them to a switcher at the same time, like this:\n\n```ts\nconst switcher = createSwRt\u003cRespond\u003cBinaryLike\u003e\u003e()\n    .route(\"/hello/:\u003cusername\u003e\", helloHandler)\n    .route(\"/goodbye/:\u003cusername\u003e\", goodbyeHandler)\n    .route(\"/update_info/:\u003cusername\u003e/:\u003coperate\u003e/:[restArgs]\", updateInfoHandler)\n    .route(\"/user/:\u003cusrename\u003e/disable\", disableUserHandler)\n    .build();\nconst res = switcher(pathname) ?? response(\"No route matched\", 404);\n```\n\nThe `build` method will return a switcher function, which returns `null` (if no routes matched) or the handler return value.\n\n`fallback` method can also return a switcher function, it won't return null:\n\n```ts\nconst switcher = createSwRt\u003cRespond\u003cBinaryLike\u003e\u003e()\n    .route(\"/hello/:\u003cusername\u003e\", helloHandler)\n    .route(\"/goodbye/:\u003cusername\u003e\", goodbyeHandler)\n    .route(\"/update_info/:\u003cusername\u003e/:\u003coperate\u003e/:[restArgs]\", updateInfoHandler)\n    .route(\"/user/:\u003cusrename\u003e/disable\", disableUserHandler)\n    .fallback((url) =\u003e response(`No route matched url ${url}`, 404));\nconst res = switcher(pathname);\n```\n\n### Methods limit\n\nUse an object instead of a function to dispatch requests to different handlers by request methods. For example,\n\n```ts\nconst getUserInfoRt = createRoute(\"/user/:\u003cusername\u003e/info\", {\n    GET: getUserInfoHandler,\n    PUT: updateUserInfoHandler,\n});\n```\n\n```ts\nconst getUserInfoRt = createRoute(\"/user/:\u003cusername\u003e/info\", {\n    GET: userInfoHandler,\n});\n```\n\nIt also works in `createSwRt`:\n\n```ts\nconst switcher = createSwRt\u003cRespond\u003cBinaryLike\u003e\u003e()\n    .route(\"/hello/:\u003cusername\u003e\", { GET: helloHandler })\n    .route(\"/goodbye/:\u003cusername\u003e\", { GET: goodbyeHandler })\n    .route(\"/update_info/:\u003cusername\u003e/:\u003coperate\u003e/:[restArgs]\", {\n        PUT: updateInfoHandler,\n    })\n    .route(\"/user/:\u003cusrename\u003e/disable\", { Put: disableUserHandler })\n    .fallback((url) =\u003e response(`No route matched url ${url}`, 404));\nconst res = switcher(pathname);\n```\n\n## Hooks\n\nSome values might be used in many functions called during a request handling process, as a result it must be passed through many hierarchies. With Node.js AsyncStorage API, freesia provides some hooks to get values with out pass them in parameters.\n\n### useRequest\n\n`useRequest` hook provide a way to access the request object any where in a request handling process.\n\n### useURL\n\n`useURL` provide a way to access request url. See the overloads [here](https://qihexiang.github.io/freesia/modules.html#useURL)\n\n### createContext\n\n`createContext` provide a way to create a context for a request handling process. See the definitions here:\n\nAnd examples:\n\n```ts\nconst [assignUser, getUser, dropUserCtx] = createContext\u003cPromise\u003cUser\u003e\u003e();\n\n// UserValidate will access DB and assign a user model to context\nfunction UserValidate(username: string, token: string): boolean {\n    if (validate(username, token)) {\n        assignUser(DB.query({ where: { username } }));\n        return true;\n    }\n    return false;\n}\n// This is a function only be called in a request handling process\nasync function getUserName(): Promise\u003cstring | undefined\u003e {\n    return (await getUser())?.username;\n}\n```\n\n## Utils\n\n### composeFn\n\n`composeFn` provides a way to compose many functions together:\n\n```ts\nconst { fn } = composeFn((x: number) =\u003e x + 1)\n    .next((x) =\u003e Math.pow(x, 2))\n    .next((x) =\u003e x / 2)\n    .next((x) =\u003e `result is ${x}`);\nfn(4); // result is 12.5\n```\n\n### computeStream\n\n`computeStream` provide a way to compute a value in a stream style process.\n\n```ts\nconst value = computeStream(4)\n    .map((x) =\u003e x + 1)\n    .map((x) =\u003e Math.pow(x, 2))\n    .map((x) =\u003e x / 2)\n    .map((x) =\u003e `the result is ${x}`).value;\n```\n\nExcept `map` method, there is `mapNN` method, which you can only deal with non-null values and let `null` and `undefined` passed through.\n\n```ts\nconst value = computeStream(token)\n    .map((tk) =\u003e validator(tk)) // validator will return null if not valid\n    .mapNN((username) =\u003e queryUser(username)).value; // queryUser will return null if no such user // Promise\u003cUser\u003e | null\n```\n\n`computeStreamLazy` has the same API with `computeStream`, but functions are called each time accessing value property.\n\n### createEffect\n\n`createEffect` can create a wrapper for a function that do some side-effect for the original function. An example looks like this:\n\n```ts\nconst add = (a: number, b: number) =\u003e a + b;\nconst debugWrapper = createEffect\u003ctypeof add\u003e((a, b) =\u003e {\n    console.log(`a is ${a}, b is ${b}`);\n    return (result) =\u003e {\n        console.log(`result is ${result}`);\n    };\n});\nconst wrappedAdd = debugWrapper(add);\nconst result = wrappedAdd(1, 2);\n/**\n * logs:\n * a is 1, b is 2\n * result is 3\n */\n```\n\n`createEffect4Any` is very similar to `createEffect`, the wrapper created by it can wrap any type of function, but the side effect should be irrelevant with the parameters and return value of the original function. For example:\n\n```ts\nconst fib = (index: number): number =\u003e {\n    if (index === 0 || index === 1) return 1;\n    else return fib(index - 1) + fib(index - 2);\n};\nconst timeMeasure = createEffect4Any(() =\u003e {\n    const start = new Date().getTime();\n    return () =\u003e {\n        console.log(`Use ${new Date().getTime() - start}ms`);\n    };\n});\nconst fibWithTM = timeMeasure(fib);\nfibWithTM(40);\n/**\n * logs:\n * Use 1452ms\n */\n```\n\n### createProxy\n\ncreateProxy provide a way to proxy a function, which can rewrite the parameters and return values.\n\nFor example:\n\n```ts\ndeclare function queryUser(userId: string): Promise\u003cUser\u003e;\nconst getUserEmailWrapper = createProxy\u003c\n    typeof queryUser,\n    (username: string, token: string) =\u003e Promise\u003cstring | undefined\u003e\n\u003e(async (username: string, token: string) =\u003e {\n    const userId = await validator(username, token);\n    // first element null told the proxy not to call the original function\n    // second element will be executed to get a fallback value.\n    if (userId === undefined) return [null, () =\u003e undefined];\n    // first element pass parameters in an array\n    // second element will be called to deal with the original return value.\n    else\n        return [\n            [userId],\n            async (user) =\u003e {\n                return (await user).email;\n            },\n        ];\n});\nconst getUserEmail = getUserEmailWrapper(queryUser);\nconst email = getUserEmail(username, token);\n```\n\nLook at docs of [createProxy](https://qihexiang.github.io/freesia/modules.html#createProxy)\n\n### memoryCache\n\nmemoryCache is a wrapper that can cache return value of a function.\n\nExample:\n\n```ts\nconst fib = memoryCache((index: number): number =\u003e {\n    if (index === 1 || index === 2) return 1;\n    else return fib(index - 1) + fib(index - 2);\n});\n```\n\nThis wrapper will receive origin parameters as an array, and compare with used parameters by `===` operator and `Object.is` function (same-zero-value comparation).\n\n\u003e Be careful when using objects as parameters, `{a: 1} !== {a: 1}`, `[1,2,3] !== [1,2,3]`.\n\n### isVoid\n\n`isVoid` can return if a value is `null` or `undefined`.\n\n```ts\ndeclare let value: string | undefined | null;\nif (isVoid(value)) {\n    value; // =\u003e undefined | null\n}\nif (isVoid(value, [undefined])) {\n    value; // =\u003e undefined\n}\nif (isVoid(value, [null])) {\n    value; // =\u003e null\n}\ndeclare let mayNotDefined: string | undefined;\nif (isVoid(mayNotDefined)) {\n    mayNotDefined; // =\u003e undefined\n}\ndeclare let nullableValue: string | null;\nif (isVoid(nullableValue)) {\n    nullableValue; // =\u003e null\n}\n```\n\n### isEnum\n\n`isEnum` provides a way to check if a value one of the enum values, for example, a user-input string:\n\n```ts\n// valid option can be \"auto\", \"manual\", \"default\"\nconst option: string = await getUserInput();\nif (isEnum(option, [\"auto\", \"manual\", \"default\"])) {\n    option; // =\u003e \"auto\" | \"manual\" | \"default\"\n}\n```\n\nSupport string and number as basic types.\n\n### Others\n\n-   resJson\n-   rateLimiter\n\nFind their docs in the [GitHub Pages](https://qihexiang.github.io/freesia/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqihexiang%2Ffreesia","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fqihexiang%2Ffreesia","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqihexiang%2Ffreesia/lists"}