Elysia
Build a self-hosted Hot Updater server with Elysia framework.
Installation
This recipe runs on Node.js. Install Elysia, its Node adapter and bearer token plugin.
npm install elysia @elysiajs/node @elysiajs/bearernpm install --save-dev tsxSetup and security
Set up a database adapter, generate its Hot Updater
schema, and apply the schema before starting the server. Define the
server in src/hotUpdater.ts with createHotUpdater, as in the
quick start.
The recommended self-hosted setup runs the apiKeys() plugin, which requires
an API key on update-check, Insights ingestion, and plugin client routes.
Always protect admin routes with framework middleware scoped to the admin
handler. Update-check routes are usually exposed for React Native clients, but
that is a deployment choice:
- Client OTA:
/hot-updater/release-catalogs/*,/hot-updater/artifacts/* - Optional public diagnostics:
/hot-updater/version - Protected admin API:
/hot-updater/admin/*
Mounting handlers.admin opts the server into the admin API;
handlers.client never serves those routes. This example requires a nonempty
token and scopes the authentication hook to an admin sub-app:
import { Elysia } from "elysia";
import { node } from "@elysiajs/node";
import { bearer } from "@elysiajs/bearer";
import { hotUpdater } from "./hotUpdater";
const adminToken = process.env.HOT_UPDATER_ADMIN_TOKEN;
if (!adminToken) {
throw new Error("HOT_UPDATER_ADMIN_TOKEN is required");
}
const adminApp = new Elysia()
.use(bearer())
.onBeforeHandle(({ bearer }) => {
if (bearer !== adminToken) {
return new Response("Unauthorized", { status: 401 });
}
})
.mount("/", hotUpdater.handlers.admin);
new Elysia({ adapter: node() })
.mount("/hot-updater/admin", adminApp)
.mount("/hot-updater", hotUpdater.handlers.client)
.listen(3000);Start the server with the database and storage variables available, and export your admin token in the same shell:
export HOT_UPDATER_ADMIN_TOKEN=your-secret-auth-token
npx tsx src/index.tsConfigure the CLI to include the auth token when deploying bundles, beside the config's storage and plugins; see CLI Configuration:
import { standaloneRepository } from "@hot-updater/standalone";
import { defineConfig } from "hot-updater";
const adminToken = process.env.HOT_UPDATER_ADMIN_TOKEN;
if (!adminToken) {
throw new Error("HOT_UPDATER_ADMIN_TOKEN is required");
}
export default defineConfig({
// ... build, storage, plugins, and updateStrategy
database: standaloneRepository({
baseUrl: "http://localhost:3000/hot-updater/admin",
commonHeaders: {
Authorization: `Bearer ${adminToken}`,
},
}),
});Handler boundaries
With insights() in plugins, handlers.client accepts Insights events and
handlers.admin serves Insights queries; without it, the server has no such
routes, and they answer 404. Apps send reports with the
Insights client plugin.
Server plugin endpoints mount on the same two
handlers. The client API key protects catalog reads, artifact resolution,
Insights ingestion, and plugin client routes; it does not protect or authorize
the admin handler.
See the shared handler boundary for route groups and the repository server contract for exact management requests. Keep the framework-specific authentication and mount ordering shown above.
Before connecting the app, verify that /hot-updater/version responds and an
admin request without the expected token is rejected. Then use
CLI configuration to
verify authenticated management access.
React Native setup
Create a client API key
against the prepared database, then follow Set up your app.
Use the client root as baseURL and send the key through x-api-key. Never
put the admin token in the app.
For local connectivity checks, localhost reaches the host from the iOS
Simulator on the same Mac and 10.0.2.2 does so from the Android Emulator.
Before a release-build OTA test, expose the server through a reachable HTTPS
endpoint and use it in the app; changing the host address does not enable
cleartext HTTP. Complete Test an OTA update
against that HTTPS endpoint.