Type routes with the satisfies operator

TypeScript
// `satisfies` checks the shape while keeping literal route names intact.
type Route = { path: string; auth: boolean; title: string };

const routes = {
  home: { path: '/', auth: false, title: 'Home' },
  settings: { path: '/settings', auth: true, title: 'Settings' },
  admin: { path: '/admin', auth: true, title: 'Admin' },
} satisfies Record<string, Route>;

type RouteName = keyof typeof routes; // 'home' | 'settings' | 'admin'

function buildUrl(name: RouteName, params: Record<string, string> = {}): string {
  const route: Route = routes[name];
  const url = new URL(route.path, 'https://example.com');
  for (const [key, value] of Object.entries(params)) {
    url.searchParams.set(key, value);
  }
  return url.pathname + url.search;
}

console.log(buildUrl('settings', { tab: 'profile' })); // /settings?tab=profile
console.log(buildUrl('home')); // /
// buildUrl('missing'); // type error: "missing" is not a RouteName