Armadillo walking toward camera at night, rim-lit

Dillo — the backend that takes the hit

introducingDILLO

Your backend, built to take a hit. Files, database, functions — described in three files, deployed to Cloudflare, armored by default.

Get the alpha See the three files $ npm init dillo@alpha

Exhibit A

Proof of armor,
courtesy of Texas.

In July 2015, a man in Marietta, Texas spotted an armadillo on his property at 3 a.m., took out a .38 revolver, and fired three times. One of the bullets came back and hit him in the jaw. He was airlifted to a hospital. His jaw was wired shut.

“We didn’t find the armadillo.”

— Sheriff Larry Rowe, Cass County, on the condition of the animal

Why the armadillo

An animal review,
as a spec sheet.

Everything the animal is, the library is. Not a metaphor we chose — a standard we have to live up to.

Armadillo armor macro
01

Tough

Permissions enforced on every single request. Private records by default. Accounts, sessions, teams — the engine holds the line so a bad call, a bad actor, or a bad day doesn’t get through the shell.

Lone armadillo on a desert ridge at dawn
02

Independent

Runs on your laptop. Runs on Cloudflare. SQLite locally, Workers + D1 in the cloud — infrastructure you control. No lock-in worship, no permission slip required to leave.

Armadillo curled in a defensive ball
03

Does its own thing

Three files. Zero ceremony. schema.ts, backend.ts, armadillo.config.ts — typed from schema to client. Opinionated defaults for everything boring, so you never think about it again.

Anatomy

What is a dillo?

A dillo is a box. Your box. Everything your app needs to exist lives inside it — and every wall of the box can be swapped, expanded, or ripped out. Your call.

Wall one

Files

Your files, with permissions attached. Stored with your data, in your dillo’s own database — uploads, storage, and access rules that respect the same permission words as everything else. Your users’ stuff, armored.

Wall two

Database

Your data, validated and permissioned at the schema level. Powered by D1 by default — JSON records with real types flowing straight to your client. Expand it, swap it, grow it. Your call.

Wall three

Functions

Your logic, at the edge. defineFunction() — validated inputs, typed outputs, callable straight from your UI with api.functions.greet({ name: "Ada" }). Middleware, secrets, and integrations live here, not scattered across provider dashboards.

Roadmap — not shipped
Wall four

Micro-apps

The vision: AI-built apps living on top of your dillo — generated against your schema, permissioned by your backend, deployed without a second thought. This one isn’t built yet. We’re not selling you a drawing of a house.

The deal

We take the entire backend.
You do whatever you want
on the frontend.

Permissions

The backend holds all the permissions. Every record, every file, every function call runs through the armor. You don’t sprinkle auth across seventeen services and pray.

Ownership

Your data is yours. Private by default, on infrastructure you control. No silent exfiltration, no “trust us” clause. If you want to leave, the box comes with you.

Responsibility

Ship the dumb stuff. Break your own data? That’s on you. We’re not your mom. But the backend will never be the thing that breaks first — that part we guarantee with armor.

Frontend

No approval needed. For anything. Build the wildest UI you can imagine — the dillo underneath doesn’t care what it looks like, only that the data contract holds.

The whole thing

Three files.
One backend.

This is real code. Describe what your data looks like, define what your app does, declare where it runs. Done.

schema.ts — what your data looks like. Who can touch it.
import { boolean, owner, schema, string, table } from "create-dillo/schema";

export default schema({
  Project: table({
    name: string().min(1).max(120),
    active: boolean().default(true),
  }).permissions({
    view: owner,   // the armor: enforced on every request
    edit: owner,
  }),
});
backend.ts — what your app does. Functions, validated.
import { defineBackend, defineFunction } from "create-dillo/backend";
import { string } from "create-dillo/schema";
import schema from "./schema.ts";

export default defineBackend({
  schema,
  functions: {
    greet: defineFunction()
      .input({ name: string().min(1) })
      .output({ message: string() })
      .handler((_ctx, input) => ({ message: `Hello ${input.name}` })),
  },
});
armadillo.config.ts — where it runs. Your infrastructure.
import { app } from "create-dillo/deploy/cloudflare";
import backend from "./backend.ts";

export default app({ id: "my-app", backend, signup: "open" });
// Workers + D1, wired. Deploy when you're ready.
client.ts — how your UI calls it. Types flow to your UI. Zero drift.
import { Armadillo } from "create-dillo/client";
import type { Project } from "./schema.ts";  // same types, shared

const api = Armadillo.client({ url: "/api" });

// Fully typed — Project comes straight from your schema.
const projects: Project[] = await api.tables.Project.query().find();
const { message } = await api.functions.greet({ name: "Victor" });
// Change the schema? The client breaks at build time, not runtime.

Alpha — out now

Take the hit.

$ npm init dillo@alpha

One command. Three files. Zero weeks spent wiring auth.
Your app has a better story than “we spent the week wiring up auth.”

Fifteen working examples

Don't take our word for it.
Click around.

Every example is a real app running on Dillo. Each one is live. Each page shows you exactly what it demonstrates and what features it uses.

Browse all examples