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';
import TaskList from './TaskList';
import {readFileSync} from 'fs';

class Application extends Nullstack {

  static async getTasks({limit}) {
    const json = readFileSync('tasks.json', 'utf-8');
    return JSON.parse(json).tasks.slice(0, limit);
  }

  prepare(context) {
    context.tasks = [];
  }

  async initiate(context) {
    context.tasks = await this.getTasks({limit: 10});
  }

  render() {
    return (
      <main>
        <TaskList route="/tasks" />
        <a href="/tasks" route="*"> List Tasks </a>
      </main>
    )
  }

}

export default Application;
import Nullstack from 'nullstack';

class TaskList extends Nullstack {

  hideComplete = false;

  renderTask({task}) {
    if(this.hideComplete && task.complete) return false;
    return (
      <li> 
        <input bind={task.description} />
      </li>
    )
  }
  
  render({tasks}) {
    return (
      <div> 
        <ul>
          {tasks.map((task) => <Task task={task} />)}
        </ul>
        <button onclick={{hideComplete: !this.hideComplete}}>
          Toggle complete tasks
        </button>
      </div>
    )
  }

}

export default TaskList;

The example above uses server functions to read tasks from a JSON file and store them in the context available to all components.

The tasks are listed in a specific route that renders a component with multiple inner components filtered by status with inputs using two-way bindings .

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 turorials

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?

[object Object]

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.
[object Object]

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.
[object Object]

Already existing ecosystem

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

All of your application speaks the same language.
[object Object]

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 ╰(*°▽°*)╯