Getting Started
Self-host the Teardown ingest server with in-memory storage in minutes, then implement the repository interfaces against your own database.
Run Teardown's identify, event ingestion, and force-update runtime inside your own TypeScript backend. This guide gets a server live with in-memory storage in a few minutes, then shows how to go to production by implementing the repository interfaces against your own database — you own the schema and its migrations.
Before you start
You need a TypeScript runtime with Web-standard fetch — Bun, Node 18+, Deno, or an
edge runtime. The examples use Bun.
1. Install
bun add @teardown/server
# or
npm install @teardown/serverThe package ships the runtime and HTTP edge only — no database driver, no ORM. You bring your own database by implementing the storage repositories (see Go to production).
2. Generate a session secret
The server signs a session token for every client it identifies. Generate a strong secret and store it in your backend's environment — never commit it or ship it to clients.
openssl rand -base64 32SESSION_SECRET=<paste the generated value>What is SESSION_SECRET?
It's the HMAC key the server uses (HS256, via jose) to sign and verify the
short-lived session tokens it issues after a successful /v1/identify. The client
sends the token back on later requests, and the server verifies it with this secret —
so it can trust the session without another database lookup. Keep it secret, keep it
stable, and use the same value on every instance (a token signed by one replica
must verify on another). See Advanced
for rotation and multi-instance notes.
3. Run it
Create the runtime with in-memory storage and serve the ingest routes. Run it single-tenant:
pass a tenant and the client needs no API-key/org/project headers. The seed just needs one
project and one environment so identify can resolve the environment by slug.
import { createTeardown } from "@teardown/server";
import { createMemoryStorage } from "@teardown/server/adapters/memory";
import { toFetchHandler } from "@teardown/server/http/fetch";
const now = new Date().toISOString();
const storage = createMemoryStorage({
projects: [{
id: "proj_demo", org_id: "org_demo", name: "Demo", slug: "demo",
type: "EXPO", status: "ACTIVE", push_notifications_enabled: false,
first_session_at: null, created_at: now, updated_at: now,
}],
environments: [{
id: "env_demo", project_id: "proj_demo", name: "Production",
slug: "production", type: "PRODUCTION", created_at: now, updated_at: now,
}],
});
const td = createTeardown({
storage, // in-memory: try it / tests
config: { sessionSecret: process.env.SESSION_SECRET! }, // required
tenant: { orgId: "org_demo", projectId: "proj_demo" }, // single-tenant: no api-key / org / project headers
});
Bun.serve({ port: 4501, fetch: toFetchHandler(td.router()) });bun run server.ts
# then in another shell:
curl http://localhost:4501/health # → { "status": "ok", ... }Your ingest server is live. It speaks the exact same wire contract as Teardown's hosted ingest, so the React Native SDK works against it unchanged.
In-memory is for trying it and tests
In-memory storage is single-process and non-persistent — everything resets on restart. For production, implement the repository interfaces against your own database (next section).
4. Connect the React Native SDK
Point the client at your server with the self-hosted config — no org, project, or API key.
import { TeardownCore } from "@teardown/force-updates";
export const teardown = new TeardownCore({
config: { type: "self-hosted", ingestUrl: "http://localhost:4501" }, // your @teardown/server endpoint
environment_slug: "production",
// storageAdapter, deviceAdapter, ...
});In single-tenant mode the server reads only td-environment-slug and td-device-id (the SDK
sends both automatically) — never an API key, org, or project. See the
React Native getting started for the full client setup.
Go to production
In-memory is single-process and non-persistent. For production you implement the storage
repository interfaces against your own database — your schema, your migrations.
@teardown/server ships no database driver and no migrations.
What changes from the snippet above:
- Swap
createMemoryStorage(...)for your owncreateMyStorage(...)(it returns aTeardownStorage). - Drop the seed — your projects and environments now live in your database.
- Keep
tenantfor a single-tenant server, or omit it for a hosted/multi-tenant deployment (where clients sendtd-api-key/td-org-id/td-project-idand you implement theapiKeysport).
import { createMyStorage } from "./storage"; // implements TeardownStorage over your DB
const td = createTeardown({
storage: createMyStorage(process.env.DATABASE_URL!),
config: { sessionSecret: process.env.SESSION_SECRET! },
tenant: { orgId: process.env.TD_ORG_ID!, projectId: process.env.TD_PROJECT_ID! },
});See Bring your own database for worked repository examples (Postgres, MongoDB, Firestore) and the correctness checklist, and Advanced for caching, metrics, and running multiple instances.
Other runtimes
The fetch handler runs anywhere Web-standard Request/Response exists (Hono,
Cloudflare Workers, Deno, Next.js route handlers). A first-class Elysia plugin is also
shipped:
import { Elysia } from "elysia";
import { ElysiaErrors } from "@teardown/errors/elysia";
import { teardownElysia } from "@teardown/server/http/elysia";
new Elysia().use(ElysiaErrors).use(teardownElysia(td)).listen(4501);See Mounting for every runtime, Node/Express, CORS, and the request/response contract.
Common configuration
Only storage is required. The options you'll reach for most:
| Option | Default | Description |
|---|---|---|
config.sessionSecret | — | HMAC secret signing session tokens. Required (or pass a custom signer) |
tenant | — | { orgId, projectId } to run single-tenant: td-api-key/td-org-id/td-project-id are not required (hosted-only). Omit for multi-tenant |
config.sessionTokenExpiry | "15m" | Session-token lifetime (jose format) |
config.maxEventsPerBatch | 100 | Max events accepted per /v1/events batch |
cache | no-op | A CachePort (e.g. Redis) to accelerate version/session lookups |
logger / metrics | no-op | Observability ports |
See the API Reference for every option, the Teardown
object, and all services.
Next steps
- Core Concepts — the architecture and the two consumption modes
- Storage — use in-memory or implement the repository interfaces for any database
- Version Management — drive force-update status from your backend