npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

apifacility

v0.2.6

Published

Build & deploy real APIs from a JSON spec — composable packs, auth, DB, WebSocket, all declarative. One command.

Readme

🚀 Apideploy

👋 Hello, I'm a maintainer! Apideploy is a submodule of apibooks — same family, same goal: ship real APIs faster. Where apibooks documents your API, Apideploy builds it.

📘 Full usage guide & code examples: jump to the Usage Guide ↓

Build and run a real API (REST + WebSocket + auth + DB) from a single JSON file. You compose packs instead of writing code.

npm install -g @apifacility/apideploy
apideploy init          # creates apideploy.json
apideploy start         # API live on http://localhost:3000

👋 By the maintainer · Par le mainteneur

🇬🇧 Hi, I'm Rayan Chattaoui — creator and active maintainer of apifacility / apideploy. It's the "build" piece of a small open-source family I develop and maintain, all designed to work together:

  • 🚀 apifacility — build real APIs from a JSON spec
  • 📘 apibooks — auto-generate beautiful API docs
  • 🤖 apiupbot — distributed uptime monitoring

⭐ Star it, install it, and tell me what you need next.

🇫🇷 Bonjour, je suis Rayan Chattaoui — créateur et mainteneur actif d'apifacility / apideploy. C'est la brique « build » d'une petite famille open-source que je développe et maintiens, pensée pour fonctionner ensemble (apifacility + apibooks + apiupbot). ⭐ Mets une étoile, installe, et dis-moi ce qu'il te faut ensuite.


🆕 What's new · Nouveautés (v0.2.x)

Early, actively-developed releases — the ecosystem is in alpha. · Versions récentes, en développement actif — l'écosystème est en alpha.

🇬🇧 Latest capabilities:

  • 🧩 Composable packs — add auth.jwt, db.sqlite / mongodb / postgres, ws, qrcode and more without writing code
  • 🛒 Community pack hub — search, add-pack, publish, vote, all packs run sandboxed
  • 📥 OpenAPI import — generate a spec straight from an existing openapi.json
  • 🔗 Ecosystem interop — now pairs natively with apibooks (docs) and apiupbot (monitoring)

🇫🇷 Dernières capacités :

  • 🧩 Packs composables — ajoutez auth.jwt, db.sqlite / mongodb / postgres, ws, qrcode, etc. sans écrire de code
  • 🛒 Hub de packs communautaires — search, add-pack, publish, vote, packs exécutés en sandbox
  • 📥 Import OpenAPI — génère une spec directement depuis un openapi.json existant
  • 🔗 Interopérabilité — s'associe nativement à apibooks (doc) et apiupbot (monitoring)

🧠 The idea

An Apideploy API = a JSON file that maps endpoints to packs. A pack = one .js file wrapping an npm library (one per capability: auth.jwt, db.sqlite, qrcode, etc.).

  • A technical user writes a pack once (~20 lines).
  • Anyone composes endpoints in JSON — no application code.
{
  "routes": {
    "POST /qr":   { "use": "qrcode" },
    "POST /pdf":  { "use": "pdf-create" },
    "POST /slug": { "use": "slugify" }
  }
}

Run apideploy start → 3 working endpoints, instantly.


⚡ Express creation: 1 line per endpoint

apideploy packs                       # list available packs
apideploy add /qr   qrcode            # add POST /qr  (pack qrcode)
apideploy add /pdf  pdf-create        # add POST /pdf (pack pdf-create)
apideploy start                       # boom, API live

📥 Use ANY npm package — auto-generated packs

Don't have a pack for the library you need? Generate one from any npm package:

apideploy install slugify             # → packs/auto/slugify.js
apideploy install js-yaml             # → { "use": "js-yaml", "op": "load" }
apideploy install validator --as v    # custom alias

Apideploy introspects the package (function / class / object of functions) and scaffolds the matching pack into packs/auto/, tracked in _registry.json. Auto-generated packs run sandboxed (see Security below).


🧩 Composition: chain several packs on one endpoint

Sequential pipeline (array) — each step's output feeds the next

"POST /signup": [
  { "use": "validate",      "schema": { "email": "email", "password": "string!" } },
  { "use": "hash-password", "input": "body.password" },
  { "use": "db.sqlite",     "op": "insert", "table": "users",
    "data": { "email": "{{body.email}}", "password": "{{_prev}}" } }
]

Named steps (object pipeline) — to cross-reference results

"POST /order": {
  "pipeline": {
    "product":   { "use": "db.sqlite", "op": "findOne", "table": "products",
                   "where": { "id": "{{body.productId}}" } },
    "order":     { "use": "db.sqlite", "op": "insert",  "table": "orders",
                   "data": { "userId": "{{auth.userId}}", "productId": "{{product.id}}", "price": "{{product.price}}" } },
    "_response": { "ok": true, "orderId": "{{order.id}}", "product": "{{product.name}}" }
  }
}

Any step can reference any previous step via {{stepName.field}}.


🔐 Auth (JWT, bearer, API key)

Root auth block + per-route override:

{
  "auth": { "type": "jwt", "secret": "env:JWT_SECRET" },
  "routes": {
    "GET /me":      { "use": "db.sqlite", "op": "findOne", "table": "users",
                      "where": { "id": "{{auth.userId}}" } },
    "POST /login":  { "auth": false, "pipeline": { /* ... */ } },
    "POST /signup": { "auth": false, "pipeline": { /* ... */ } }
  }
}

→ Every route is protected except those with "auth": false. The decoded JWT payload is exposed via {{auth.X}}.

3 auth packs shipped: auth.jwt, auth.bearer, auth.api-key.


🗄️ Database (SQLite / MongoDB / Postgres)

{
  "db": {
    "type": "sqlite",
    "path": "./app.db",
    "schema": {
      "users":    { "id": "INTEGER PRIMARY KEY AUTOINCREMENT", "email": "TEXT UNIQUE", "password": "TEXT" },
      "products": { "id": "INTEGER PRIMARY KEY AUTOINCREMENT", "name": "TEXT", "price": "REAL" }
    }
  }
}

Tables are created automatically at startup. The db.sqlite pack exposes:

  • op: "insert" → step.table, step.data (or the input)
  • op: "findOne" → step.table, step.where
  • op: "find" → step.table, step.where, step.limit
  • op: "update" → step.table, step.where, step.data
  • op: "delete" → step.table, step.where
  • op: "query" → step.sql, step.params (raw SQL)

Same operations on db.mongo and db.postgres.


🌐 WebSocket

Same pipeline grammar, inside a ws block:

{
  "ws": {
    "/chat": {
      "auth":         { "type": "jwt", "input": "query.token" },
      "onConnect":    { "use": "ws.join", "channel": "chat" },
      "onMessage": [
        { "use": "validate",     "schema": { "text": "string!" } },
        { "use": "db.sqlite",    "op": "insert", "table": "messages",
          "data": { "userId": "{{auth.userId}}", "text": "{{body.text}}" } },
        { "use": "ws.broadcast", "channel": "chat",
          "message": { "from": "{{auth.userId}}", "text": "{{body.text}}" } }
      ],
      "onDisconnect": { "use": "ws.leave" }
    }
  }
}

→ Handshake authentication (rejects 401), DB persistence, broadcast — all in one file.

4 WS packs: ws.broadcast, ws.join, ws.leave, ws.send.


🌍 Community packs

Validated packs from the community hub — distributed as readable .js, never via npm.

apideploy search yaml                 # browse the registry
apideploy add-pack reverse            # download + verify sha256 + install (sandboxed)
apideploy publish ./my-pack           # validate + open a PR to the hub
apideploy vote reverse                # 👍 to promote a pack

A pack becomes a base pack only after both enough community votes and a maintainer security sign-off (status: listed → candidate → verified). Hub: github.com/Aiglator/apideploy-packs.


🛡️ Security — trust tiering, sandbox & limitations

Running arbitrary npm code is powerful and risky. Apideploy defends it with three layers: a static validator, a runtime sandbox, and human sign-off.

Trust tiers — who runs where

| Tier | Packs | Execution | | --- | --- | --- | | core / local | shipped with Apideploy, or your own via --dir | in-process (trusted — you wrote/vetted them) | | auto | apideploy install <npm-pkg> | sandboxed | | community | apideploy add-pack <name> | sandboxed |

Only auto and community packs are treated as untrusted. Core/local packs run in-process for speed — so only point --dir at code you trust.

Layer 1 — static validator (apideploy validate)

Runs at publish time, in the hub CI, and on demand. It parses the pack (real AST pass via acorn when available, lexical scan otherwise) and rejects a pack that:

  • doesn't export { name, run };
  • uses eval() or new Function();
  • uses a dynamic require(variable) (only string literals allowed);
  • requires a forbidden builtin: child_process, cluster, worker_threads, vm, v8, inspector, repl, module, process;
  • requires a network builtin (net, tls, http, https, http2, dgram, dns) without a declared net capability;
  • requires fs without a declared fs capability;
  • requires a 3rd-party module not declared in pack.json deps.

Reading process.env.X without declaring it raises a warning.

Layer 2 — runtime sandbox

Untrusted packs run in a separate child_process.fork under Node's Permission Model (--permission --allow-fs-read=*), plus in-process hardening applied before the pack loads:

| Control | Default (deny-all) | | --- | --- | | Spawn processes (child_process, cluster, worker_threads) | ❌ blocked | | Escapes (vm, v8, inspector, repl, module) | ❌ blocked | | eval() | ❌ disabled | | Filesystem write | ❌ denied by the OS permission model | | Network (net/http/https/…) | ❌ blocked unless net capability | | require('fs') (reads) | ❌ blocked unless fs capability | | Native addons | ❌ blocked unless addons capability | | process.env | 🔒 stripped to only the declared env keys | | Wall-clock time | ⏱ 5 s then SIGKILL | | Memory | 🧠 128 MB cap (--max-old-space-size) |

Granting a capability (opt-in)

Capabilities are declared in the pack's pack.json. Nothing is granted implicitly — you ask for exactly what the pack needs:

{
  "name": "fetch-quote",
  "deps": { "node-fetch": "^3.0.0" },
  "capabilities": {
    "net": ["api.example.com"],   // re-enables network builtins (host list = intent, reviewed at publish)
    "fs":  false,                  // true → allow require('fs') for reads
    "env": ["QUOTE_API_TOKEN"],    // only these env vars reach the pack
    "addons": false                // true → allow native addons
  }
}

Example — a refused pack

// bad-pack.js
const cp = require('child_process');           // ❌ forbidden builtin
module.exports = { name: 'oops', run: () => cp.execSync('whoami').toString() };
$ apideploy validate bad-pack.js
  validateur: ast   requires: child_process
  ✗ 1 erreur(s):
     - forbidden builtin require('child_process')

At install time, apideploy add-pack re-runs the validator, verifies the sha256 against the registry, shows the requested capabilities, and refuses non-verified packs unless you pass --force.

⚠️ Real limitations — read this before trusting the sandbox

  • Node ≥ 20 required for the OS-level jail. The Permission Model is stable on Node ≥ 22, experimental on ≥ 20. On older Node, there is no kernel-enforced sandbox — only the in-process hardening (require guard, env strip, eval off), which a determined attacker may bypass. A warning is printed when this happens.
  • Filesystem reads are allowed (--allow-fs-read=*) so packs can load their dependencies. A sandboxed pack with the fs capability can read files it has OS permission to; only writes are denied.
  • The net host list is not egress-filtered at runtime. Declaring net re-enables network modules; the host array documents intent and is checked by reviewers, but Node does not enforce per-host network rules here.
  • In-process hardening is defense-in-depth, not a perfect jail. The robust isolation target (container / microVM) is the hosted phase-2 runtime.

Bottom line: treat sandboxed packs as reduced-trust, not zero-trust. Run untrusted packs on Node ≥ 22, review the capabilities block before installing, and never expose secrets through env vars a pack can declare.


🔧 CLI

apideploy start [file]              Launch the server (default: apideploy.json)
apideploy init  [file]             Create a starter spec
apideploy packs                    List every available pack
apideploy install <npm-pkg>        Auto-generate a pack from any npm package
apideploy add [METHOD] <path> <pk> Add a route in one command
apideploy from-openapi <file>      Generate a spec from OpenAPI

Community:
apideploy search [keyword]         Search the pack registry
apideploy add-pack <name>          Install a community pack (sandboxed)
apideploy remove-pack <name>       Uninstall a community pack
apideploy publish <dir|pack.js>    Validate + prepare a hub PR
apideploy validate <pack>          Run the security validator
apideploy vote <name>              Vote to promote a pack
apideploy registry [--set <url>]   Show / switch the registry backend

start options:

  • -p, --port <n> port (default 3000)
  • -d, --delay <ms> simulated latency
  • -w, --watch hot-reload on file change
  • --no-cors disable CORS
  • --no-verbose suppress request logs

🎨 Template tokens

Available anywhere in string values of the spec:

| Token | Value | | --------------------- | ---------------------------------------------- | | {{params.id}} | path param (/users/:id) | | {{query.page}} | query param (?page=2) | | {{body.email}} | JSON body field | | {{headers.x-thing}} | HTTP header | | {{auth.userId}} | payload returned by the auth pack | | {{_prev}} | previous step output (sequential pipeline) | | {{stepName.field}} | output of a named step | | {{$uuid}}, {{$name}}, {{$email}}, {{$int 1 100}}, {{$date}} | random fake data | | env:NAME | environment variable (resolved at startup) |


📦 Write your own pack

A pack = packs/<name>.js:

module.exports = {
  name: 'uppercase',
  description: 'Convert text to UPPERCASE',
  defaultInput: 'body.text',

  // optional — called once at startup if present
  init: (config) => { /* open a connection, read a secret, ... */ },

  // called on each request
  run: async (input, context) => ({ result: String(input).toUpperCase() })
};

Subfolders = namespace: packs/db/sqlite.js → pack db.sqlite.


📥 Optional dependencies (peer deps)

Apideploy's only hard dependency is ws (required for WebSocket). The rest install à la carte depending on the packs you use:

| If you use… | Install | | ----------------------- | ---------------------- | | auth.jwt | npm i jsonwebtoken | | db.sqlite | npm i better-sqlite3 | | db.mongo | npm i mongodb | | db.postgres | npm i pg | | hash-password | npm i bcryptjs | | qrcode | npm i qrcode | | pdf-create, pdf-info| npm i pdf-lib |

If a dependency is missing, the pack still appears in apideploy packs but throws a clear error at runtime.


📘 Usage Guide

A step-by-step walkthrough with copy-paste examples.

Contents: Install · Quick start · Spec anatomy · Route types · Fake data & tokens · Pipelines · Auth · Database · WebSocket · CLI reference · Write a pack · Recipes

Install

# Global install (recommended for the CLI)
npm install -g @apifacility/apideploy

# or locally in a project
npm install @apifacility/apideploy

Node.js 14+ is required. The only hard dependency is ws (WebSocket). The other libraries (JWT, SQLite, bcrypt, qrcode…) install à la carte depending on the packs you use.

Quick start

apideploy init            # creates a sample apideploy.json
apideploy start           # API live on http://localhost:3000

init generates an apideploy.json with CRUD routes on /users. Try it:

curl http://localhost:3000/api/users
curl http://localhost:3000/api/users/42
curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice","email":"[email protected]"}'

💡 The /api prefix comes from the spec's baseUrl field.

Handy launch options:

apideploy start mocks.json -p 4000     # custom port
apideploy start mocks.json --watch     # hot reload on every change
apideploy start mocks.json -d 300      # simulated 300 ms latency
apideploy start mocks.json --no-cors   # disable CORS headers

Spec anatomy

A spec file is JSON with a few top-level blocks:

{
  "baseUrl": "/api",        // prefix added to every route (optional)
  "delay": 0,               // global latency in ms (optional)
  "cors": true,             // CORS headers (optional, default: true)

  "auth": { "...": "..." },  // global auth config (optional)
  "db":   { "...": "..." },  // database config (optional)

  "routes": { "GET /users": { "...": "..." } },  // REST endpoints
  "ws":     { "/chat": { "...": "..." } }          // WebSocket endpoints (optional)
}

A route key follows the "METHOD /path" format:

"GET /users"          // → GET  /api/users
"POST /users/:id"     // → POST /api/users/:id   (:id = dynamic parameter)

Route types

Apideploy detects a route's type from its shape.

a) Static / mocked response — the value is the body (status 200):

"GET /ping": { "message": "pong" },
"GET /tags": ["news", "tech", "sport"]

b) Detailed response — control the HTTP code, headers or latency with the reserved keys status, body, headers, delay:

"POST /users": {
  "status": 201,
  "delay": 200,
  "headers": { "X-Created-By": "apideploy" },
  "body": { "id": "{{$uuid}}", "name": "{{body.name}}", "created": "{{$date}}" }
},
"DELETE /users/:id": { "status": 204 }

⚠️ A data field named status that is non-numeric (e.g. "status": "active") is treated as data, not as an HTTP code. A lone numeric status is the HTTP code.

c) Pack-powered route — use names the pack to run; the response is its output:

"POST /qr": { "use": "qrcode" },
"GET /me":  { "use": "db.sqlite", "op": "findOne", "table": "users",
              "where": { "id": "{{auth.userId}}" } }

d) Dynamic handler — in a .js spec, a route can be a function receiving the request context (method, path, params, query, body, headers, auth):

// api.mock.js
module.exports = {
  baseUrl: '/api',
  routes: {
    'GET /time':     (ctx) => ({ now: Date.now(), path: ctx.path }),
    'GET /users/:id': (ctx) => ({ status: 200, body: { id: ctx.params.id, q: ctx.query } })
  }
};

Fake data & tokens

Any string in the spec can contain {{ ... }} tokens, resolved per request.

| Token | Value | | --------------------- | ---------------------------------------------- | | {{params.id}} | path param (/users/:id) | | {{query.page}} | query param (?page=2) | | {{body.email}} | JSON body field | | {{headers.x-token}} | HTTP header | | {{auth.userId}} | payload returned by the auth pack | | {{_prev}} | previous step's output (sequential pipeline) | | {{stepName.field}} | a named step's output | | env:NAME | environment variable (resolved at startup) |

Fake-data generators ($ prefix): {{$uuid}}, {{$id}}, {{$name}}, {{$firstname}}, {{$lastname}}, {{$email}}, {{$city}}, {{$int 18 65}}, {{$float 0 1}}, {{$bool}}, {{$date}}, {{$word}}, {{$sentence}}, {{$paragraph}}, {{$url}}, {{$phone}}, {{$pick a|b|c}}.

🎯 Type preserved: a string that is only a token ("{{$int 1 100}}") injects the real typed value (the number 42, not "42"). Mixed with text ("user-{{$int 1 9}}"), the result is a string.

"GET /posts": [
  { "id": "{{$int 1 999}}", "title": "{{$sentence}}", "author": "{{$name}}" }
],
"GET /search": {
  "query": "{{query.q}}", "page": "{{query.page}}",
  "results": [ { "id": "{{$uuid}}", "label": "{{$sentence}}" } ]
}

Pipelines

Sequential (array) — each step's output feeds the next ({{_prev}}); the last step's output is the response body:

"POST /signup": [
  { "use": "validate",      "schema": { "email": "email", "password": "string!" } },
  { "use": "hash-password", "input": "body.password" },
  { "use": "db.sqlite",     "op": "insert", "table": "users",
    "data": { "email": "{{body.email}}", "password": "{{_prev}}" } }
]

Named (object pipeline) — steps cross-reference via {{stepName.field}}; _response defines the body and _status the HTTP code:

"POST /order": {
  "pipeline": {
    "product":   { "use": "db.sqlite", "op": "findOne", "table": "products",
                   "where": { "id": "{{body.productId}}" } },
    "order":     { "use": "db.sqlite", "op": "insert",  "table": "orders",
                   "data": { "userId": "{{auth.userId}}", "productId": "{{product.id}}",
                             "price": "{{product.price}}" } },
    "_response": { "ok": true, "orderId": "{{order.id}}", "product": "{{product.name}}" }
  }
}

Auth

A global auth block protects every route except those with "auth": false. The decoded payload is exposed via {{auth.X}}.

{
  "auth": { "type": "jwt", "secret": "env:JWT_SECRET" },
  "routes": {
    "POST /login": { "auth": false, "pipeline": { "...": "..." } },
    "GET /me":     { "use": "db.sqlite", "op": "findOne", "table": "users",
                     "where": { "id": "{{auth.userId}}" } }
  }
}

Mint a token in a login pipeline:

"token": { "use": "auth.jwt", "op": "sign",
           "payload": { "userId": "{{user.id}}", "email": "{{user.email}}" } }

Packs: auth.jwt (npm i jsonwebtoken), auth.bearer, auth.api-key.

Database

Declare a db block — tables are auto-created at startup (use "path": ":memory:" for an ephemeral SQLite DB):

{
  "db": {
    "type": "sqlite", "path": "./app.db",
    "schema": {
      "users": { "id": "INTEGER PRIMARY KEY AUTOINCREMENT", "email": "TEXT UNIQUE", "password": "TEXT" }
    }
  }
}

db.sqlite / db.mongo / db.postgres ops: insert · findOne · find · update · delete · query (raw SQL).

"GET /products":  { "use": "db.sqlite", "op": "find",    "table": "products" },
"GET /users/:id": { "use": "db.sqlite", "op": "findOne", "table": "users",
                    "where": { "id": "{{params.id}}" } }

Deps: better-sqlite3, mongodb, or pg.

WebSocket

Same pipeline grammar inside a ws block, with lifecycle handlers onConnect, onMessage, onDisconnect and an optional handshake auth (rejects with 401):

{
  "ws": {
    "/chat": {
      "auth":      { "type": "jwt", "input": "query.token" },
      "onConnect": { "use": "ws.join", "channel": "chat" },
      "onMessage": [
        { "use": "validate",     "schema": { "text": "string!" } },
        { "use": "ws.broadcast", "channel": "chat",
          "message": { "from": "{{auth.userId}}", "text": "{{body.text}}" } }
      ],
      "onDisconnect": { "use": "ws.leave" }
    }
  }
}

Client URL: ws://localhost:3000/api/chat?token=.... Packs: ws.broadcast, ws.join, ws.leave, ws.send.

CLI reference

See the 🔧 CLI section above for the full command list. Build an API without hand-writing JSON:

apideploy packs                  # see available packs
apideploy add /qr  qrcode        # add POST /qr  (pack qrcode)
apideploy install slugify        # generate a pack from any npm package
apideploy start                  # it's live

Write a pack

A pack is a packs/<name>.js file (subfolders namespace it: packs/db/sqlite.js → db.sqlite):

module.exports = {
  name: 'uppercase',
  description: 'Convert text to UPPERCASE',
  defaultInput: 'body.text',          // where to read input by default
  init: (config) => { /* once at startup: DB connection, secret… */ },
  run:  async (input, context) => ({ result: String(input).toUpperCase() })
};

context = { method, path, params, query, body, headers, auth, step, _prev }. Point Apideploy at custom packs with --dir ./my-packs or packsDir in the spec.

Recipes

Mocked REST API for a front-end:

{
  "baseUrl": "/api", "delay": 150,
  "routes": {
    "GET /users":     [ { "id": "{{$uuid}}", "name": "{{$name}}", "email": "{{$email}}" } ],
    "GET /users/:id": { "id": "{{params.id}}", "name": "{{$name}}", "email": "{{$email}}" },
    "POST /users":    { "status": 201, "body": { "id": "{{$uuid}}", "name": "{{body.name}}" } },
    "DELETE /users/:id": { "status": 204 }
  }
}

Minimal full backend (auth + DB):

npm i jsonwebtoken better-sqlite3 bcryptjs
JWT_SECRET=dev-secret apideploy start app.json
{
  "auth": { "type": "jwt", "secret": "env:JWT_SECRET" },
  "db":   { "type": "sqlite", "path": ":memory:",
            "schema": { "users": { "id": "INTEGER PRIMARY KEY AUTOINCREMENT",
                                   "email": "TEXT UNIQUE", "password": "TEXT" } } },
  "routes": {
    "POST /signup": { "auth": false, "pipeline": {
      "hash":  { "use": "hash-password", "input": "body.password" },
      "user":  { "use": "db.sqlite", "op": "insert", "table": "users",
                 "data": { "email": "{{body.email}}", "password": "{{hash}}" } },
      "token": { "use": "auth.jwt", "op": "sign", "payload": { "userId": "{{user.id}}" } },
      "_response": { "id": "{{user.id}}", "token": "{{token}}" }, "_status": 201
    } },
    "GET /me": { "use": "db.sqlite", "op": "findOne", "table": "users",
                 "where": { "id": "{{auth.userId}}" } }
  }
}

Generate a spec from OpenAPI:

apideploy from-openapi openapi.json -o mocks.json && apideploy start mocks.json

🤝 The Api family

  • apibooks — auto-generated documentation for Express APIs
  • apideploy — build the API itself, declaratively (a submodule of apibooks)
  • apiupbot — uptime monitoring bot for your APIs

📄 License

MIT