Architecture
Rewind is a single-user, local-only API client. It is built as a Bun workspace with three packages and two runnable services. This page is the map of the codebase.
Packages
.
├── apps/
│ ├── api/ @rewind/api # Bun + Elysia HTTP server, request proxy, SQLite
│ └── web/ @rewind/web # SvelteKit UI
└── docs/ @rewind/docs # VitePress site (this documentation)All three are workspaces under the root package.json. The root bun.lock is the single source of truth for dependencies; workspace packages do not have their own lockfiles. Shared versions (Elysia, Eden, Bun types, TypeScript) are pinned in the root workspaces.catalog.
Runtime services
Two services run side by side in development:
- API at
http://127.0.0.1:8000. Listens on the loopback interface by default. Binds to the loopback for safety — there is no authentication, and the proxy will relay any HTTP request the user can author. - Web app at
http://localhost:5173. The SvelteKit dev server. A Vite proxy inapps/web/vite.config.tsforwards/api/*to the API so the web app can use relative URLs.
The release build fuses these into a single executable that serves the web bundle on the same port as the API.
Data flow
A request goes through this pipeline:
graph LR
UI[SvelteKit UI] -- Eden Treaty --> Elysia
Elysia -- Elysia router --> Handler
Handler -- SQLite --> DB[(SQLite)]
Handler -- fetch --> Upstream[Upstream API]
Handler -- storage paths --> FS[(Attachments / Response bodies / Logs)]
Handler -- response --> Elysia
Elysia -- typed response --> UIKey points:
- The web app never calls the upstream API directly. It always goes through the local proxy, which avoids CORS and lets the local proxy apply its own CORS allowlist.
- The proxy persists every sent request and its response to the local database, so history is built up automatically.
- Variables are resolved on the server, just before the upstream call. The client sends the unresolved draft; the server returns the resolved URL and the resolved headers.
- Secrets are redacted before they touch the database. The redaction list lives in
apps/api/src/services/request-runner.ts(redactPairsandredactDraft) and coversAuthorization,Proxy-Authorization,Cookie,Set-Cookie,X-API-Key, and any pair whose key matchespassword|secret|token|authorization|cookie.
Backend modules
The API is organized around three concerns: a thin composition root, a set of core domain services, and a set of module route handlers. The whole package uses the @/ TypeScript path alias (configured in apps/api/tsconfig.json) so the relative-import chains are short.
The API lives under apps/api/src/:
Process & composition
index.ts— process bootstrap, signal handling, signal traps, and serving the bundled web build when one is present.app.ts— the side-effect-free Elysia app factory. It instantiates the core services, wires up the HTTP middlewares, anduse()s everymodules/*route. Exports theApptype that the web app imports.config/config.ts—loadConfig()and theAppConfig/ENVconstants. Owns all env-var resolution and defaults (HOST,PORT,REWIND_DATA_DIR,SQLITE_PATH,WEB_BUILD_PATH,REWIND_OPEN_BROWSER,DEFAULT_APP_VERSION,DEFAULT_ALLOWED_ORIGINS).runtime-paths.ts— resolves the data directory, the database path, the migrations directory, and the bundled-web path. Includes the one-time legacy migration from olderapps/api/data/rewind-api-studio.sqliteworkspaces.cli.ts— the CLI runner entrypoint, exposed asbun run cli ….
Core services (core/<domain>/<domain>.service.ts)
Each domain owns its own service module. Services are plain factory functions: coreService(drizzle, …deps) → { …methods }. They are the only thing that talks to the database; route handlers never call Drizzle directly.
core/requests/— collections, folders, saved requests, tree mutations.core/env/— environments and variables; activation, duplicate, lookup of the active environment's variable map.core/history/— history entries, response body storage, retention, listing/filtering.core/execution/— composing a draft, resolving variables, applying auth, sending throughfetch, persisting to history. Owns the v1 (executeOnce) and v2 (executeStream) execution flows.core/attachments/— uploaded files: create, lookup, delete, garbage-collect.core/cookies/— the cookie jar: list, upsert fromSet-Cookie, clear.core/settings/— single-row key/value settings + the typednetworkandstoragesettings.core/workspace/— full-workspace import/export, archive pack/unpack helpers.core/oauth/— in-memory OAuth token cache (keyed bytokenUrl + clientId + scope).core/execution.types.ts,core/env.types.ts,core/history.types.ts,core/requests/request.types.ts,core/workspace/workspace.types.ts— the domain TypeScript types for each service. They are re-exported throughtypes.ts→types/domain.tsso consumers can import from one place.
HTTP layer (http/)
http/response.ts— the shared error contract. Exports theApiErrorCodeunion (validation_error,request_validation_error,not_found,forbidden,conflict,internal_error), theApiErrorBodyshape, and thenotFound/errorEnvelopehelpers. Every route uses this to produce error responses.http/middleware/session.ts— CORS allowlist + the same-siterewind_sessioncookie that guards mutating requests. See Troubleshooting → Local session is not initialized.http/middleware/logging.ts— request/response logging wired to the local logger.http/middleware/errors.ts— uniform error response wrapping.
Route modules (modules/<resource>/index.ts)
One Elysia sub-app per resource, all with name: "rewind.<resource>". Each module file is intentionally thin: it parses the Elysia model, delegates to the matching core/* service, and returns the result. The app.ts composition root is the single place that wires the modules together.
modules/health—GET /health.modules/workspace—GET /api/v1/workspace(the workspace summary used by the sidebar on boot).modules/collection— collections, folders, andPATCH /api/v1/workspace/treefor bulk reordering.modules/request— individual request CRUD:GET/PUT/DELETE /api/v1/requests/:id,POST /api/v1/requests/:id/duplicate,POST /api/v1/collections/:id/requests.modules/execution— the actual sending pipeline:POST /api/v1/requests/send,POST /api/v2/requests/send,DELETE /api/v1/executions/:id,POST /api/v1/runner.modules/environment— environment CRUD.modules/history—GET/DELETE /api/v1/history, the v1/v2GET /api/{v1,v2}/history/:id, andGET /api/v2/history/:id/body(downloads the stored response body).modules/attachment—POST /api/v1/attachments(multipart, max 100 MB),GET /api/v1/attachments/:id,DELETE /api/v1/attachments/:id.modules/cookie—GET /api/v1/cookies?jarId=…,DELETE /api/v1/cookies?jarId=….modules/settings—GET/PUT /api/v1/settings/network,GET/PUT /api/v1/settings/storage.modules/importer—POST /api/v1/import/{openapi,postman,workspace},GET /api/v1/export/workspace,GET /api/v1/export/postman/:id.modules/archive—GET /api/v2/workspace/archive,POST /api/v2/workspace/archive/{preview,restore}.modules/diagnostic—GET /api/v1/diagnostics/archive.
Each module has a sibling model.ts that exports an Elysia …ModelRegistry. The module's index.ts does .use(<Registry>) and then references the model names (body: "Request.SendInput", response: "Request.SendResponseV2", etc.) by string. Shared types (like Api.Error and Api.IdParams) live in modules/shared.model.ts and are registered where they are needed.
Persistence
db/schema.ts— Drizzle table definitions. The source of truth for the data model; see the Data model section below.db/client.ts— the Drizzle client setup. SetsPRAGMA foreign_keys = ON,PRAGMA journal_mode = WAL, opens the SQLite file, and runs the Drizzle migrations fromapps/api/drizzle/.db/repository.ts— only theAttachmentReferenceErrorandRevisionConflictErrorerror classes live here now. The typed methods that used to live here were moved into the per-domain services incore/*.
Services (services/)
Pure functions that the route modules call into. They don't know about HTTP.
services/request-runner.ts— variable resolution, auth application, body construction, the v1runRequestfunction, and the redaction helpers (redactPairs,redactDraft).services/openapi-importer.ts— OpenAPI 3 import.services/postman-importer.ts— Postman v2.1 import and export.services/workspace-archive.ts—.rewindarchive packing, unpacking, checksumming, the safety-backup flow, and the diagnostic archive.services/logger.ts— re-exports the rolling text logger; the implementation lives ininfra/logging/logger.ts.
Infra
infra/logging/logger.ts— theLocalLoggerclass. Rolling text log under<data-dir>/logs/rewind.logwith redaction.
Lib
lib/http.ts,lib/variables.ts— small utilities used by the services and the request runner.
The App type is exported through the workspace package boundary as @rewind/api/app. The web app imports it through the workspace:* dependency, so the TypeScript types are inferred end-to-end without bundling any server runtime.
Frontend modules
The web app lives under apps/web/src/:
routes/+page.svelte— the workspace page that ties everything together.routes/+layout.svelteand+layout.ts— the SvelteKit shell.lib/api/client.ts— the typed Eden Treaty client. The exportedapiobject is what the rest of the app uses.lib/api/mappers.ts— maps between the API's DTOs and the in-memory draft types. Svelte 5 runes are not in the wire shapes; the mappers are the boundary.lib/api/types.ts— Treaty-derived type aliases, kept in one place so other modules can import them without round-tripping throughclient.ts.lib/api/index.ts— the barrel that re-exportsapi,ApiClientError, the Treaty types, and the mappers. Most of the app imports from here.lib/state/*.svelte.ts— Svelte 5 runes-based state. One file per concern:collections,documents,environment,request,response.request.svelte.tsowns the runes state; the non-runes draft types and constructors live in the same file (the olderstate/request.tshas been folded in).lib/components/*.svelte— UI components. The biggest ones aresidebar,request-panel,response-panel,url-bar,body-editor, andtop-bar. The newMethodBadgecomponent is the color-coded method label used in tabs, the request panel, the sidebar, and the history list.lib/components/ui/*— the local component library (button, input, dialog, sheet, tabs, table, etc.), each in its own folder.lib/theme/tokens.cssandlib/theme/theme.svelte.ts— the design tokens (surface, subtle-foreground, per-method colors) and the theme toggle.lib/utils/*.ts— small utilities (formatting, code generation, HTTP helpers).
Data model
The schema is the source of truth for the data model. The ten tables are:
collections— a top-level groupfolders— a sub-grouping inside a collection (or another folder), with a self-referentialparentIdrequests— a saved request, withmethod,url, and JSON-encodedheaders,queryParams,body,authenvironments— a named variable bundle, with exactly oneis_active = 1environment_variables— the variables inside an environment, with a unique key per environmenthistory— every sent request and its outcomeresponse_bodies— the on-disk location of every stored response body, with astorage_stateofstored/omitted_limit/missingsettings— single-row key/value store for network, storage, and other global settingsattachments— uploaded files, with the file's path on diskcookies— the cookie jar, keyed by(jarId, domain, path, name)
The schema is in apps/api/src/db/schema.ts; generated SQL migrations are in apps/api/drizzle/. After editing the schema, run bun run db:generate to produce a new migration.
Why a local backend at all
A browser cannot reliably send requests to arbitrary APIs because CORS blocks many targets. The local proxy also lets Rewind:
- Read the local cookie jar, response bodies, and attachments without going through the browser's storage.
- Apply its own CORS allowlist so the local API only serves the local UI.
- Persist every request to the local database for history.
- Resolve
{{variables}}consistently between the URL bar and the actual upstream call.
The local backend is the simplest design that meets these requirements. It binds to 127.0.0.1 and is intended for local single-user development.
What's next?
- Contributing — the developer workflow
- Request schema — the wire types
- API surface — the routes