Full-stack Javascript Components

for one-dev armies

Nullstack is a full-stack framework for building progressive web applications.

It connects a stateful UI layer to specialized microservices in the same component using vanilla javascript.

Focus on solving your business logic instead of writing glue code.

Server-Side Rendering

Nullstack generates SEO ready HTML optimized for the first paint of your route in a single request using local functions with zero javascript dependencies in the client bundle.

Single Page Application

After hydration, requests will fetch JSON from an automatically generated API off server functions, update the application state, and rerender the page.

Static Site Generation

You can even use Nullstack to generate lightning-fast static websites that serve HTML and become a single page application using an automatically generated static API .

Complete Features as Components

Nullstack is not another part of your stack, it is your stack

Your application can be exported from back-end to front-end as a component and mounted into another application

import Nullstack from 'nullstack';

class ProductList extends Nullstack {

  products = [];

  static async getProducts({ database }) {
    const [products] = await database.query(
      'SELECT * FROM products'
    );
    return products;
  }

  async initiate() {
    this.products = await this.getProducts();
  }

  static async deleteProduct({ database, id }) {
    await database.query(
      'DELETE FROM products WHERE id=?', 
      [id]
    );
  }

  async remove({ id }) {
    await this.deleteProduct({ id });
    await this.initiate();
  }

  renderProduct({ id, name }) {
    return (
      <li>
        <button onclick={this.remove} id={id}>
          {name}
        </button>    
      </li>
    )
  }
  
  render() {
    return (
      <ul>
        {this.products.map((product) => (
          <Product {...product} />
        ))}
      </ul>
    )
  }

}

export default ProductList;
import Nullstack from 'nullstack';

class ProductForm extends Nullstack {

  name = '';
  price = 0;

  static async getProductById({ database, id }) {
    const [products] = await database.query(
      'SELECT * FROM products WHERE id=? LIMIT 1', 
      [id]
    );
    return products[0];
  }

  async initiate({ params }) {
    const product = await this.getProductById({
      id: params.id
    });
    this.name = product.name;
    this.price = product.price;
  }

  static async updateProduct({ database, name, price, id }) {
    await database.query(
      'UPDATE products SET name=?, price=? WHERE id=?',
      [name, price, id]
    );
  }

  async submit({ router, params }) {
    await this.updateProduct({
      id: params.id,
      name: this.name,
      price: this.price
    });
    router.url = '/products';
  }
  
  render() {
    return (
      <form onsubmit={this.submit}>
        <input class="form-control" bind={this.name} />
        <input type="number" step=".01" bind={this.price} />
        <button>Submit</button>
      </form>
    )
  }

}

export default ProductForm;

The example above is the complete code of a product list and edit form without hiding a single line.

Both components invoke server functions to access a MySQL database inside JavaScript without you having to think about APIs

Productivity is in the Details

Nullstack features have been extracted from real life projects with convenience and consistency in mind

import Nullstack from 'nullstack';

class Controlled extends Nullstack {

  count = 0;

  increment({delta}) {
    this.count += delta;
  }
  
  render() {
    return (
      <div>
        <button onclick={this.increment} delta={-1}> 
          {this.count}
        </button>
        <span> {this.count} </span>
        <button onclick={this.increment} delta={1}> 
          {this.count}
        </button>
      </div>
    )
  }

}

export default Controlled;
import Nullstack from 'nullstack';

class Binding extends Nullstack {

  number = 1;
  boolean = true;
  
  object = {number: 1};
  array = ['a', 'b', 'c'];
  
  render({params}) {
    return (
      <form>
        <input bind={this.number} />
        <input bind={this.boolean} type="checkbox" />
        <input bind={this.object.number} />
        {this.array.map((value, index) => (
          <input bind={this.array[index]} />
        ))}
        <input bind={params.page} />
      </form>
    )
  }

}

export default Binding;
import Nullstack from 'nullstack';

class Routes extends Nullstack {

  renderPost({params}) {
    return (
      <div>
        <div route="/post/getting-started">
          npx create-nullstack-app name
        </div>
        <div route="*"> {params.slug} </div>
      </div>
    )
  }
  
  render() {
    return (
      <div> 
        <Post route="/post/:slug" />
        <a href="/post/hello-world"> Welcome </a>
      </div>
    )
  }

}

export default Routes;
import Nullstack from 'nullstack';

class Lifecycle extends Nullstack {
  
  prepare({environment}) {
    const {server, client} = environment;
  }

  async initiate({environment}) {
    const {server, client} = environment;
  }

  async hydrate({environment}) {
    const {client} = environment;
  }

  async update({environment}) {
    const {client} = environment;
  }

  async terminate({environment}) {
    const {client} = environment;
  }

}

export default Lifecycle;

Watch our Nullstack video tutorials

Nullstack cares about making its content as direct to the point and easy to understand as possible

Full-stack with Nullstack - Part 1
Full-stack with Nullstack - Part 2
Full-stack with Nullstack - Part 3

Why should you use Nullstack?

Scalable Development

Every project starts small and becomes complex over time. Scale as you go, no matter the size of the team.

No compromises, no enforcements.

Feature-driven Development

Development of both back and front ends of a feature in the same component in an organized way with ease of overview.

True componentization and code reusability.

Already existing ecosystem

Takes advantage of any isomorphic vanilla Javascript package made throughout history.

All of your application speaks the same language.

Quickly adapt to scope changes

The horizontal structure, as opposed to a hierarchical one, makes it a lot easier to move resources around.

Flexibility over bureaucracy.
Get Started ╰(*°▽°*)╯