HotupdaterHot Updater
Self Hosting (Custom)

Overview

Since v0.22.0+

Build a custom backend server for Hot Updater database with any framework and ORM.

Installation

Install the server package and standalone plugin.

npm install @hot-updater/server @hot-updater/standalone --save-dev

Architecture

Self-hosted mode lets you control the database layer while using existing cloud storage plugins.

How it works:

  • CLI: Uses standaloneRepository in hot-updater.config.ts to send requests to your server
  • Server: Handles database operations with @hot-updater/server
  • Storage: Uses existing plugins (AWS S3, Supabase, Cloudflare R2, Firebase)

This approach gives you full control over metadata while leveraging reliable cloud storage.

CLI Configuration

Configure your Hot Updater CLI to use the self-hosted database.

hot-updater.config.ts
import { defineConfig } from "@hot-updater/core";
import { bare } from "@hot-updater/bare";
import { s3Storage } from "@hot-updater/aws";
import { standaloneRepository } from "@hot-updater/standalone";

export default defineConfig({
  build: bare(),
  storage: s3Storage({
    region: "auto",
    endpoint: process.env.R2_ENDPOINT,
    credentials: {
      accessKeyId: process.env.R2_ACCESS_KEY_ID!,
      secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
    },
    bucketName: process.env.R2_BUCKET_NAME!,
  }),
  database: standaloneRepository({
    baseUrl: "http://localhost:3000/hot-updater",
  }),
});

Storage Plugins

The storages field in your server configuration accepts storage plugins that implement the runtime profile. Runtime storage plugins translate stored bundle locations into client download URLs. They can also read small metadata files directly from storage when bundle diffing data is available.

  1. When you run npx hot-updater deploy from your hot-updater.config.ts, the bundle is uploaded and a storageUri is saved to the database
  2. When the API returns bundle information, the server decodes the storageUri using runtime.getDownloadUrl to generate an HTTP(S) download URL
  3. When diffing data is available, the server reads it directly from storage so the client can reuse unchanged files
  4. React Native clients download the update files using the returned URLs

The CLI storage field uses the node profile for upload/delete/local file download operations. A plugin such as s3Storage can support both profiles, while edge providers such as Cloudflare Workers may use a separate runtime plugin that reads from platform bindings.

Multiple Storage Plugins

storages is an array, allowing you to configure multiple storage providers simultaneously. Each plugin acts as a decoder for its specific storage URI format.

Use cases:

  • Bundles stored across different storage providers
  • Migrating between storage providers (old and new bundles coexist)
  • Multi-region deployments with different storage backends

Example with multiple plugins:

src/hotUpdater.ts
export const hotUpdater = createHotUpdater({
  database: kyselyAdapter({ db, provider: "sqlite" }),
  storages: [
    s3Storage({ /* AWS S3 config */ }),
    supabaseStorage({ /* Supabase config */ }),
    myCustomStorage({ /* My Custom Storage */})
  ],
  basePath: "/hot-updater",
});

The server automatically uses the correct plugin to decode each storageUri based on its format.

Route Groups

createHotUpdater exposes two configurable route groups:

  • updateCheck: update-check endpoints used by React Native clients
  • bundles: bundle management endpoints used by the CLI standaloneRepository plugin

updateCheck is mounted by default. bundles is disabled by default and must be enabled explicitly when the CLI needs bundle-management APIs. When bundles: true, protect /api/bundles* with framework middleware. The /version endpoint is always mounted for diagnostics. If you provide routes, specify both configurable route groups.

src/hotUpdater.ts
export const hotUpdater = createHotUpdater({
  database: kyselyAdapter({ db, provider: "sqlite" }),
  storages: [s3Storage({ /* storage config */ })],
  basePath: "/hot-updater",
  routes: {
    updateCheck: true,
    bundles: true,
  },
});

If you want to hide bundle management endpoints but keep /version, disable the bundles group:

src/hotUpdater.ts
export const hotUpdater = createHotUpdater({
  database: kyselyAdapter({ db, provider: "sqlite" }),
  storages: [s3Storage({ /* storage config */ })],
  basePath: "/hot-updater",
  routes: {
    updateCheck: true,
    bundles: false,
  },
});

Recommendation: Match Your CLI Configuration

For most use cases, use only the storage plugin that matches your CLI's hot-updater.config.ts.

CLI (hot-updater.config.ts):

storage: s3Storage({ ... })

Server (src/hotUpdater.ts):

storages: [s3Storage({ ... })]

This ensures consistency and simplifies configuration. Only use multiple plugins when you need to support bundles from different storage providers.

Available official plugins:

Database Adapters

Start by setting up your database adapter. Choose the ORM that fits your project:

  • Drizzle - TypeScript ORM with SQL-like syntax
  • Prisma - Next-generation ORM with intuitive data modeling
  • Kysely - Type-safe SQL query builder
  • MongoDB - Native MongoDB driver
  • S3 - S3-compatible object storage for bundle metadata

Server Frameworks

After configuring your database, choose the framework that fits your stack:

  • Hono - Lightweight Web Standard framework
  • Express - Popular Node.js framework
  • Elysia - Modern Bun-first framework

Key Features

  • Framework Agnostic: Use Hono, Express, Elysia, or any Web Standard framework
  • ORM choice: Drizzle, Prisma, Kysely, or native MongoDB driver
  • Database control: PostgreSQL, SQLite, or MongoDB on your infrastructure
  • Cloud storage: Leverage existing AWS S3, Cloudflare R2, Supabase, or Firebase storage
  • Type safety: Full TypeScript support across all adapters
  • Production ready: Built-in graceful shutdown and error handling

On this page