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

@arnelirobles/express-rnx

v1.0.0

Published

Express middleware for rnxJS reactive components integration

Downloads

100

Readme

@arnelirobles/express-rnx

Express middleware and view helpers for integrating rnxJS reactive components into Express applications.

License: MPL-2.0 Node Version

Overview

express-rnx provides Express middleware that makes it easy to:

  • Include rnxJS library and stylesheets in your views
  • Create reactive state from server data
  • Render rnxJS components with template variables
  • Initialize rnxJS plugins (router, toast, storage)
  • Bind server data to reactive components

Installation

npm

npm install @arnelirobles/express-rnx

yarn

yarn add @arnelirobles/express-rnx

Quick Start

1. Setup Express Middleware

const express = require('express');
const rnxMiddleware = require('@arnelirobles/express-rnx');

const app = express();

// Add rnx middleware
app.use(rnxMiddleware({
  cdn: true,
  theme: 'bootstrap'
}));

// Set view engine
app.set('view engine', 'ejs');
app.set('views', './views');

// Routes
app.get('/', (req, res) => {
  res.render('index');
});

app.listen(3000);

2. Use in EJS Templates

<head>
  <%- rnx.scripts() %>
</head>

<body>
  <%
    const userData = {
      name: 'John Doe',
      email: '[email protected]'
    };
  %>

  <%- rnx.state(userData, 'user') %>

  <p>Welcome, <span data-bind="user.name"></span>!</p>

  <%- rnx.component('Button', { variant: 'primary', label: 'Save' }) %>
</body>

3. Available Methods

// In your Express route handlers
app.get('/profile', (req, res) => {
  const user = req.user; // Your user object

  res.render('profile', {
    user: user,
    // res.locals.rnx helpers are automatically available
  });
});

Middleware Configuration

Configure the middleware when initializing:

app.use(rnxMiddleware({
  // Include rnxJS scripts (default: true)
  cdn: true,

  // Theme variant (default: 'bootstrap')
  // Options: 'bootstrap', 'm3', 'plugins', or null
  theme: 'bootstrap',

  // Storage configuration
  storagePrefix: 'myapp_',

  // Router configuration
  routerMode: 'hash',

  // Toast configuration
  toastPosition: 'top-right',
  toastDuration: 3000,
  toastMax: 5
}));

View Helpers Reference

rnx.scripts()

Include rnxJS library and stylesheets.

Syntax:

rnx.scripts(cdn, theme)

Parameters:

  • cdn (boolean, default: true) - Use CDN for resources
  • theme (string, default: 'bootstrap') - Theme variant

Example:

<head>
  <%- rnx.scripts(true, 'm3') %>
</head>

rnx.state()

Create reactive state from server data.

Syntax:

rnx.state(data, varName)

Parameters:

  • data - JavaScript object or array to serialize
  • varName (string, default: 'state') - Global state variable name

Example:

<%
  const appData = {
    user: {
      name: req.user.name,
      email: req.user.email,
      role: req.user.role
    },
    notifications: notifications,
    routes: {
      home: '/',
      profile: '/profile',
      settings: '/settings'
    }
  };
%>

<%- rnx.state(appData, 'appState') %>

<span data-bind="appState.user.name"></span>
<span data-bind="appState.user.email"></span>

rnx.component()

Render rnxJS components.

Syntax:

rnx.component(name, props)

Parameters:

  • name (string) - Component name
  • props (object, optional) - Component properties

Example:

<%- rnx.component('Button', { variant: 'primary', label: 'Save' }) %>

<%- rnx.component('Input', {
  type: 'email',
  placeholder: '[email protected]',
  data_bind: 'state.email'
}) %>

<%- rnx.component('DataTable', {
  data: 'state.users',
  columns: 'state.columns',
  sortable: true,
  pageable: true
}) %>

rnx.plugin()

Initialize rnxJS plugins.

Syntax:

rnx.plugin(name, options)

Parameters:

  • name (string) - Plugin name ('router', 'toast', 'storage')
  • options (object, optional) - Plugin configuration

Example:

<%- rnx.plugin('router', { mode: 'hash', default_route: '/' }) %>

<%- rnx.plugin('toast', {
  position: 'top-right',
  duration: 3000,
  max_toasts: 5
}) %>

<%- rnx.plugin('storage', {
  prefix: 'myapp_',
  storage: 'localStorage'
}) %>

rnx.dataBind()

Create a data-bind attribute.

Syntax:

rnx.dataBind(path)

Example:

<span <%- rnx.dataBind('user.name') %>></span>
<!-- Output: <span data-bind="user.name"></span> -->

rnx.dataRule()

Create a validation rule attribute.

Syntax:

rnx.dataRule(rules)

Example:

<input <%- rnx.dataRule('required|email') %> />
<!-- Output: <input data-rule="required|email" /> -->

<input <%- rnx.dataRule(['required', 'email', 'max:100']) %> />

Complete Example

app.js

const express = require('express');
const rnxMiddleware = require('@arnelirobles/express-rnx');

const app = express();

// Middleware
app.use(express.static('public'));
app.use(rnxMiddleware({
  cdn: true,
  theme: 'bootstrap',
  toastPosition: 'top-right',
  toastDuration: 3000
}));

app.set('view engine', 'ejs');
app.set('views', './views');

// Mock user
const mockUser = {
  id: 1,
  name: 'John Doe',
  email: '[email protected]',
  role: 'admin'
};

const mockNotifications = [
  { id: 1, title: 'Welcome', message: 'Welcome to the app!', type: 'info' },
  { id: 2, title: 'Success', message: 'Your profile was updated', type: 'success' }
];

// Routes
app.get('/', (req, res) => {
  res.render('index', {
    user: mockUser,
    notifications: mockNotifications
  });
});

app.get('/profile', (req, res) => {
  res.render('profile', {
    user: mockUser
  });
});

app.get('/components', (req, res) => {
  const users = [
    { id: 1, name: 'Alice', email: '[email protected]', status: 'active' },
    { id: 2, name: 'Bob', email: '[email protected]', status: 'inactive' },
    { id: 3, name: 'Charlie', email: '[email protected]', status: 'active' }
  ];

  const columns = [
    { key: 'id', label: 'ID' },
    { key: 'name', label: 'Name' },
    { key: 'email', label: 'Email' },
    { key: 'status', label: 'Status' }
  ];

  res.render('components', {
    users: users,
    columns: columns
  });
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

views/layout.ejs

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>rnxJS Express Example</title>
  <%- rnx.scripts(true, 'bootstrap') %>
</head>
<body>
  <nav class="navbar navbar-expand-lg navbar-light bg-light">
    <div class="container-fluid">
      <a class="navbar-brand" href="/">My App</a>
      <div class="collapse navbar-collapse">
        <ul class="navbar-nav ms-auto">
          <li class="nav-item">
            <a class="nav-link" href="/">Home</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="/profile">Profile</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="/components">Components</a>
          </li>
        </ul>
      </div>
    </div>
  </nav>

  <main class="container mt-5">
    <%- body %>
  </main>

  <%- rnx.plugin('toast', { position: 'top-right' }) %>
</body>
</html>

views/index.ejs

<%
  const appState = {
    user: user,
    notifications: notifications,
    page: 'home'
  };
%>

<%- rnx.state(appState, 'appState') %>

<h1>Welcome to rnxJS!</h1>
<p>Hello, <span data-bind="appState.user.name"></span>!</p>

<div class="row mt-5">
  <div class="col-md-6">
    <h2>Recent Notifications</h2>
    <% notifications.forEach(notif => { %>
      <%- rnx.component('Card', {
        title: notif.title,
        content: notif.message,
        type: notif.type
      }) %>
    <% }); %>
  </div>

  <div class="col-md-6">
    <h2>Actions</h2>
    <%- rnx.component('Button', {
      variant: 'primary',
      label: 'View Profile',
      onclick: 'window.location.href = "/profile"'
    }) %>
    <br><br>
    <%- rnx.component('Button', {
      variant: 'secondary',
      label: 'View Components',
      onclick: 'window.location.href = "/components"'
    }) %>
  </div>
</div>

views/profile.ejs

<%
  const profileState = {
    user: user
  };
%>

<%- rnx.state(profileState, 'profileState') %>
<%- rnx.plugin('toast', { position: 'top-right' }) %>

<h1>User Profile</h1>

<div class="profile-card">
  <h2 data-bind="profileState.user.name"></h2>
  <p><strong>Email:</strong> <span data-bind="profileState.user.email"></span></p>
  <p><strong>Role:</strong> <span data-bind="profileState.user.role"></span></p>

  <%- rnx.component('Button', {
    variant: 'primary',
    label: 'Edit Profile',
    onclick: 'window.rnx.toast.info("Opening edit dialog")'
  }) %>

  <%- rnx.component('Button', {
    variant: 'danger',
    label: 'Logout',
    onclick: 'window.rnx.toast.warning("Logging out...")'
  }) %>
</div>

Security Considerations

HTML Escaping

All component props and attributes are automatically HTML-escaped to prevent XSS:

<%- rnx.component('Button', { label: userInput }) %>
<!-- userInput is escaped automatically -->

Data Binding Expressions

String values starting with state., {, or [ are preserved for data binding:

<%- rnx.component('Div', { content: 'state.message' }) %>
<!-- Preserves as: content="state.message" -->

<%- rnx.component('Div', { title: 'Hello' }) %>
<!-- Escapes and quotes: title="Hello" -->

Testing

Tests are included. Run with:

npm test

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

MPL-2.0 - See LICENSE for details.

Support

Changelog

1.0.0 (2024)

  • Initial release
  • Express middleware with res.locals.rnx helpers
  • View helpers: scripts, state, component, plugin
  • Attribute helpers: dataBind, dataRule
  • Bootstrap 5.3+ support
  • Plugin integration (router, toast, storage)
  • Full XSS prevention and HTML escaping
  • Support for EJS, Pug, and other template engines
  • Configuration options