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

react-saas

v1.0.3

Published

React.JS SaaS Architecture Kit

Readme

React.JS SaaS Architecture Kit

This document describes base architecture initially developed for SPA back-office and SaaS applications.

Rather than a module or a blueprint, it is a set of principles and conventions that streamline development of SPA applications.

Based on the ideas from frameworks such as react-admin and Firebase, however has key differences in its approach designed to address code reuse and vendor lock-in limitations in the existing frameworks.

In its implementation it builds upon same concepts that React effects and functional components use.

The Core Principles

  1. Separation of component look and business logic.
  2. Use of standard APIs such as REST CRUD and JWT Bearer instead of vendor-specific whenever possible.
  3. Convention over dependency. Shared code that contains component functionality should not depend on React or parts of this framework.

Scope

  • High-level Application Architecture
  • Authentication
  • CRUD data editing with back-end validation errors per field
  • Notification alerts (flash messages)

High-level Architecture

The top-level component is responsible for displaying application, routing and transitioning between the 'pages' and providing data access objects for underlying components.

See reference example app.js.

Routing functionality can be implemented with React Router, which parses the URL and launches respective components.

Authentication state is kept in the top-level component, set during initialisation or by child components that can change authentication state such as Login.

const [authenticated, setAuthenticated] = useState(null)

if (authenticated === null)
  authProvider('AUTH_CHECK').then(() => {
    setAuthenticated(true)
  }).catch(() => {
    setAuthenticated(false)
  })

return (<Login {...props} authProvider={authProvider} setAuthenticated={setAuthenticated} />)

Flux isn't used intentionally to limit the dependencies as the authentication state is the only 'global' state that needs to be passed this way

authProvider is dependent on the back-end authentication API and the goal is to implement reusable authProviders for each of the back-end authentication interfaces.

It is implemented according to the react-admin Authentication interface auth flow, however the JS interface is using strings instead of constants to define call type to remove dependency on a third-party module.

This is a perfect example of all three principles. authProvider code implements authentication with the back-end and session keeping in a function that doesn't define the UI and doesn't have hard dependencies or expectations for the Component that will include it. This function implements standard JWT Bearer interface when communicating with the back-end and also standardises its JS interface by a well documented convention. It also follows the Implementation Conventions that listed in the bottom of this document to store current credentials within the session.

The top-level component is also responsible for transforming CRUD data requests from JS components into HTTP requests to the back-end.

This reference application uses a simpleRestProvider, which is based on ra-data-simple-rest React Admin Data Provider, however the interface has been modified to remove data provider function dependency on react-admin.

httpClient is responsible for passing active credentials in HTTP requests, deserialising responses and converting bad responses to application-friendly errors.

It is injected into a data provider, which is then passed to child components that have no knowledge of the back-end or authentication:

const apiUrl = window.location.hostname == 'production_hostname' ? '/api' : 'http://localhost:3000'
const dataProvider = simpleRestProvider(apiUrl, httpClient)
//...
return ( <Profile dataProvider={dataProvider} /> )

As this application doesn't use Flux, authentication errors should be handled by components and either result in user-friendly error message (such as when attempting to retrieve object to which current user doesn't have access to) or redirecting to the Login page if session expires.

A back-end API often has additional logic for handling requests and a predefined format for error messages. An example of this is railsHttpClient, which is extended version of httpClient that automatically includes CSRF token available in Ruby on Rails views in requests, as well as uses a few predefined fields for errors that follow Ruby on Rails errors' and validations' structure.

TODO: Implement dismissable flash notifications stored in sessionStorage?

CRUD Editing and Forms

TODO

Implement AppEdit component that receives data provider and form component, responsible for rendering forms for a resource; architect to optionally support different components for display, edit and creation.

AppEdit, linking forms and data providers should be generic, whereas forms tend to have different implementations that are application specific.

Simple Forms

Trivial forms for editing an simple record with fields that can be represented as HTML5 input values can be implemented using uncontrolled components along with resourcePage responsible for serialisation and deserialisation of form fields.

This approach results in code significantly slimmer than controlled components but can only be used when all field values can be stored as DOM input values.

Example:

import { useResource, persistForm } from 'react-saas/resourcePage'
...
  const formId = 'enabledNotificationForm'
  let resourceName = 'api/properties/' + match.params.propertyId + '/enabled_notifications'
  useResource(formId, props.dataProvider, resourceName, { id: match.params.id })
  ...
  function handleSubmit(e) {
    e.preventDefault()
    persistForm(e.target, props.dataProvider, resourceName).then(() => {
      alert('added successfully')
    }).catch(err => {
      console.log(err) // TODO: create app-common exception handling
    })
  }

  return (
    <>
      <form id={formId} onSubmit={handleSubmit}>
        <input type="hidden" name="id" />
        Name: <input name="name" />
        <input type="submit" value="Save" className="btn btn-primary" />
      </form>
    </>
  )

Styling of Components

TODO: Global stylesheets vs styled components? Where each of the styles go?

Notifications

Use https://github.com/teodosii/react-notifications-component or create a similar API.

See https://alligator.io/react/react-notifications-component/ for customisation example.

Implementation Conventions

  • Store state in localStorage or sessionStorage rather than cookies
  • Use single quotes instead of double: https://bytearcher.com/articles/single-or-double-quotes-strings-javascript/