# How Nitter Organizes Route Handlers: Modular Architecture with Jester

> Discover how Nitter organizes route handlers in discrete, feature-focused modules using Jester. Learn about router registration and endpoint initialization from the main entry point.

- Repository: [Zed/nitter](https://github.com/zedeus/nitter)
- Tags: architecture
- Published: 2026-08-29

---

**Nitter organizes route handlers into discrete, feature-focused modules under `src/routes/`, where each router registers its endpoints via Jester's `router` macro and is initialized through a `createXRouter` procedure called from the main entry point.**

Nitter, the privacy-focused Twitter alternative written in Nim, implements its HTTP endpoints using the **Jester** web framework. Understanding how Nitter route handlers are structured reveals a clean, modular architecture that separates concerns by feature while maintaining shared utilities for common operations like cookie handling and request validation.

## Central Entry Point and Router Registration

The orchestration of Nitter route handlers begins in **`src/nitter.nim`**, the application's main entry point. This file does not define routes directly; instead, it imports individual route modules and delegates endpoint registration to specialized procedures.

After loading the global configuration and initializing services (Redis cache, HTTP pools, and session management), `src/nitter.nim` calls specific router creation functions for each feature area:

```nim

# src/nitter.nim

import routes/[timeline, status, media, search, preferences, ...]

# ...configuration and service initialization...

createTimelineRouter(cfg)   # Registers all timeline-related endpoints

createStatusRouter(cfg)     # Registers tweet-status endpoints

createMediaRouter(cfg)      # Registers media serving endpoints

# ...additional router creation calls...

```

Each `createXRouter(cfg: Config)` procedure encapsulates the route definitions for a specific domain, keeping the main file clean and allowing developers to enable or disable entire features by commenting out a single line.

## Per-Feature Router Modules

Every major feature in Nitter resides in its own file under `src/routes/`, following a consistent pattern. Each module defines a **single router** using Jester's `router <name>` macro wrapped inside a `createXRouter*` procedure.

Inside each router, individual `get` and `post` statements define URL patterns, parameter validation logic, and handler bodies. This design ensures that related endpoints—such as user timelines, follower lists, and RSS feeds—are grouped logically within the same source file.

### Timeline Routes in `src/routes/timeline.nim`

The timeline router handles user profiles, follower/following pages, and list timelines. It defines parameterized routes that capture usernames and response types:

```nim
proc createTimelineRouter*(cfg: Config) =
  router timeline:
    get "/i/user/@user_id":
      respUserId()                # Redirects /i/user/<id> to username page

    get "/@name/@kind/?":         # Followers/following pages

      cond @"kind" in ["followers", "following"]
      # ...validation and rendering logic...

      resp renderMain(html, request, cfg, prefs, title)

    get "/@name/?@tab?/?":         # Main user timeline with optional tab

      # ...timeline retrieval logic...

      resp renderMain(pHtml, request, cfg, prefs, pageTitle(u), ...)

```

This router demonstrates **url pattern matching** with optional segments (`?`) and conditional validation using Jester's `cond` statement to restrict `@kind` to specific values.

### Status Routes in `src/routes/status.nim`

Individual tweet pages and edit history endpoints reside in **`src/routes/status.nim`**. This router includes validation logic directly in the route handler:

```nim
proc createStatusRouter*(cfg: Config) =
  router status:
    get "/@name/status/@id/?":
      let id = @"id"
      if id.len > 19 or id.any(c => not c.isDigit):
        resp Http404, showError("Invalid tweet ID", cfg)
      # ...tweet retrieval...

      let conv = await getTweet(id, getCursor(), sort)
      resp renderMain(html, request, cfg, prefs, title, desc, ...)

```

The router validates tweet ID format before processing, returning a 404 response with a standardized error page if validation fails.

## Shared Utilities in `router_utils.nim`

Common operations across Nitter route handlers are centralized in **`src/routes/router_utils.nim`**. This module provides reusable **templates** that keep individual router files concise while enforcing consistent behavior:

```nim
template requestPrefs*(): untyped {.dirty.} =
  getPrefs(cookies(request), params(request))

template getCursor*(): string =
  let cursor = @"cursor"
  decodeUrl(if cursor.len > 0: cursor else: @"max_position", false)

template showError*(error: string; cfg: Config): string =
  renderMain(renderError(error), request, cfg, requestPrefs(), "Error")

```

These helpers handle **cookie-based preference extraction**, **cursor-based pagination** for infinite scroll, and **standardized error rendering** across all routes.

## Execution Flow

The lifecycle of a request through Nitter's route handlers follows this sequence:

1. **Configuration Loading**: `src/nitter.nim` reads configuration and initializes global services
2. **Router Registration**: Each `createXRouter` procedure registers its routes with Jester's internal dispatcher
3. **Request Matching**: When an HTTP request arrives, Jester matches the path against registered patterns in order
4. **Handler Execution**: The matching Nim code block executes, accessing utilities from `router_utils.nim` as needed
5. **Response Generation**: Handlers call rendering functions from `src/views/` to generate HTML or redirect responses

## Summary

- **Nitter route handlers** are organized into single-responsibility modules under `src/routes/`, with each feature (timeline, status, media) having its own file
- The **Jester** web framework provides the underlying routing mechanism through the `router` macro and `get`/`post` DSL
- **`src/nitter.nim`** serves as the composition root, importing route modules and calling `createXRouter` procedures to register endpoints
- **Shared utilities** in `src/routes/router_utils.nim` provide templates for common tasks like cookie parsing, cursor extraction, and error handling
- Each router follows a consistent pattern: a public `createXRouter(cfg: Config)` procedure that encapsulates route definitions for a specific domain

## Frequently Asked Questions

### What web framework does Nitter use for its route handlers?

Nitter uses **Jester**, a Sinatra-like web framework for Nim. The framework provides the `router` macro and HTTP verb shortcuts (`get`, `post`, etc.) that define route handlers within each module.

### Where are the route handler modules located in Nitter's codebase?

All route handler modules reside in the **`src/routes/`** directory. Each `.nim` file in this directory corresponds to a specific feature area (timeline, status, media, search, preferences, embed, debug, etc.).

### How does Nitter share common logic across different route handlers?

Nitter centralizes shared functionality in **`src/routes/router_utils.nim`**, which provides templates like `requestPrefs` for cookie handling, `getCursor` for pagination, and `showError` for error page rendering. These utilities are imported and used across all route modules to ensure consistent behavior.

### What is the purpose of the `createXRouter` procedures in Nitter?

Each `createXRouter` procedure (e.g., `createTimelineRouter`, `createStatusRouter`) acts as a factory function that instantiates a Jester router for a specific feature domain. These procedures are called from `src/nitter.nim` during application startup, allowing the main entry point to compose the full routing table without knowing the internal details of each route.