Skip to main content

Verdant🌿

is a framework and philosophy for small, sustainable, human web apps

app.ts
// initialize your data directly on// the clientconst post = await client.posts.put({	title: 'Hello World'});// reactively update your UIpost.subscribe(	'change',	() => {		bodyText.innerText =			post.get('content');	},);// synchronously interact with datapost.set(	'content',	`Verdant makes both local storage	and synchronized data feel as simple	as plain objects.`);// build rich apps with// simple toolspost.set('image', fileInput.files[0]);post.get('tags').push('local-first');// now your post is saved locally// and synced to the server,// even if you're offline// or refresh the page					
app.ts
// initialize your data directly on the clientconst post = await client.posts.put({	title: 'Hello World'});// reactively update your UIpost.subscribe(	'change',	() => bodyText.innerText = post.get('content'),);// synchronously interact with datapost.set(	'content',	`Verdant makes both local storage and synchronized data	feel as simple as plain objects.`);// build rich apps with simple toolspost.set('image', fileInput.files[0]);post.get('tags').push('local-first');// now your post is saved locally and synced to the server,// even if you're offline or refresh the page					

There is a little plant on a shelf near my desk. It's no larger than the size of a dime. For years I've watered it, but it never changes. It neither grows nor dies, but it is alive.

Some living things must wait patiently for their season.

Some living things are only meant to be what they are.

They are no less alive.

Verdant is an approach to software which aims to make space for these overlooked forms of life. Nurture your idea at its own pace, free from the expectations of endless growth and pressures of cycles of debt.

Verdant gives access to this new-old way of building software to modern web developers, leveraging established and familiar technologies.

On a technical level, it allows you to:

  • 📱 Run your whole web app on the user's device

    Using IndexedDB and built-in reactive queries and objects, Verdant apps feels are easy to make, fast, and fun.

  • 📶 Work offline and leverage advanced functionality

    Verdant apps can work offline when configured with a service worker, and feature tools like undo and file storage out of the box.

  • 🔃 Sync data between devices

    Verdant apps can sync data between devices by connecting to a plain Node server with a SQLite database. Authenticate and store additional user data using familiar technologies of your choosing.

I made Verdant to build my cooking app Gnocchi, and I've open-sourced it for anyone else with similar goals. My ambition is not to revolutionize computing, it's only to build sustainable, human apps.

To do this, I've adopted a few principles:

  • 🌿 Grow at your own pace

    Not every idea must produce billion-dollar returns. Sometimes it's enough to let something exist, and see what comes of it. Verdant is designed to limit the cost of producing and maintaining small apps. The simplest, local-only Verdant app costs only as much as your domain name.

  • 🌱 Operate sustainably

    Verdant is designed to help you earn revenue in proportion to your costs, by doing the simple thing: charging for access to costly services, like servers. Despite manifestos against endless-growth-capitalism, I don't think profit is a dirty word. A sustainable business is one which can pay for itself, including your time and effort.

  • 🚲 Practicality over perfection

    In a vacuum, software developers gravitate toward imagined ideas of perfect systems. But we are human, and our users are human. Often what humans want is not perfection someday, but usefulness today. Verdant embraces simple solutions which produce real outcomes, and avoids complex systems pursuing abstract perfection.

How to use Verdant

Build a schema

  • Define model fields, defaults, and query indexes
  • Schemas are TypeScript, so you can use familiar code and modules
  • Create migrations as your schema evolves
schema.ts
import {	collection,	schema} from '@verdant-web/store';import cuid from 'cuid';const posts = collection({	name: 'post',	primaryKey: 'id',	fields: {		id: {			type: 'string',			default: cuid,		},		title: {			type: 'string',		},		body: {			type: 'string',		},		createdAt: {			type: 'number',			default: Date.now,		},		image: {			type: 'file',			nullable: true,		}	},});export default schema({	version: 1,	collections: {		posts	}});

Generate a client

  • Generated code is type-safe to your schema
  • Queries and models are reactive
  • React hook bindings included
client.ts
import {	ClientDescriptor,	createHooks,	migrations} from './generated.js';export const clientDescriptor =	new ClientDescriptor({		migrations,		namespace: 'demo',	})// React hooks are created for youexport const hooks = createHooks();async function getAllPosts() {	const client = await		clientDescriptor.open();	const posts = await client.posts		.findAll().resolved;	return posts;}

Store local data

  • Store data in IndexedDB, no server or internet connection required
  • Undo/redo and optimistic updates out of the box
  • Assign files directly to model fields
app.tsx
const post = await client.posts.put({	title: 'All the Trees of the Field will Clap their Hands',	body: '',});post.set('body', 'There is a little plant on a shelf near my desk...');fileInput.addEventListener('change', () => {	const file = fileInput.files[0];	post.set('image', file);});client.undoHistory.undo();

Sync with a server

  • Access data across devices
  • Share a library with collaborators
  • Peer presence data and profile info
  • Works realtime, pull-based, even switching dynamically
server.ts
import { Server } from '@verdant-web/server';const server = new Server({	databaseFile: 'db.sqlite',	tokenSecret: 'secret',	profiles: {		get: async (userId: string) => {			return {				id: userId,			}		}	}});server.listen(3000);