Rennko Cloud docs

Rennko Cloud gives your app auth, a realtime database that works offline, file storage and hosting. If you've used Firebase, most of this will look familiar.

Get started

  1. Sign up in the console and create a project, for example my-app. It lives at https://my-app.rennko.tech.
  2. Import the SDK from your project. There's nothing to install:
<script type="module">
  import { app } from 'https://my-app.rennko.tech/__/init.js'
  // app.auth, app.db, app.storage are ready to use
</script>

Using a bundler, or several projects? Import the SDK directly:

import { initApp, serverTimestamp, increment, arrayUnion, arrayRemove, deleteField } from 'https://console.rennko.tech/rennko.js'
const app = initApp('https://my-app.rennko.tech')

Auth

await app.auth.signUp(email, password)          // also upgrades an anonymous user in place
await app.auth.signIn(email, password)
await app.auth.signInAnonymously()
const { user, providerToken } = await app.auth.signInWithGitHub({ scopes: ['read:user'] })
await app.auth.signInWithGoogle()
await app.auth.linkWithGitHub()                  // add GitHub to the signed-in user
await app.auth.unlink('github')
app.auth.onAuthChange(user => { /* null when signed out; works offline */ })
await app.auth.signOut()

user has uid, email, name, photo, anon and providers (the GitHub and Google accounts linked to it). providerToken is the GitHub or Google access token, so you can call their APIs as the user.

To turn on GitHub or Google, open the console's Authentication tab. Register an OAuth app with the callback URL shown there, then paste its client ID and secret. Popup sign-in has to start from a click handler, or the browser blocks the popup.

Database

Data lives in documents (JSON objects up to 1 MB) inside collections. Paths alternate: posts/abc, users/u1/notes/n1.

const ref = app.db.doc('todos/abc')
await ref.set({ text: 'hi', done: false, at: serverTimestamp() })
await ref.set({ tags: ['x'] }, { merge: true })
await ref.update({ count: increment(1), 'meta.seen': true, tags: arrayUnion('y') })
await ref.delete()
const snap = await ref.get()          // snap.exists, snap.data(), snap.metadata.fromCache

const todos = app.db.collection('todos')
const newRef = await todos.add({ text: 'buy milk' })
const q = todos.where('done', '==', false).orderBy('at', 'desc').limit(20)
const unsubscribe = q.onSnapshot(snap => {
  for (const c of snap.docChanges()) console.log(c.type, c.doc.id, c.doc.data())
})

const batch = app.db.batch()          // all or nothing
batch.set(app.db.doc('a/1'), { n: 1 }).delete(app.db.doc('a/2'))
await batch.commit()

Query operators: == != < <= > >= in not-in array-contains array-contains-any. Use the field __id for the document ID. You can also page with .offset(n).

Offline

Nothing to set up. In the browser the SDK keeps a copy of what you've read, plus a queue of your writes, in IndexedDB.

  • Listeners fire right away from the cache. snap.metadata.fromCache and hasPendingWrites say what you're looking at.
  • Writes update every listener instantly. They sync when there's a connection, even after a reload or restart, and each one is applied exactly once.
  • A write's promise resolves when the server confirms it. In an offline-friendly UI, don't wait on it before updating the screen.
  • app.onConnectionChange(online => …), app.disableNetwork(), app.enableNetwork() and app.waitForPendingWrites() give you control.

Storage

const file = await app.storage.upload(`avatars/${user.uid}/me.png`, blob)
img.src = file.url                        // a shareable link
await app.storage.list(`avatars/${user.uid}/`)
await app.storage.download(path)          // a Blob
await app.storage.delete(path)

Files can be up to 25 MB. Storage needs a connection.

Hosting

In the console's Hosting tab, drop your built site's folder (the one with index.html), or use the CLI. Turn on Single-page app for React/Vue routers, or Clean URLs to serve /about from about.html. A 404.html becomes your not-found page.

Hosted sites appear on Discover, ranked by how many people visited them today. A visit is the first page someone opens on your site that day, and bots don't count. Discover uses your index.html's <title>, <meta name="description"> and og:image. To hide your site, turn off Show in Discover in the Hosting tab.

Security rules

Rules decide who can read and write. Edit them in the console's Rules tab, or deploy a rules.json:

{
  "db": {
    "users/{uid}/**": { "read": "auth.uid == uid", "write": "auth.uid == uid" },
    "posts/{id}": {
      "read": "true",
      "create": "data.owner == auth.uid && data.text.size() < 500",
      "update": "doc.owner == auth.uid && data.owner == auth.uid",
      "delete": "doc.owner == auth.uid"
    }
  },
  "storage": {
    "avatars/{uid}/**": { "read": "true", "write": "auth.uid == uid && data.size < 5000000", "delete": "auth.uid == uid" }
  }
}
  • {name} matches one path segment. ** matches the rest.
  • Actions: read (or get, list) and write (or create, update, delete). If any matching rule allows it, it's allowed.
  • Conditions can use:
    • auth: uid, email, anon and provider, or null when signed out.
    • doc: what's stored now.
    • data: what's being written. It's null on delete. For files it has size and type.
    • now, which equals serverTimestamp() values in the same write.
    • Your {name} variables.
  • Operators: == != < <= > >= && || ! + - in. Functions: .size() .includes(x) .startsWith(s) .endsWith(s) .keys() .hasAll(list) .hasAny(list) .hasOnly(list).
  • Queries only return documents the user may list.
  • The console and the admin key skip rules.

CLI

npx github:learningnihongo934-byte/RennkoCloud login
npx github:learningnihongo934-byte/RennkoCloud create my-app
npx github:learningnihongo934-byte/RennkoCloud deploy --project my-app --dir dist --spa --rules rules.json

Put { "project": "my-app", "dir": "dist", "spa": true, "rules": "rules.json" } in a rennko.json to make plain rennko deploy work. In CI, set RENNKO_ADMIN_KEY (from the console's Overview tab) instead of logging in.

Run your own

Rennko Cloud is MIT-licensed and fits Cloudflare's free plan, with no card needed.

git clone https://github.com/learningnihongo934-byte/RennkoCloud && cd RennkoCloud
./build.sh          # everything on http://localhost:8787, works with no internet
./build.sh test     # end-to-end tests
./build.sh deploy   # to your Cloudflare account

Limits

  • Per account: 5 projects.
  • Per project: 500 MB of storage.
  • Per file: 25 MB.
  • Per document: 1 MB.
  • Per query: 1,000 documents.

The whole service shares Cloudflare's free daily quota. Not included yet: email verification and password-reset emails, server functions, and count queries.