Application URLs
Give email, workers and HTTP routes one trusted application address. A URL resolver is not a redirect policy.
On this page
Source-backed MarkdownConfigure the browser-facing URL
Set new App({ url: { baseUrl: "https://your-app.example" } }), or provide APP_URL. Resolution is explicit baseUrl, then APP_URL, then PUBLIC_APP_URL, then localPort. The frontend’s development port may differ from the backend port.
The service requires an HTTP(S) base or a valid local port. It does not read request Host headers. A configured environment base wins over the local fallback, so keep production values out of a local test shell.
Copy the example
Use the independent application and tools from Installation. No SQL or running server is needed.
mkdir -p examples
cp -R node_modules/@db3.ai/app/src/url/examples/. examples/Run the links
Expect the relative path under /workspace/, the root-relative path under the origin, both rejection flags true, and the repaired sign-in URL.
npx tsx examples/runApplicationLinks.tsUse the canonical generator
app().url.baseUrl is normalized without trailing slashes. to("notes/one") resolves beneath a configured base path; to("/notes/one") resolves from the origin root. Query strings and fragments follow the standard URL rules.
An absolute or protocol-relative argument can change origin. The example’s applicationLink() is an application-owned guard for root-relative same-origin redirects, not another framework method. Never treat to() as permission to fetch a URL or redirect a user.
import { pathToFileURL } from 'node:url';
import { App } from '@db3.ai/app/server';
import { UrlGenerator } from '@db3.ai/app/url';
/**
* Builds a link from an app-owned path, rejecting another origin.
*
* This is an example redirect policy, not a method supplied by UrlGenerator.
* @param url - Trusted application URL generator.
* @param path - Root-relative path to resolve.
* @returns Same-origin absolute URL.
*/
export function applicationLink(url: UrlGenerator, path: string): string {
if (!path.startsWith('/') || path.startsWith('//')) throw new Error('Use a root-relative application path.');
const resolved = url.to(path);
if (new URL(resolved).origin !== new URL(url.baseUrl).origin) throw new Error('Link must remain on the application origin.');
return resolved;
}
/** Exercises canonical links, subpath resolution, invalid configuration and repair. */
export async function runApplicationLinks() {
const application = new App({ url: { baseUrl: 'https://notes.example.test/workspace/' } });
try {
const url = application.url;
let invalidBaseRejected = false;
try { new UrlGenerator({ baseUrl: 'file:///private/notes' }); } catch { invalidBaseRejected = true; }
let externalRejected = false;
try { applicationLink(url, '//outside.example.test/'); } catch { externalRejected = true; }
return { base: url.baseUrl, relative: url.to('notes/one'), root: applicationLink(url, '/notes/one'), externalRejected, invalidBaseRejected, repaired: new UrlGenerator({ baseUrl: 'https://notes.example.test' }).to('/sign-in') };
} finally { await application.close(); }
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) console.log(JSON.stringify(await runApplicationLinks(), null, 2));
Keep request URLs separate
The canonical address is application configuration, not mutable per-request state. SSR receives each request URL on its own render context. Background jobs can use the same canonical service without an HTTP server.
There is no named-route registry, signed URL service, URL-shortener or remote-fetch allowlist here. Use explicit paths, and document any application-specific routing policy.
Testing
Create a tests/url directory and save the test below as runApplicationLinks.test.ts. It imports the example you copied into examples/. Keep the same folder layout so the relative import resolves.
Run from the application root with the development dependencies from Installation. These are consumer tests, not commands that assume a framework checkout.
import { expect, it, vi } from 'vitest';
import { UrlGenerator } from '@db3.ai/app/url';
import { applicationLink, runApplicationLinks } from '../../examples/runApplicationLinks';
it('resolves trusted canonical links and repairs invalid configuration', async () => {
expect(await runApplicationLinks()).toEqual({ base: 'https://notes.example.test/workspace', relative: 'https://notes.example.test/workspace/notes/one', root: 'https://notes.example.test/notes/one', externalRejected: true, invalidBaseRejected: true, repaired: 'https://notes.example.test/sign-in' });
const url = new UrlGenerator({ baseUrl: 'https://notes.example.test' });
for (const path of ['https://outside.example.test', '//outside.example.test', '/\\outside.example.test', 'javascript:alert(1)']) expect(() => applicationLink(url, path)).toThrow();
});
it('requires configuration and checks local ports when environment fallbacks are absent', () => {
vi.stubEnv('APP_URL', ''); vi.stubEnv('PUBLIC_APP_URL', '');
try {
expect(() => new UrlGenerator()).toThrow('not configured');
expect(() => new UrlGenerator({ localPort: 0 })).toThrow('local port');
expect(new UrlGenerator({ localPort: 8717 }).baseUrl).toBe('http://localhost:8717');
} finally { vi.unstubAllEnvs(); }
});
Run and extend the test
The tests clear URL environment fallbacks when checking missing configuration and restore them afterwards. Change the base subpath, test your redirect inputs and keep rejection of external origins.
npx vitest run tests/url/runApplicationLinks.test.ts
npx tsc --noEmit --target ES2022 --module ESNext --moduleResolution Bundler --types node --skipLibCheck examples/*.tsCoverage and API
Tested: explicit base, subpaths, local fallback, invalid/missing configuration, guarded external references and recovery. Reference: the two configuration options and complete generator. No external service is contacted.
Builds canonical links, checks root versus relative paths, rejects invalid config and external redirects, then repairs the base.
The guide test passes against the real framework components.packages/app/src/url/tests/examples/runApplicationLinks.test.tsThis test command requires the framework repository. Use the walkthrough commands in an installed application.
Environment: Node.js only; no HTTP requests, database or external provider.