{"id":15650349,"url":"https://github.com/sachinraja/uttp","last_synced_at":"2025-04-06T10:11:35.920Z","repository":{"id":40606417,"uuid":"496025247","full_name":"sachinraja/uttp","owner":"sachinraja","description":"write your request handlers once, run anywhere","archived":false,"fork":false,"pushed_at":"2025-02-07T01:12:20.000Z","size":235,"stargazers_count":36,"open_issues_count":8,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-30T08:11:15.255Z","etag":null,"topics":["express","fastify","fetch","h3","koa","node"],"latest_commit_sha":null,"homepage":"","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/sachinraja.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"sachinraja"}},"created_at":"2022-05-25T00:05:40.000Z","updated_at":"2024-06-30T02:54:15.000Z","dependencies_parsed_at":"2024-07-09T18:38:29.862Z","dependency_job_id":"2f2a22e4-d098-4e33-9ac1-a9d7ef8efabd","html_url":"https://github.com/sachinraja/uttp","commit_stats":{"total_commits":36,"total_committers":2,"mean_commits":18.0,"dds":0.02777777777777779,"last_synced_commit":"e6ba12c4b7bfe25b9bae554a66bfadff41c493a1"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sachinraja%2Futtp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sachinraja%2Futtp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sachinraja%2Futtp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sachinraja%2Futtp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sachinraja","download_url":"https://codeload.github.com/sachinraja/uttp/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247464222,"owners_count":20942970,"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":["express","fastify","fetch","h3","koa","node"],"created_at":"2024-10-03T12:34:14.394Z","updated_at":"2025-04-06T10:11:35.894Z","avatar_url":"https://github.com/sachinraja.png","language":"TypeScript","funding_links":["https://github.com/sponsors/sachinraja"],"categories":[],"sub_categories":[],"readme":"# uttp\n\nwrite your request handlers once, run anywhere\n\ncurrently supports:\n\n- [Node (vanilla HTTP)](https://nodejs.org/api/http.html)\n- [Express](https://expressjs.com/)\n- [Fastify](https://www.fastify.io/)\n- [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) (Cloudflare Workers, Deno, SvelteKit, Astro, Remix, etc.)\n- [h3](https://github.com/unjs/h3) (Nuxt)\n- [Koa](https://koajs.com/)\n- [AWS Lambda](https://aws.amazon.com/lambda/)\n\n## Install\n\n```sh\nnpm install uttp\n```\n\n## Usage\n\nFirst, define your universal request handler:\n\n```ts\n// handler.ts\nimport { defineHandler } from 'uttp'\n\nexport const handler = defineHandler(() =\u003e {\n\t// return an object that will be used by each adapter\n\treturn {\n\t\t// called on each request\n\t\thandleRequest() {\n\t\t\t// return a response object\n\t\t\t// that will be sent by the server framework\n\t\t\treturn {\n\t\t\t\tstatus: 200,\n\t\t\t\tbody: 'Hello world!',\n\t\t\t\theaders: { 'Content-Type': 'text/html' },\n\t\t\t}\n\t\t},\n\t\tadapterOptions: {},\n\t}\n})\n```\n\nFor all server frameworks uttp supports this will show `Hello world!` as HTML.\n\nThen you can use adapters to get middleware/plugins/handlers for the server frameworks.\n\nFor Node:\n\n```ts\n// adapters/node.ts\nimport { getNodeAdapter } from 'uttp/adapters/node'\nimport { handler } from '../handler'\n\nexport const nodeHandler = getNodeAdapter(handler)\n```\n\nUsers would use it like this:\n\n```ts\nimport { nodeHandler } from 'my-lib/adapters/node'\n\nconst server = createServer(await nodeHandler())\n\nserver.listen(3000)\n```\n\nThis process is the same for other server frameworks.\n\nFor Fastify:\n\n```ts\n// adapters/fastify.ts\nimport { getFastifyAdapter } from 'uttp/adapters/fastify'\nimport { handler } from '../handler'\n\nexport const getFastifyPlugin = getFastifyAdapter(handler)\n```\n\nUsers would use it like this:\n\n```ts\nimport { getFastifyPlugin } from 'my-lib/adapters/fastify'\n\nconst server = fastify()\n\nserver.register(await getFastifyPlugin())\n\nserver.listen(3000)\n```\n\nNote these are placed in different entry points / files because `uttp/adapters/*` imports directly from the server frameworks. You cannot export multiple handlers from the same entry point because users would be forced to install server frameworks that they are not using.\n\n### Request\n\nA universal request object is passed to `handleRequest` containing some common properties coerced from the individual frameworks:\n\n```ts\nimport { defineHandler } from 'uttp'\n\nexport const handler = defineHandler(() =\u003e {\n\treturn {\n\t\thandleRequest(req) {\n\t\t\tif (req.method !== 'GET') {\n\t\t\t\treturn { status: 400, body: 'method must be get' }\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tstatus: 200,\n\t\t\t\tbody: 'Hello world!',\n\t\t\t\theaders: { 'Content-Type': 'text/html' },\n\t\t\t}\n\t\t},\n\t\tadapterOptions: {},\n\t}\n})\n```\n\n### Helpers\n\nRequest handlers are passed a set of universal functions that vary in implementation across frameworks but retain the same signature:\n\n```ts\nimport { defineHandler } from 'uttp'\n\nexport const handler = defineHandler((helpers) =\u003e {\n  return {\n    async handleRequest(req) {\n      // each adapter will pass helpers\n      // that conform to function signatures\n      const body = await helpers.parseBodyAsString(req.rawRequest)\n\n      if (!body) {\n        return { status: 400, body: 'must pass body' }\n      }\n\n      const json = JSON.parse(body)\n\n      json.\n\n      return {\n        status: 200,\n        body: 'Hello world!',\n        headers: { 'Content-Type': 'text/html' },\n      }\n    },\n    adapterOptions: {},\n  }\n})\n```\n\nIf you need a helper that is not currently available, please create an issue.\n\n### User Options\n\nYour request handler can take in options from users of your handler:\n\n```ts\nimport { defineHandler } from 'uttp'\n\ninterface HandlerOptions {\n\tparse(text: string): any | Promise\u003cany\u003e\n\tmaxBodySize?: number\n}\n\nexport const handler = defineHandler(\n\t// specify options type here\n\t// can specify as many arguments as you want after `helpers`\n\t// which the user will need to pass\n\t(helpers, options: HandlerOptions) =\u003e {\n\t\treturn {\n\t\t\tasync handleRequest(req) {\n\t\t\t\tconst body = await helpers.parseBodyAsString(req.rawRequest)\n\t\t\t\tif (!body) return { status: 400, body: 'must have body' }\n\n\t\t\t\tconst parsedBody = await options.parse(body)\n\n\t\t\t\t// ...\n\n\t\t\t\treturn { status: 200 }\n\t\t\t},\n\t\t\tadapterOptions: {\n\t\t\t\tmaxBodySize: options.maxBodySize,\n\t\t\t},\n\t\t}\n\t},\n)\n```\n\nUsers will pass options like this:\n\n```ts\nimport { nodeHandler } from 'my-lib/adapters/node'\n\nconst server = createServer(await nodeHandler({ parse: JSON.parse }))\n\nserver.listen(3000)\n```\n\n### Adapter Options\n\nYou must return an `adapterOptions` object. These options may be derived from user options. Here is an example with a description of what each option does:\n\n```ts\nimport { defineHandler } from 'uttp'\n\nexport const handler = defineHandler(() =\u003e {\n\treturn {\n\t\thandleRequest() {\n\t\t\treturn { status: 200, body: 'Hello world!' }\n\t\t},\n\t\tadapterOptions: {\n\t\t\t// limit body size\n\t\t\tmaxBodySize: 1000,\n\t\t},\n\t}\n})\n```\n\n## Starter Templates\n\nSee starter templates for how to setup a package that uses uttp.\n\n- [uttp-starter](examples/starter)\n\n## Utilities\n\nuttp comes with some utils to help you build and test your handler.\n\n### Runners\n\nRunners are an easy way to get a server up for a framework by providing your handler. Only some frameworks are supported.\n\n```ts\nimport {\n\trunNode,\n\t// runExpress,\n\t// runFastify,\n\t// runH3,\n\t// runKoa,\n} from 'uttp/utils/runners'\n// your universal handler\nimport { handler } from './handler.js'\n\nrunNode(\n\thandler,\n\t// handler options as an array\n\t[{ token: 'secret' }],\n\t// server-related options\n\t{ port: 3000 },\n)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsachinraja%2Futtp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsachinraja%2Futtp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsachinraja%2Futtp/lists"}