npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rip-lang/server

v1.4.28

Published

Bun-native content server: static sites, apps, HTTP proxy, and TCP/TLS passthrough

Readme

Rip Server - @rip-lang/server

Rip Server serves content: static sites, Rip apps, proxied HTTP services, and TCP/TLS services -- all from one Bun-native runtime.

Here, content means anything you want to make reachable over the network. That may be a static site, a small Rip app, an HTTP service behind a proxy, a raw TCP/TLS service, or a containerized tool you want to publish.

Rip Server replaces the usual app framework + process manager + reverse proxy stack with one runtime. Start with rip server for a single app. Add serve.rip when you want multi-host routing, shared apps, named proxy backends, reusable certs, or TCP/TLS passthrough. The job stays the same: take something you have and make it reachable safely, cleanly, and coherently.

Written entirely in Rip. Runs on Bun.

Serving Modes

  • Static file and website serving
  • Small app serving with Sinatra-style routing and read() validators
  • HTTP / WebSocket reverse proxy
  • Layer 4 TCP / TLS passthrough

Serving Guarantees

  • Managed worker pools with rolling restarts
  • HTTPS, ACME, certificate reuse, and SNI routing
  • Proxy health checks, retry behavior, and upstream timeouts
  • Shared-port HTTPS multiplexer
  • Atomic config reload with verification and rollback
  • Drain semantics, diagnostics, and control APIs
  • Composable serve.rip config

Quick Start

Install

bun add @rip-lang/server

Single app

import { get, read, start } from '@rip-lang/server'

get '/' ->
  'Hello from Rip Server!'

get '/users/:id' ->
  id = read 'id', 'id!'
  { user: { id, name: "User #{id}" } }

start()

Schema-validated routes + OpenAPI

A route registered with an input: schema validates the JSON body before the handler runs — a 400 with structured {field, error, message} issues goes out on failure, and the parsed (defaulted, coerced) value is @input. Every such route contributes to an auto-generated GET /openapi.json:

import { post, openapi } from '@rip-lang/server'

SignupInput = schema
  email!    email
  password! string, 8..100
  page?     ~integer

post '/signup', input: SignupInput, ->
  { welcome: @input.email }

openapi title: 'My API', version: '1.0.0'   # optional info block

The read() validator vocabulary doubles as schema coercers: importing @rip-lang/server registers every validator as a ~:name field type, so chart! ~:id, ssn? ~:ssn, amount? ~:money, dob? ~:date all normalize exactly like read 'x', 'id' does. registerValidator registers both surfaces.

Run it:

rip server

Add serve.rip

Add serve.rip next to your entry file when you want host routing, proxy backends, automatic TLS, or TCP/TLS passthrough.

serve.rip

serve.rip is the one config file. Top-level keys: ssl, hsts, acme, sites, apps, version, server.

export default
  ssl: '/path/to/ssl'

  sites:
    dev:  'local.example.com'
    prod: 'example.com'

  apps:
    web: 'dev prod'

ssl

Path to a directory of .crt/.key file pairs. Rip scans the directory, parses X509 SANs, and automatically matches certificates to hostnames.

sites

Named aliases mapping to hostnames:

sites:
  dev:    'local.medlabs.health'
  prod:   'medlabs.health'
  incus:  'incus.trusthealth.com'

apps

String-based app specs binding targets to sites:

apps:
  medlabs: 'dev prod'
  patient: '../patient dev prod'
  zion:    '/home/shreeve/www zion browse'
  incus:   'https://10.0.0.50:8443 incus'
  mysql:   'tcp://10.0.0.50:3306 db'

Target kind is inferred from prefix:

| Prefix | Kind | Behavior | |--------|------|----------| | ./, ../, / | local | Rip app (if index.rip exists) or static files | | (none) | local | Rip app in current directory or static files | | http://, https:// | HTTP proxy | Reverse proxy to URL | | tcp:// | TCP proxy | Layer 4 SNI passthrough |

Optional flags: browse (directory listing), spa (SPA fallback).

Path-scoped mounts

A site token can carry a mount path with site@/path, letting multiple apps share one hostname at different path prefixes:

sites:
  relay: 'relay.trusthealth.com'

apps:
  mqtt: 'http://127.0.0.1:9001 relay@/mqtt'
  repl: '/var/www/relay-repl relay@/repl browse'

wss://relay.trusthealth.com/mqtt hits the MQTT proxy; https://relay.trusthealth.com/repl serves the static directory. Omitting @/path keeps the old root-mount behavior. TCP proxies cannot be path-mounted.

Rules:

  • Each (site, mountPath) pair may be bound by exactly one app; / plus subpath mounts on the same site are allowed
  • TCP targets must include a port
  • HTTP proxy routes automatically enable WebSocket upgrade
  • If a TCP stream route shares the HTTPS port, Rip switches into multiplexer mode: matching SNI is passed through at Layer 4, everything else falls through to Rip's internal HTTPS server

server

Optional global settings:

server:
  hsts: true
  acme: ['example.com']
  timeouts:
    connectMs: 2000
    readMs: 30000

Clean Example

export default
  ssl: '/ssl'
  hsts: true
  acme: ['medlabs.health', 'trusthealth.com']

  sites:
    dev:      'local.medlabs.health'
    prod:     'medlabs.health'
    zion:     'dev.zionlabshare.com'
    incus:    'incus.trusthealth.com'
    db:       'db.trusthealth.com'

  apps:
    medlabs:  'dev prod'
    patient:  '../patient dev prod'
    zion:     '/home/shreeve/www zion browse'
    incus:    'https://10.0.0.50:8443 incus'
    mysql:    'tcp://10.0.0.50:3306 db'

Operator Runbook

Start with an explicit config file

rip server --file=./serve.rip

Validate config without serving

rip server --check-config
rip server --check-config --file=./serve.rip

Generate nginx.conf

rip server --nginx > /etc/nginx/nginx.conf && nginx -s reload

Generates a complete, production-hardened nginx config from your serve.rip.

Generate a Caddyfile

rip server --caddy > Caddyfile && caddy reload

Generates a Caddyfile from the same serve.rip. TCP/TLS passthrough routes emit a caddy-l4 layer4 block, which requires a custom build (xcaddy build --with github.com/mholt/caddy-l4).

Reload config safely

kill -HUP "$(cat /tmp/rip_myapp.pid)"

or:

curl --unix-socket /tmp/rip_myapp.ctl.sock -X POST http://localhost/reload

Binding to ports 80 and 443

Ports below 1024 require elevated privileges. If you see a permission error on startup, grant Bun the capability once:

sudo setcap cap_net_bind_service=+ep $(which bun)

This survives reboots but not Bun upgrades — re-run it after bun upgrade.

Run as a systemd service

A minimal unit for running rip-server under systemd. Save as /etc/systemd/system/rip-myapp.service:

[Unit]
Description=Rip Server (myapp)
After=network.target

[Service]
Type=simple
User=rip
WorkingDirectory=/srv/myapp
Environment=PATH=/home/rip/.bun/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/home/rip/.bun/bin/rip server -f /srv/myapp/serve.rip
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

Then:

sudo systemctl daemon-reload
sudo systemctl enable --now rip-myapp.service
journalctl -u rip-myapp -f

Notes:

  • WorkingDirectory should be the directory holding serve.rip — relative apps: targets resolve against it.
  • User=rip should be a dedicated unprivileged account. To bind ports 80/443 without running as root, grant Bun the capability once (see "Binding to ports 80 and 443" above).
  • rip server -r and rip server --restart apply config changes and rebuild workers without restarting the unit, so most edits don't need systemctl restart.

Diagnostics

curl http://localhost/diagnostics

The config section reports:

  • active serve.rip path
  • version
  • counts for apps, proxies, hosts, routes, and streams
  • active route descriptions
  • reload history and rollback details

Realtime

Rip Server includes built-in WebSocket pub/sub while your backend stays HTTP-oriented.

Security

Built-in protections include:

  • conflicting Content-Length + Transfer-Encoding rejection
  • multiple Host header rejection
  • oversized URL rejection
  • null-byte URL rejection
  • path traversal protection for static serving
  • CORS preflight policy unified with cors() middleware (no reflect-any-origin during OPTIONS short-circuit)
  • per-IP token-bucket rate limiter with LRU eviction, O(1) per check

Middleware to opt into:

  • csrf() — double-submit cookie + HMAC-bound token. Recommended for any form/cookie-authenticated app:

    use sessions secret: process.env.SESSION_SECRET, encrypt: true
    use csrf     secret: process.env.CSRF_SECRET

    Handlers receive the token as c.csrfToken for embedding in HTML forms or fetch headers (X-CSRF-Token).

  • sessions({encrypt: true}) — AES-256-GCM (AEAD) sealed cookie. Required when storing tokens, internal IDs, or any non-public data in session. The default signed mode is tamper-proof but the payload is client-readable.

Roadmap

  • Prometheus / OpenTelemetry metrics export
  • Inline edge handlers
  • HTTP response caching at the edge

License

MIT