Peko
Fast - Regex route matching and caching with Node, Deno, Bun & Cloudflare Worker support šļøšØ
Featherweight - Functional API built with Web Standards & zero dependencies šŖ¶
Feature-rich - Static files, auth, server-sent events & server profiling utils š¤¹
Ā Overview Ā Ā Getting Started Ā Ā Recipes Ā Ā Motivations Ā
npm i @sejori/peko
Overview
Routes and middleware are added to a Router
instance with .use
, .addRoute
or .get/post/put/delete
.
The router is then used with your web server of choice, e.g. Deno.serve
or Bun.serve
.
import * as Peko from "@sejori/peko"; // or https://deno.land/x/peko/mod.ts in Deno
const router = new Peko.Router();
router.use(Peko.logger(console.log));
router.get("/shorthand-route", () => new Response("Hello world!"));
router.post(
"/shorthand-route-ext",
async (ctx, next) => {
await next();
console.log(ctx.request.headers);
},
(req) => new Response(req.body)
);
router.addRoute({
path: "/object-route",
middleware: async (ctx, next) => {
await next();
console.log(ctx.request.headers);
}, // can also be array of middleware
handler: () => new Response("Hello world!"),
});
router.addRoutes([
/* array of route objects */
]);
Deno.serve((req) => router.handle(req));
Getting started
git clone https://github.com/sejori/peko
&&cd peko
Check example
directory to see implementations of:
- server-side rendering Preact to HTML
- streaming server-sent events to web client
- logging requests
- caching responses
- JWT authentication middleware
Deno Live Deploy
- Process 1:
deno task dev:build
- Process 2:
deno task dev:deno
Cloudflare Workers Live Deploy
npm i
- Process 1:
npm run dev:build
- Process 2:
npm run dev:wrangler
Bun: Bun is currently not deployed but it is profiled against Deno, check the GitHub actions to see results.
bun install
- Process 1:
bun dev:build
- Process 2:
bun dev:bun
Types
Router
The main class/entrypoint of Peko.
The handle
method generates a Response
from a Request
argument via configured routes and middleware.
Route
Routes 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
).
Dynamic path parameters are supported in the /users/:userid
syntax.
RequestContext
An object containing request data that is passed into middleware and handlers in the Request
process lifecycle.
The state
property is an object designed to transfer information between middleware/handlers.
Middleware
Functions that receive RequestContext
and next
. They are designed to:
- Return a
Response
and end theRequest
processing lifecycle (e.g. returning a401
) - Call
await next()
to access the final response (e.g. logging) - Edit the contextās
state
(e.g. rendering geolocation to HTML)
Handler
The final request handling function on a Route
, receives RequestContext
argument.
Must return/resolve to a Response
(e.g. Render HTML or return JSON payload).
Recipes
Error handling
If 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:
router.use(async (_, next) => {
const response = await next();
if (!response)
return new Response("Would you look at that? Nothing's here!", {
status: 404,
});
});
router.use(async (_, next) => {
try {
await next();
} catch (e) {
console.log(e);
return new Response("Oh no! An error occured :(", { status: 500 });
}
});
Response Caching
In 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.
router.addRoute(
"/get-time",
Peko.cacher({ itemLifetime: 5000 }),
() => new Response(Date.now())
);
The 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).
import { Router, CacheItem, cacher } from "@sejori/peko";
const router = new Router();
const itemMap: Map<string, CacheItem> = new Map();
router.addRoute("/get-time", {
middleware: cacher({
itemLifetime: 5000,
store: {
get: (key) => itemMap.get(key),
set: (key, value) => itemMap.set(key, value),
delete: (key) => itemMap.delete(key),
},
}),
handler: () => new Response(Date.now()),
});
Showcase
PR to add your project š
shineon.systems
- Stack: React, Cloudflare Workers, KV and R2
- Features: KV cache, R2 email list, Markdown rendering
- source
thesebsite.deno.dev
- Stack: HTML5
- Features: UI TS scripts transpiled to JS and cached for browser
- source
Note: lit-html and es6-string-css VS Code extensions recommended.
Motivations
Apps on the edge
The modern JavaScript edge rocks because the client-server gap practically disappears. We can share modules across the client and cloud.
This eliminates much of the bloat in traditional JS server-side systems, increasing project simplicity while making our software faster and more efficient.
This is made possible by engines such as Cloudflare Workers, Deno and Bun that are built to the ECMAScript specification.
If you are interested in contributing please submit a PR or get in contact ^^
What does stateless mean?
Peko 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.
This 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 than traditional server provisioning.
Because 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!
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.
Credits:
Chick logo from Twemoji