{"id":15011421,"url":"https://github.com/sejori/peko","last_synced_at":"2025-04-13T00:49:34.431Z","repository":{"id":37531539,"uuid":"352394324","full_name":"sejori/peko","owner":"sejori","description":"Featherweight apps on the edge 🐣⚡ Node, Deno, Bun \u0026 Cloudflare Workers.","archived":false,"fork":false,"pushed_at":"2024-12-24T00:29:55.000Z","size":6227,"stargazers_count":163,"open_issues_count":3,"forks_count":9,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-13T00:49:27.031Z","etag":null,"topics":["bunjs","cloudflare-workers","denojs","http","middleware","react","router","typescript"],"latest_commit_sha":null,"homepage":"https://peko.deno.dev/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sejori.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}},"created_at":"2021-03-28T17:32:29.000Z","updated_at":"2025-02-10T11:48:06.000Z","dependencies_parsed_at":"2024-07-25T19:12:10.451Z","dependency_job_id":"15301b98-f69c-46e9-b607-fb92807a242b","html_url":"https://github.com/sejori/peko","commit_stats":{"total_commits":690,"total_committers":6,"mean_commits":115.0,"dds":"0.49855072463768113","last_synced_commit":"181e82b4f406b04434b7f417e5f6ad654a82f7c6"},"previous_names":["sejori/peko","sebringrose/peko"],"tags_count":85,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sejori%2Fpeko","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sejori%2Fpeko/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sejori%2Fpeko/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sejori%2Fpeko/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sejori","download_url":"https://codeload.github.com/sejori/peko/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248650436,"owners_count":21139672,"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":["bunjs","cloudflare-workers","denojs","http","middleware","react","router","typescript"],"created_at":"2024-09-24T19:41:04.647Z","updated_at":"2025-04-13T00:49:34.413Z","avatar_url":"https://github.com/sejori.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\" style=\"font-size: 72px;\"\u003e\n    \u003cimg \n        width=\"270px\"\n        style=\"margin: 1rem auto;\"\n        src=\"https://raw.githubusercontent.com/sejori/peko/main/example/reactSSR/assets/twemoji_chick.svg\" alt=\"peko-chick\" \n    /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003e\n    Peko\n\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cstrong\u003eFast\u003c/strong\u003e - Regex route matching and caching with Node, Deno, Bun \u0026 Cloudflare Worker support 🏎️💨\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n    \u003cstrong\u003eFeatherweight\u003c/strong\u003e - Functional API built with Web Standards \u0026 zero dependencies 🪶\u003cbr\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n    \u003cstrong\u003eFeature-rich\u003c/strong\u003e - Static files, auth, server-sent events \u0026 server profiling utils 🤹\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cspan\u003e\n        \u0026nbsp;\n        \u003ca href=\"#overview\"\u003e\n            Overview\n        \u003c/a\u003e\n        \u0026nbsp;\n    \u003c/span\u003e\n    \u003cspan\u003e\n        \u0026nbsp;\n        \u003ca href=\"#getting-started\"\u003e\n            Getting Started\n        \u003c/a\u003e\n        \u0026nbsp;\n    \u003c/span\u003e\n        \u003cspan\u003e\n        \u0026nbsp;\n        \u003ca href=\"#recipes\"\u003e\n            Recipes\n        \u003c/a\u003e\n        \u0026nbsp;\n    \u003c/span\u003e\n    \u003cspan\u003e\n        \u0026nbsp;\n        \u003ca href=\"#motivations\"\u003e\n            Motivations\n        \u003c/a\u003e\n        \u0026nbsp;\n    \u003c/span\u003e\n\u003c/p\u003e\n\n```bash\nnpm i @sejori/peko\n```\n\n\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\nRoutes and middleware are added to a `Router` instance with `.use`, `.addRoute` or `.get/post/put/delete`.\n\nThe router is then used with your web server of choice, e.g. `Deno.serve` or `Bun.serve`.\n\n\n```js\nimport * as Peko from \"@sejori/peko\"; // or https://deno.land/x/peko/mod.ts in Deno\n\nconst router = new Peko.Router();\n\nrouter.use(Peko.logger(console.log));\n\nrouter.get(\"/shorthand-route\", () =\u003e new Response(\"Hello world!\"));\n\nrouter.post(\n  \"/shorthand-route-ext\",\n  async (ctx, next) =\u003e {\n    await next();\n    console.log(ctx.request.headers);\n  },\n  (req) =\u003e new Response(req.body)\n);\n\nrouter.addRoute({\n  path: \"/object-route\",\n  middleware: async (ctx, next) =\u003e {\n    await next();\n    console.log(ctx.request.headers);\n  }, // can also be array of middleware\n  handler: () =\u003e new Response(\"Hello world!\"),\n});\n\nrouter.addRoutes([\n  /* array of route objects */\n]);\n\nDeno.serve((req) =\u003e router.handle(req));\n```\n\n\u003ch2 id=\"getting-started\"\u003eGetting started\u003c/h2\u003e\n\n- `git clone https://github.com/sejori/peko` \u0026\u0026 `cd peko`\n\nCheck `example` directory to see implementations of:\n\n- server-side rendering Preact to HTML\n- streaming server-sent events to web client\n- logging requests\n- caching responses\n- JWT authentication middleware\n\n**Deno [Live Deploy](https://peko.deno.dev)**\n\n- Process 1: `deno task dev:build`\n- Process 2: `deno task dev:deno`\n\n**Cloudflare Workers [Live Deploy](https://peko.sejori.workers.dev)**\n\n- `npm i`\n- Process 1: `npm run dev:build`\n- Process 2: `npm run dev:wrangler`\n\n**Bun:**\nBun is currently not deployed but it is profiled against Deno, check the GitHub actions to see results.\n\n- `bun install`\n- Process 1: `bun dev:build`\n- Process 2: `bun dev:bun`\n\n\u003ch2 id=\"types\"\u003eTypes\u003c/h2\u003e\n\n### [**Router**](https://deno.land/x/peko/mod.ts?s=Router)\n\nThe main class/entrypoint of Peko.\n\nThe `handle` method generates a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response/Response) from a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request) argument via configured routes and middleware.\n\n### [**Route**](https://deno.land/x/peko/mod.ts?s=Route)\n\nRoutes are added to a `Router` and matched to a `Request` via their `path` property. Once matched, the route's `middleware` and `handlers` are invoked to process the `Request` (after global middleware on the `Router`).\n\nDynamic path parameters are supported in the `/users/:userid` syntax.\n\n### [**RequestContext**](https://deno.land/x/peko/mod.ts?s=RequestContext)\n\nAn object containing request data that is passed into middleware and handlers in the `Request` process lifecycle.\n\nThe `state` property is an object designed to transfer information between middleware/handlers.\n\n### [**Middleware**](https://deno.land/x/peko/mod.ts?s=Middleware)\n\nFunctions that receive `RequestContext` and `next`. They are designed to:\n\n- Return a `Response` and end the `Request` processing lifecycle (e.g. returning a `401`)\n- Call `await next()` to access the final response (e.g. logging)\n- Edit the context's `state` (e.g. rendering geolocation to HTML)\n\n### [**Handler**](https://deno.land/x/peko/mod.ts?s=Handler)\n\nThe final request handling function on a `Route`, receives `RequestContext` argument.\n\nMust return/resolve to a `Response` (e.g. Render HTML or return JSON payload).\n\n\u003ch2 id=\"recipes\"\u003eRecipes\u003c/h2\u003e\n\n### Error handling\n\nIf no matching route is found for a request an empty 404 response is sent. If an error occurs in handling a request an empty 500 response is sent. Both of these behaviours can be overwritten with the following middleware:\n\n```js\nrouter.use(async (_, next) =\u003e {\n  const response = await next();\n  if (!response)\n    return new Response(\"Would you look at that? Nothing's here!\", {\n      status: 404,\n    });\n});\n```\n\n```js\nrouter.use(async (_, next) =\u003e {\n  try {\n    await next();\n  } catch (e) {\n    console.log(e);\n    return new Response(\"Oh no! An error occured :(\", { status: 500 });\n  }\n});\n```\n\n### Response Caching\n\nIn stateless computing, memory should only be used for source code and disposable cache data. Response caching ensures that we only store data that can be regenerated or refetched. The configurable `cacher` middleware provides drop in handler memoization and response caching for your routes.\n\n```js\nrouter.addRoute(\n  \"/get-time\",\n  Peko.cacher({ itemLifetime: 5000 }),\n  () =\u003e new Response(Date.now())\n);\n```\n\nThe cacher stores response items in memory by default, but it can be extended to use any key value storage by supplying the `store` options parameter (e.g. Cloudflare Workers KV).\n\n```js\nimport { Router, CacheItem, cacher } from \"@sejori/peko\";\n\nconst router = new Router();\n\nconst itemMap: Map\u003cstring, CacheItem\u003e = new Map();\n\nrouter.addRoute(\"/get-time\", {\n  middleware: cacher({\n    itemLifetime: 5000,\n    store: {\n      get: (key) =\u003e itemMap.get(key),\n      set: (key, value) =\u003e itemMap.set(key, value),\n      delete: (key) =\u003e itemMap.delete(key),\n    },\n  }),\n  handler: () =\u003e new Response(Date.now()),\n});\n```\n\n\u003ch2 id=\"showcase\"\u003eShowcase\u003c/h2\u003e\n\nPR to add your project 🙌\n\n### [shineon.systems](https://shineon.systems)\n\n- **Stack:** React, Cloudflare Workers, KV and R2\n- **Features:** KV cache, R2 email list, Markdown rendering\n- [source](https://github.com/shine-systems/shineponics/blob/main/server.ts)\n\n### [thesebsite.deno.dev](https://thesebsite.deno.dev)\n\n- **Stack:** HTML5\n- **Features:** UI TS scripts transpiled to JS and cached for browser\n- [source](https://github.com/sebringrose/peko/blob/main/examples/auth/app.ts)\n\n**Note:** [lit-html](https://marketplace.visualstudio.com/items?itemName=bierner.lit-html) and [es6-string-css](https://marketplace.visualstudio.com/items?itemName=bashmish.es6-string-css) VS Code extensions recommended.\n\n\u003ch2 id=\"motivations\"\u003eMotivations\u003c/h2\u003e\n\n### Apps on the edge\n\nThe modern JavaScript edge rocks because the client-server gap practically disappears. We can share modules across the client and cloud.\n\nThis eliminates much of the bloat in traditional JS server-side systems, increasing project simplicity while making our software faster and more efficient.\n\nThis is made possible by engines such as Cloudflare Workers, Deno and Bun that are built to the [ECMAScript](https://tc39.es/) specification\u003c/a\u003e.\n\nIf you are interested in contributing please submit a PR or get in contact ^^\n\n### What does stateless mean?\n\nPeko apps are designed to boot from scratch at request time and disappear once the request is served. Therefore, storing data in memory between requests (stateful logic) is not reliable. Instead we should use stateless logic and store data within the client or external services.\n\nThis paradigm is often referred to as \"serverless\" or \"edge computing\" on cloud platforms, which offer code execution on shared server hardware (a.k.a JavaScript isolates). This is [much more resource efficient](https://developer.ibm.com/blogs/the-future-is-serverless/) than traditional server provisioning.\n\nBecause stateless apps can \"cold-start\" it is important to keep their codebases small. The preact demo app only imports Peko, Preact and Htm as dependencies and is very fast as a result - [https://peko.deno.dev](https://peko.deno.dev)!\n\n**Note:** In reality a single app instance will serve multiple requests, we just can't guarantee it. This is why caching is still an effective optimization strategy but in-memory user sessions are not an effective authentication strategy.\n\n## Credits:\n\nChick logo from [Twemoji](https://github.com/twitter/twemoji)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsejori%2Fpeko","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsejori%2Fpeko","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsejori%2Fpeko/lists"}