The fastest way to build your minimal viable product. Using React, MobX, Koa and Postgres. Under active development.
JavaScript
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
packages
.babelrc
.editorconfig
.eslintrc.js
.gitignore
.travis.yml
LICENSE
README.md
jest.json
lerna.json
package-lock.json
package.json

README.md

The fastest way to build your minimal viable product.

Build Status Coverage Status License Badge



Node on Fire Logo

The fastest way to build your minimal viable product. Using React, MobX, Koa and Postgres. Under active development.

Features

  • Universal JavaScript.
  • React front-end, Node.js back-end.
  • Zero-configuration, similar to create-react-app.
  • Automatic HTTP endpoints.
  • Schema-aware query builder.
  • Automatic database migrations based on changes.

nodeonfire.org


import {
    React,
    Service,
    startup
    Store,
} from 'fire';

class MyService extends Service {
    loadItems() {
        return [
            `Item #1`,
            `Item #2`,
            `Item #3`,
        ];
    }
}

@inject(MyService, `myService`)
class MyStore extends Store {
    items = [];

    loadItems() {
        const response = await this.myService.loadItems();
        this.items = await response.json();
    }
}

@inject(MyStore, `myStore`)
class App extends React.Component {
    componentDidMount() {
        this.props.myStore.loadItems();
    }

    render() {
        const {
            items,
        } = this.props.myStore;

        return (
            <div>
                 <h1>Hello, world!</h1>

                 {items.map((item) => <p>{item}</p>)}
             </div>
        );
    }
}

startup(App);

Node on Fire reduces boilerplate and improves developer productivity significantly. The idea is to give you everything you need until your first 100,000 users.

API

Service

Creates a back-end service which is invokable on the client and server-side. On the client-side, the service is transparently invoked over HTTP.

By default, because of security concerns, no service method is accessible from the client. You have to allow or deny clients access, see @allow and @deny.

On the client, the service methods are stripped from being included in the source by babel-plugin-transform-strip-classes.

Simple example.

class MyService extends Service {
    @allow(() => () => true)
    test() {
        return 'Hello, world!';
    }
}

@inject(MyService, `myService`)
class MyComponent extends React.Component {
    async componentDidMount() {
        const test = await this.props.myService.test();

        // test = `Hello, world!`
    }
}

@inject(Class, propertyName)

Injects an instance of Class in the target. In case the target is React.Component, the instance is added to the props, otherwise it's added as a property of the instance. The created instance is re-used when injecting multiple times.

Argument Type Description
Class Class The Class of the instance to inject.
propertyName String The name of the property the instance is injected to.
class Foo {
    test() {
        return `Hello, world!`;
    }
}

@inject(Foo, `foo`)
class Bar {
    //
}

const bar = new Bar();
const test = bar.foo.test();

// test = `Hello, world!`

Store

Creates a basic MobX store: all properties are set as observables, getter functions as computed, and functions as actions. This is a convenience decorator. Alternatively, you can call @action, @observable, @computed et al are also available.

@style(classes)

Adds a JSS style to a React.Component.

Argument Type Description
classes Object/Function A classes object with key-values, or a function which takes a theme object and returns classes.

Simple example.

@style({
    button: {
        backgroundColor: `#f0f`,
    },
})
class MyComponent extends React.Component {
    render() {
        const {
            classes,
        } = this.props;

        return (
            <button className={classes.button}>Click me</button>
        );
    }
}

Theme example. You can also use a theme, see setTheme.

setStyle({
    magenta: `#f0f`,
});

@style((theme) => ({
    button: {
        backgroundColor: theme.magenta,
    },
}))
class MyComponent extends React.Component {
    render() {
        const {
            classes,
        } = this.props;

        return (
            <button className={classes.button}>Click me, too</button>
        );
    }
}

setTheme(theme)

Sets the theme which is accessible in @style.

Argument Type Description
theme Object A theme object with key-values.

Example. Set a theme and use it in @style.

setStyle({
    magenta: `#f0f`,
});

@style((theme) => ({
    button: {
        backgroundColor: theme.magenta,
    },
}))
class MyComponent extends React.Component {
    render() {
        const {
            classes,
        } = this.props;

        return (
            <button className={classes.button}>Click me, too</button>
        );
    }
}

configureWebpack(type, reducer)

Extends the webpack config for the given type.

Argument Type Description
type String Either client or server.
reducer Function A function which takes a config and returns a new config. You may mutate the existing config, just make sure you always return the config.

Example

configureWebpack(`client`, (config) => {
    config.devtool = `source-map`;
    return config;
});

addShims(type, moduleNames)

Configures which 3rd party modules should be shimmed. If type is set to client the modules will be shimmed in the client bundle. If type is set to server the modules will be shimmed on the server side.

Some modules are only designed to work on either the client or the server side, but, in Node on Fire, all your code plus it's modules run on both the client and the server side. To ignore a module on a specific side, you can shim the module.

The following modules are shimmed in the client bundle by default:

  • fsevents
  • koa
  • webpack
  • koa-webpack
  • dns
Argument Type Description
type String The type, or side, on which to shim. Either client or server.
moduleNames String[] The module names to shim.

Example

addShims(`client`, [`foo`, `bar`]);

@observer

Sets the target React.Component as a MobX observer. This happens automatically when you inject a store into the component.

@observer
class MyComponent extends React.Component {
    //
}

@allow(accessControlFunc)

Adds an allow rule to the service method. The user is allowed to invoke the service method if the allow rule return true.

accessControlFunc is invoked with the authentication token's payload and should return true if the user is allowed to invoke the method, and should return false if the user is not allowed to invoke the method. accessControlFunc may also return a function which takes the methods arguments and should, again, return true if the user is allowed or false if the user is not allowed to invoke the method.

See also @deny(accessControlFunc).

Argument Type Description
accessControlFunc Function(payload) The function which is invoked.

Example. Logged in users with role admin are allowed to invoke the method.

@service
class MyService {
    @allow((payload) => payload && payload.role === `admin`)
    send() {
        //
    }
}

Example with auth func. Logged in users who send Hello, world! as text are allowed.

@service
class MyService {
    @allow((payload) => (text) => payload && text === `Hello, world!`)
    send(text) {
        //
    }
}

@deny(accessControlFunc)

Adds a deny rule to the service method. The user is not allowed to invoke the service method if the deny rule return true. It's possible to add multiple rules to a service method and they all must allow to allow user access. See also @allow(accessControlFunc).

accessControlFunc is invoked with the authentication token's payload and should return true if the user is not allowed to invoke the method, and should return false if the user is allowed to invoke the method. accessControlFunc may also return a function which takes the methods arguments and should, again, return true if the user is not allowed or false if the user is allowed to invoke the method.

Argument Type Description
accessControlFunc Function(payload) The function which is invoked.

Example. Logged in users with role admin are allowed to invoke the method.

@service
class MyService {
    @deny((payload) => !payload || payload.role !== `admin`)
    send() {
        //
    }
}

Example with auth func. Logged in users who send Hello, world! as text are allowed.

@service
class MyService {
    @allow((payload) => (text) => !payload || text !== `Hello, world!`)
    send(text) {
        //
    }
}

@login

Configures the target service method to be a login method.

The login method's should return the user account, or throw an error. The user account's id is stored is considered the payload, which is stored in a JSON web token, and stored in a httpOnly cookie.

@logout

Configures the target service method to be a logout method.

The logout method clears the authentication cookie.

Table

Configures the target class as a table which allows you to declare your schema. All changes to your schema are automatically written in migration files.

@model
class Account extends Model {
    static create(transaction) {
        transaction.sql `CREATE TABLE account (
            id SERIAL PRIMARY KEY,
            first_name TEXT
        )`;
    }

    findByName(firstName) {
        return this.select `*`
                   .where `first_name = ${firstName}`
                   .limit `1`;
    }
}

In the build stage, the first migration is created with the schema of account. Now, whenever you change the schema, for example, add a NOT NULL clause to the first_name column, the second migration is automatically created.

@worker(queueUrl)

Creates a worker class on the server.Allows you to transparently execute tasks in a different processes. When you execute a worker method, a message is posted over the internal queue. Currently only AWS SQS is supported, but the idea is to support more queues.

@worker(process.env.MY_WORKER_QUEUE_URL)
class MyWorker {
    doWork(test) {
        // This is executed on another process.
    }
}

Route

Switch

Link

@login

isClient

isServer

React

addInjectProvider

API (in development)

  • @computed
  • @action
  • @observable
  • @model creates a model.
  • @worker adds a worker process and allows to execute tasks over a message queue.
  • @experiment defines an experiment and participates the user to the experiment.
  • @logout.

Help wanted!

Do you want to contribute? Getting started is easy and you can always reach out to @martijndeh. You could work on the following bits:

  • Make sure the fire start watches changes and refresh both the client- and server-side of things.
  • Implement server side rendering!
  • Create the @experiment decorator which could work with https://github.com/martijndeh/musketeer.
  • Create a @worker which runs on the server and can execute tasks in a different process other than the web. E.g. so we can send mails on another process. It should be as transparent as @service.
  • Come up with other cool stuff. :)