Cross-runtime JavaScript framework

Overview

Primate, a cross-runtime framework

Primate is a full-stack cross-runtime Javascript framework (Node.js and Deno). It relieves you of dealing with repetitive, error-prone tasks and lets you concentrate on writing effective, expressive code.

Highlights

  • Flexible HTTP routing, returning HTML, JSON or a custom handler
  • Secure by default with HTTPS, hash-verified scripts and a strong CSP
  • Built-in support for sessions with secure cookies
  • Input verification using data domains
  • Many different data store modules: In-Memory (built-in), File, JSON, MongoDB
  • Easy modelling of1:1, 1:n and n:m relationships
  • Minimally opinionated with sane, overrideable defaults
  • Supports both Node.js and Deno

Getting started

Prepare

Lay out your app

$ mkdir -p primate-app/{routes,components,ssl} && cd primate-app

Create a route for /

// routes/site.js

import {router, html} from "primate";

router.get("/", () => html`<site-index date="${new Date()}" />`);

Create a component for your route (in components/site-index.html)

Today's date is <span data-value="${date}"></span>.

Generate SSL key/certificate

openssl req -x509 -out ssl/default.crt -keyout ssl/default.key -newkey rsa:2048 -nodes -sha256 -batch

Add an entry file

// app.js

import {app} from "primate";
app.run();

Run on Node.js

Create a start script and enable ES modules (in package.json)

{
  "scripts": {
    "start": "node --experimental-json-modules app.js"
  },
  "type": "module"
}

Install Primate

$ npm install primate

Run app

$ npm start

Run on Deno

Create an import map file (import-map.json)

{
  "imports": {
    "runtime-compat": "https://deno.land/x/runtime_compat/exports.js",
    "primate": "https:/deno.land/x/primate/exports.js"
  }
}

Run app

deno run --import-map=import-map.json app.js

You will typically need the allow-read, allow-write and allow-net permissions.

Table of Contents

Routes

Create routes in the routes directory by importing and using the router singleton. You can group your routes across several files or keep them in one file.

router[get|post](pathname, request => ...)

Routes are tied to a pathname and execute their callback when the pathname is encountered.

// in routes/some-file.js
import {router, json} from "primate";

// on matching the exact pathname /, returns {"foo": "bar"} as JSON
router.get("/", () => json`${{"foo": "bar"}}`);

All routes must return a template function handler. See the section on handlers for common handlers.

The callback has one parameter, the request data.

The request object

The request contains the path, a / separated array of the pathname.

import {router, html} from "primate";

router.get("/site/login", request => json`${{"path": request.path}}`);
// accessing /site/login -> {"path":["site","login"]}

The HTTP request's body is available under request.payload.

Regular expressions in routes

All routes are treated as regular expressions.

import {router, json} from "primate";

router.get("/user/view/([0-9])+", request => json`${{"path": request.path}}`);
// accessing /user/view/1234 -> {"path":["site","login","1234"]}
// accessing /user/view/abcd -> error 404

router.alias(from, to)

To reuse certain parts of a pathname, you can define aliases which will be applied before matching.

import {router, json} from "primate";

router.alias("_id", "([0-9])+");

router.get("/user/view/_id", request => json`${{"path": request.path}}`);

router.get("/user/edit/_id", request => ...);

router.map(pathname, request => ...)

You can reuse functionality across the same path but different HTTP verbs. This function has the same signature as router[get|post].

import {router, html, redirect} from "primate";

router.alias("_id", "([0-9])+");

router.map("/user/edit/_id", request => {
  const user = {"name": "Donald"};
  // return original request and user
  return {...request, user};
});

router.get("/user/edit/_id", request => {
  // show user edit form
  return html`<user-edit user=${request.user} />`
});

router.post("/user/edit/_id", request => {
  const {user} = request;
  // verify form and save / show errors
  return await user.save() ? redirect`/users` : html`<user-edit user=${user} />`
});

Handlers

Handlers are tagged template functions usually associated with data.

html`<component-name attribute=${value} />`

Compiles and serves a component from the components directory and with the specified attributes and their values. Returns an HTTP 200 response with the text/html content type.

json`${{data}}`

Serves JSON data. Returns an HTTP 200 response with the application/json content type.

redirect`${url}`

Redirects to url. Returns an HTTP 302 response.

Components

Create HTML components in the components directory. Use data-attributes to show data within your component.

// in routes/user.js
import {router, html, redirect} from "primate";

router.alias("_id", "([0-9])+");

router.map("/user/edit/_id", request => {
  const user = {"name": "Donald", "email": "[email protected]"};
  // return original request and user
  return {...request, user};
});

router.get("/user/edit/_id", request => {
  // show user edit form
  return html`<user-edit user=${request.user} />`
});

router.post("/user/edit/_id", request => {
  const {user, payload} = request;
  // verify form and save / show errors
  // this assumes `user` has a method `save` to verify data
  return await user.save(payload) ? redirect`/users` : html`<user-edit user=${user} />`
});
<!-- components/edit-user.html -->
<form method="post">
  <h1>Edit user</h1>
  <p>
    <input name="user.name" data-value="${user.name}"></textarea>
  </p>
  <p>
    <input name="user.email" data-value="${user.email}"></textarea>
  </p>
  <input type="submit" value="Save user" />
</form>

Grouping objects with data-for

You can use the special attribute data-for to group objects.

<!-- components/edit-user.html -->
<form data-for="${user}" method="post">
  <h1>Edit user</h1>
  <p>
    <input name="name" data-value="${name}" />
  </p>
  <p>
    <input name="email" data-value="${email}" />
  </p>
  <input type="submit" value="Save user" />
</form>

Expanding arrays

data-for can also be used to expand arrays.

// in routes/user.js
import {router, html} from "primate";

router.get("/users", request => {
  const users = [
   {"name": "Donald", "email": "[email protected]"},
   {"name": "Ryan", "email": "[email protected]"},
  ];
  return html`<user-index users=${users} />`;
});
<!-- in components/user-index.html -->
<div data-for="${users}">
  User <span data-value="${name}"></span>
  Email <span data-value="${email}"></span>
</div>

Resources

License

BSD-3-Clause

You might also like...

Zero runtime type-safe CSS in the same file as components

macaron comptime-css is now called macaron! macaron is a zero-runtime and type-safe CSS-in-JS library made with performance in mind Powered by vanilla

Jan 4, 2023

This document introduces an early implementation of the Node-RED runtime that runs on resource-constrained microcontrollers (MCUs).

Node-RED MCU Edition Copyright 2022, Moddable Tech, Inc. All rights reserved. Peter Hoddie Updated June 25, 2022 Introduction This document introduces

Jan 3, 2023

Windmill: Open-source platform and runtime to turn any scripts into internal apps, integrations and workflows

Windmill: Open-source platform and runtime to turn any scripts into internal apps, integrations and workflows

. Open-source and self-hostable alternative to Airplane, Pipedream, Superblocks and a simplified Temporal with autogenerated UIs to trigger flows and

Jan 4, 2023

Storybook Addon Root Attributes to switch html, body or some element attributes (multiple) at runtime for you story

Storybook Addon Root Attributes to switch html, body or some element attributes (multiple) at runtime for you story

Storybook Addon Root Attributes What is this This project was inspired by le0pard/storybook-addon-root-attribute The existing library received only on

Sep 6, 2022

On-demand runtime webpack compilations over HTTP.

webpack-http-server On-demand runtime webpack compilations over HTTP. Overview This package is, effectively, an Express server that exposes webpack co

Oct 20, 2022

Modern Cross Browser Testing in JavaScript using Playwright

 Modern Cross Browser Testing in JavaScript using Playwright

Modern Cross Browser Testing in JavaScript using Playwright This repository contains the example code for the Modern Cross Browser Testing in JavaScri

Oct 3, 2022

A lightweight cross browser javascript scrollbar.

tinyscrollbar ** HELP MAINTAINER NEEDED!! ** Environments in which to use tinyscrollbar Browser support differs between the jQuery plugin and the plai

Nov 9, 2022

Simple wrapper for cross-browser usage of the JavaScript Fullscreen API

screenfull Simple wrapper for cross-browser usage of the JavaScript Fullscreen API, which lets you bring the page or any element into fullscreen. Smoo

Dec 30, 2022

🔨 Cross-browser JavaScript library to disable scrolling page

🔨 Cross-browser JavaScript library to disable scrolling page

scroll-lock Cross-browser JavaScript library to disable scrolling page Live demo | README на русском New features 2.0 More advanced touch event handli

Dec 17, 2022
Comments
  • [html] Support nesting components

    [html] Support nesting components

    It's currently impossible to nest components, i.e.

    import {router, html} from "primate";
    
    router.get("/", html`<parent-component />`);
    
    <!-- in parent-component -->
    <child-component />
    

    Doesn't expand <child-component>.

    Additionally, there is the issue of passing fixed strings vs. values to child components. In JS this is not a problem, as we use template literals. But in HTML there is no distinction between strings and values.

    A possible solution is to adhere to data attributes and use them to pass data, and otherwise treat everything as literal, e.g.

    import {router, html} from "primate";
    
    router.get("/", html`<parent-component user=${{"name": "Donald"}} />`);
    
    <!-- in parent-component -->
    <child-component label="user" data-user="user" />
    
    <!-- in child-component -->
    <span data-value="label"></span>
    <span data-value="user"></span>
    

    This, however, might seem inconsistent with the JS side and would result in conflicts if the data attribute and the normal attribute are called the same.

    An alternative would be to force using template literals for actual data, and treat everything else as text in components.

    <!-- in parent-component -->
    <child-component label="user" user="${user}" />
    
    <!-- in child-component -->
    <span data-value="label"></span>
    <span data-value="user"></span>
    

    This raises the question whether enforcing the template literal syntax should be limited to component tags or to all HTML code within components, to reach uniformity at the cost of being more verbose.

    improvement 
    opened by terrablue 2
  • Support slots in web components

    Support slots in web components

    SSR web components should support slots.

    Parent component:

    <child-component attribute="${value"}>
      <div>Some content</div>   
    </child-component>
    

    Child component:

    <h1>Child component</h1>
    <slot />
    <div>More content</div>
    
    improvement 
    opened by terrablue 1
  • [routing] Map named capture groups to `request.path.<name>`

    [routing] Map named capture groups to `request.path.`

    Currently, it is possible to access the parts of a pathname via the request.path array:

    router.get(`/namespace/action/([a-z0-9]*)`, request => {
       const [,, _id] = request.path;
    });
    

    It would be more convenient to populate request.path with the results of matching named capture groups, such as that the name of the capture group is available as a property on request.path.

    router.get(`/namespace/action/(?<_id>[a-z0-9]*)`, request => {
       // still works
       const [,, _id] = request.path;
       // but also available as
       const {"_id": also_id} = request.path;
    });
    
    improvement 
    opened by terrablue 1
  • Return data instead of handler from routes

    Return data instead of handler from routes

    It could be desirable to provide a short-syntax variant in routes by returning an object instead of a handler call. Such cases should be identified automatically.

    Right now in order to return JSON you need to explicitly use the json handler.

    router.get("/", () => json`${{foo: "bar"}} />`);
    

    It's more straightforward to simply return the object and have Primate guess a handler.

    router.get("/", () => ({foo: "bar"}) />`);
    

    Cases to consider (assuming obj) and appropriate handlers

    • isJSONSerializeable(obj) -> json
    • Object.getPrototypeOf(obj) === ReadableStream.prototype -> stream

    To consider:

    • Should be there a default? E.g. text/plain
    • Or should there be any error thrown if no appropriate handler can be found
    improvement help wanted 
    opened by terrablue 0
Releases(0.8.1)
⚗️Nitro provides a powerful toolchain and a runtime framework from the UnJS ecosystem to build and deploy any JavaScript server, anywhere

⚗️Nitro provides a powerful toolchain and a runtime framework from the UnJS ecosystem to build and deploy any JavaScript server, anywhere

unjs 1.3k Jan 5, 2023
⚡️ A fast, minimalist web framework for the Bun JavaScript runtime

?? Bao.js A fast, minimalist web framework for the Bun JavaScript runtime. ⚡️ Bao.js is 3.7x faster than Express.js and has similar syntax for an easy

Matt Reid 746 Dec 26, 2022
Bun-Bakery is a web framework for Bun. It uses a file based router in style like svelte-kit. No need to define routes during runtime.

Bun Bakery Bun-Bakery is a web framework for Bun. It uses a file based router in style like svelte-kit. No need to define routes during runtime. Quick

Dennis Dudek 44 Dec 6, 2022
Runtime type checking in pure javascript.

Install npm install function-schema Usage import { signature } from 'function-schema'; const myFunction = signature(...ParamTypeChecks)(ReturnValueCh

Jeysson Guevara 3 May 30, 2022
TypeScript type definitions for Bun's JavaScript runtime APIs

Bun TypeScript type definitions These are the type definitions for Bun's JavaScript runtime APIs. Installation Install the bun-types npm package: # ya

Oven 73 Dec 16, 2022
io-ts Typed Event Bus for the runtime of your Node.js application. A core for any event-driven architecture based app.

Typed Event Bus Based on io-ts types, this bus provides a handy interface to publish and consume events in the current runtime of the Node.js process.

Konstantin Knyazev 3 May 23, 2022
Runtime object parsing and validation with static TypeScript typing.

TypeParse Runtime object transformation, parsing and validation with inferred static TypeScript typing. Install Using npm npm install typeparse Using

Kenneth Herrera 4 May 5, 2022
Simple, lightweight at-runtime type checking functions, with full TypeScript support

pheno Simple, lightweight at-runtime type checking functions, with full TypeScript support Features Full TypeScript integration: TypeScript understand

Lily Scott 127 Sep 5, 2022
🦆 lightning fast duckdb bindings for bun runtime

@evan/duckdb lightning fast duckdb bindings for bun runtime Install bun add @evan/duckdb Features ?? batteries included ?? jit optimized bindings ?? 4

evan 29 Oct 20, 2022
Example code for MFE routing, shared state, build-time & runtime deploy video

Turborepo starter with pnpm This is an official starter turborepo. What's inside? This turborepo uses pnpm as a packages manager. It includes the foll

Jack Herrington 42 Nov 2, 2022