- 0.1.0Latest
- 0.1.1
- 0.1.2
- 0.1.3
- 0.2.0
- 0.2.1
- 0.2.2
- 0.3.0
- 0.4.0
- 0.4.1
- 0.4.2
- 0.4.3
- 0.5.0
- 0.5.1
- 0.5.2
- 0.5.3
- 0.5.4
- 0.6.0
- 0.6.1
- 1.0.0
- 1.1.0
- 1.2.0
- 1.2.1
- 1.2.2
- 1.2.3
- 1.2.4
- 1.2.5
- 1.3.0
- 1.3.1
- 1.3.2
- 2.2.0
- 2.1.0
- 2.0.0
- 2.0.0-beta
- 1.9.0
- 1.8.3
- 1.8.2
- 1.8.1
- 1.8.0
- 1.7.5
- 1.7.4
- 1.7.3
- 1.7.2
- 1.7.1
- 1.7.0
- 1.6.0
- 1.5.3
- 1.5.2
- 1.5.1
- 1.5.0
- 1.4.1
- 1.4.0
- 1.3.5
- 1.3.4
- 1.3.3
- v.1.3.2
- v1.3.1
- v1.3.0
- v1.2.5
- v1.2.4
- v1.2.3
- v1.2.2
- v1.2.1
- v1.2.0
- v1.1.0
- v1.0.0
- v0.6.1
- v0.6.0
- v0.5.4
- v0.5.3
- v0.5.2
- v0.5.1
- v0.5
- v0.4.3
- v0.4.2
- v0.4.1
- v0.4.0
- v0.3.0
- v0.2.2
- v0.2.1
- v0.2.0
- v0.1.3
- v0.1.2
- v0.1.1
- v0.1.0
Types Routing Request handling Response caching
Stateless HTTP(S) apps that are:
Featherweight - Functional API built with ECMAScript + Deno std library
Production-ready - High test coverage + stable APIs + server profiling
Community-driven - Compatible with Deno, Bun & Node + contributions encouraged
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 "https://deno.land/x/peko/mod.ts";
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))
Types
Router
The main class of Peko, provides handle
method to generate Response
from Request
via configured routes and middleware.
Route
Objects with path
, method
, middleware
, and handler
properties. Requests are matched to a regex generated from the given path. Dynamic parameters are supported in the /users/:userid
syntax.
RequestContext
An object containing url
, params
and state
properties that is provided to all middleware and handler functions associated to a router or matched route.
Middleware
Functions that receives a RequestContext and a next fcn. Should update ctx.state
, perform side-effects or return a response.
Handler
The final request handling function on a Route
. Must generate and return a response using the provided request context.
Request handling
Each route must have a handler
function that generates a Response. Routes can also middleware
.
ctx: RequestContext
Upon receiving a request a Router
will construct a RequestContext and cascade it through global middleware, route middleware and the route handler.
Middleware are invoked in the order they are added. If a response is returned no subsequent middleware/handler will execute.
Peko comes with a library of utilities, middleware and handlers for common route use-cases, such as:
- server-side rendering apps to HTML
- streaming server-sent events
- hashing and JWT authentication
- logging requests
- caching responses
See handlers
, middleware
or utils
for source, or dive into examples
for demo implementations.
next: () => Promise<Response>
The second argument to any middleware is the next
fcn. This returns a promise that resolves to the first response returned by any subsequent middleware/handler. This is useful for error-handling as well as post-response operations such as editing headers or logging. See the below snippet or middleware/logger.ts
for examples.
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. Peko provides a ResponseCache
utility for this with configurable item lifetime. The cacher
middleware wraps it and 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!
import { Router, CacheItem, cacher } from "https://deno.land/x/peko/mod.ts"
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())
})
And that’s it! Check out the API docs for deeper info. Otherwise happy coding 🤓
App showcase
PR to add your project 🙌
iiisun.art - artistic storefront
- Stack: React, ImageMagick_deno
- Features: CI resized-image precaching, Gelato & Stripe integrations, Parallax CSS
- source
shineponics.org - smart-farming PaaS
- Stack: React, Google Cloud Platform
- Features: Google Sheet analytics, GCP email list, Markdown rendering
- source
peko-auth.deno.dev - basic authentication demo
- Stack: HTML5
- Features: JWT-based auth
- source
Note: lit-html and es6-string-css VS Code extensions recommended.
Deployment
- Deno Deploy (fork and deploy the examples if you fancy 💖)
- Docker (coming soon…)
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. This is much more resource efficient than traditional server provisioning.
Because our stateless apps cold-start it is important to keep their codebases small. The preact demo app only imports Peko and Preact as external 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.
Motivations
The modern JavaScript edge rocks because the client-server gap practically disappears. We can share modules across the client and cloud. If we want TS source we can emit JS. 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 Deno that are built to the ECMAScript specification (support for URL module imports is the secret sauce). UI libraries like Preact combined with htm offer lightning fast client-side hydration with a browser-friendly markup syntax. Deno also has native TypeScript support, a rich runtime API and loads of community tools for your back-end needs.
If you are interested in contributing please submit a PR or get in contact ^^