# Server Discovery > **Area:** server-directory > **Status:** Active > **Last updated:** 2025-02-14 ## Overview Server discovery lets a signed-in user find public servers to join without knowing an exact name. It spans the signaling **server** (REST routes + CQRS query handlers that rank public servers) and the product **client** (`server-directory` domain API/facade plus the `/dashboard` landing and `/servers` browse page). It complements the existing free-text `GET /api/servers` search with two curated lists — **featured** and **trending**. ## Responsibilities - Server: rank and return public servers as **featured** (most-populated) and **trending** (most-recently-active) lists, capped per request. - Client: fetch those lists through `ServerDirectoryFacade` and render them via the reusable `app-server-browser` component on `/servers` and `/dashboard`. - It does NOT own: free-text search (`GET /api/servers`), join/access checks (`/api/servers/:id/join`), invites, or room signal-affinity. Discovery is read-only browsing; joining flows through existing paths. ## Key concepts - **Featured**: public servers ranked by membership count descending, ties broken by most recent `lastSeen` (`rankFeaturedServers`). - **Trending**: public servers ranked by most recent `lastSeen` descending, ties broken by membership count (`rankTrendingServers`). - **Discovery limit**: each route clamps `limit` to `[1, 50]` (`parseDiscoveryLimit`), default `12`. --- ## API Endpoints Both endpoints live in `server/src/routes/servers.ts` and **must be registered before** the parameterised `/:id` route, otherwise Express resolves `featured`/`trending` as a server id. ### `GET /api/servers/featured` - **Method**: GET - **Authentication**: None (public discovery) - **Rate Limiting**: No - **Query params**: `limit` (optional integer; clamped to `[1, 50]`, default `12`) ### `GET /api/servers/trending` - **Method**: GET - **Authentication**: None (public discovery) - **Rate Limiting**: No - **Query params**: `limit` (optional integer; clamped to `[1, 50]`, default `12`) ### Response Schema (both) ```json { "servers": "ServerInfo[] — enriched public servers (icon, channels, sourceId/sourceName/sourceUrl filled by the client API layer)", "total": "number — count of servers returned", "limit": "number — the effective clamped limit" } ``` `ServerInfo` matches the shape returned by `GET /api/servers` search results, so the client normalises and renders all three lists identically. ### Error Responses - **500 Internal Server Error**: query handler / persistence failure. --- ## Server internals - Routes delegate to CQRS query handlers `handleGetFeaturedServers` / `handleGetTrendingServers` (`server/src/cqrs/queries/handlers/`), dispatched via `GetFeaturedServers` / `GetTrendingServers` query types. - Ranking lives in `server/src/cqrs/queries/handlers/server-ranking.util.ts` (`rankFeaturedServers`, `rankTrendingServers`, `loadMembershipCounts`). Membership counts load in a single grouped query. - Results pass through the same `enrichServer()` step as search before serialisation. ## Client internals - `ServerDirectoryApiService.getFeaturedServers()` / `getTrendingServers()` call the routes through a shared private `getDiscoveryServers(path)` helper and normalise into `ServerInfo[]`. - `ServerDirectoryService` → `ServerDirectoryFacade` expose `getFeaturedServers()` / `getTrendingServers()` as the domain boundary. - `FindServersComponent` (`/servers`) composes **Recently active** (the user's saved rooms, capped at 6), **Featured**, and **Trending** sections, all rendered through `app-server-browser` with `[showMyServers]="true"`. - `DashboardComponent` (`/dashboard`) is a single-column landing page (max-width centered, no in-page sidebars): a header greeting (no emoji), a global search with `Ctrl+K` focus and localStorage-backed **Recent Searches** chips shown beneath it, three primary action cards (Find People → `/people`, Find Servers → `/servers`, Create Server → `/create-server` — one link each), and discovery panels **People you might know**, **Popular Servers**, **Your Friends**, and **Recently Active Servers**. Each list is capped at 5 (`DISCOVERY_LIMIT`). It loads `popularServers` on init from `getFeaturedServers(5)`, falling back to `getTrendingServers(5)` when featured is empty; reuses `app-friend-button` for Add and `app-user-avatar` for people rows. `peopleYouMightKnow` excludes existing friends (via `FriendService.friendIds()`); `friends` lists discovered people who are friends. "See all" header links route to the matching `/people` or `/servers` page (no duplicated footer links). Recent searches are recorded on Enter (deduped, most-recent-first, capped at 8) and persisted under `metoyou_dashboard_recent_searches`. - The servers-rail top button (`servers-rail.component`) is the **Dashboard** button (`lucideLayoutDashboard`, `title="Dashboard"`); its `goToDashboard()` handler deselects any active voice server and navigates to `/dashboard`. A **Create a server** button (`lucidePlus`, `data-testid="server-rail-create"`) sits below the saved-server icons and opens `app-create-server-dialog` (a Toju modal on desktop / bottom sheet on mobile) which dispatches `RoomsActions.createRoom` directly; the dashboard / `/create-server` route remains as an alternative entry point. Rail icons (`h-12 w-12`, `md:h-11 w-11`) animate their corner radius on hover and `:active` for a Discord-style squircle effect. - On mobile (`ViewportService.isMobile()`), `DashboardComponent`, `FindPeopleComponent` (`/people`), and `FindServersComponent` (`/servers`) each mount their page body inside a single `` slide next to `app-servers-rail` (rail `shrink-0`, content `flex-1` with a left border), mirroring the chat-room / DM-workspace mobile layout so the primary navigation rail stays reachable. The page body is shared between the desktop and mobile branches via an `` + `[ngTemplateOutlet]`, and each component declares `schemas: [CUSTOM_ELEMENTS_SCHEMA]` for the Swiper custom elements. ## Related - Product-client domain README: `toju-app/src/app/domains/server-directory/README.md` - People discovery (`/people`): `toju-app/src/app/domains/direct-message/README.md`