# How the code-server Route System Handles Login, VS Code, Health, and Error Endpoints

> Explore the code-server route system and how it manages login, VS Code proxying, health checks, and error endpoints efficiently within its central registry for robust backend operations.

- Repository: [Coder/code-server](https://github.com/coder/code-server)
- Tags: internals
- Published: 2026-03-01

---

**The code-server route system centrally registers all HTTP and WebSocket endpoints in [`src/node/routes/index.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/index.ts), mounting specialized routers for login, health checks, and VS Code proxying, while attaching global error handlers last to catch unhandled exceptions.**

The **route system** in the `coder/code-server` repository organizes all server-side HTTP and WebSocket traffic through a layered Express architecture. Located under `src/node/routes`, the system separates concerns into modular routers that handle authentication, IDE proxying, health monitoring, and error rendering. Understanding this routing architecture is essential for debugging deployment issues or extending the server's functionality.

## Route Registration Architecture

The **`register`** function in [`src/node/routes/index.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/index.ts) serves as the central configuration point for the entire application. It initializes both an Express HTTP router (`app.router`) and a WebSocket router (`app.wsRouter`), applies shared middleware, and mounts individual route modules in a specific order.

### Shared Middleware Setup

Before mounting routes, the registration function disables the default `x-powered-by` header and installs cookie parsing middleware. It then attaches a **common middleware** that executes on every incoming request (except `/healthz`) to update the server's heartbeat state. According to lines **[66‑74](https://github.com/coder/code-server/blob/main/src/node/routes/index.ts#L66-L74)**, this middleware calls the heartbeat update logic to track server activity. Lines **[76‑80](https://github.com/coder/code-server/blob/main/src/node/routes/index.ts#L76-L80)** populate essential request properties including `req.args`, `req.heart`, `req.settings`, `req.updater`, and `req.cookieSessionName`, making configuration and state accessible to all downstream route handlers.

### Mounting Individual Routers

The registration function mounts route modules at specific paths based on the authentication configuration. Lines **[54-L56](https://github.com/coder/code-server/blob/main/src/node/routes/index.ts#L54-L56)** attach the health router:

```typescript
app.router.use("/healthz", health.router)
app.wsRouter.use("/healthz", health.wsRouter.router)

```

For authentication, the system conditionally mounts the login router. When `args.auth === AuthType.Password`, lines **[57-L59](https://github.com/coder/code-server/blob/main/src/node/routes/index.ts#L57-L59)** register the login and logout endpoints:

```typescript
app.router.use("/login", login.router)
app.router.use("/logout", logout.router)

```

If password authentication is disabled, the system redirects users instead of rendering login pages.

The VS Code proxy mounts at multiple prefixes to ensure compatibility. Lines **[70-L73](https://github.com/coder/code-server/blob/main/src/node/routes/index.ts#L70-L73)** loop through `["/vscode", "/"]` to mount the VS Code router on both paths:

```typescript
for (const routePrefix of ["/vscode", "/"]) {
  app.router.use(routePrefix, vscode.router)
  app.wsRouter.use(routePrefix, vscode.wsRouter.router)
}

```

### Error Handler Attachment

The system mounts error handlers **after** all other routes to ensure they catch unhandled exceptions. Lines **[75-L80](https://github.com/coder/code-server/blob/main/src/node/routes/index.ts#L75-L80)** first add a catch-all route that throws a 404 error for undefined paths, then attach the HTTP error handler. Lines **[80-L81](https://github.com/coder/code-server/blob/main/src/node/routes/index.ts#L80-L81)** similarly attach the WebSocket error handler:

```typescript
app.router.use(() => { throw new HttpError("Not Found", HttpCode.NotFound); })
app.router.use(errorHandler)
app.wsRouter.use(wsErrorHandler)

```

## Login Endpoint Handling

The login module in **[`src/node/routes/login.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/login.ts)** manages both page rendering and credential validation, implementing rate limiting to prevent brute-force attacks.

### GET /login Page Rendering

When users access `GET /login`, the router invokes the **`getRoot`** function to serve the authentication page. Lines **[28‑53](https://github.com/coder/code-server/blob/main/src/node/routes/login.ts#L28-L53)** show this function reading [`src/browser/pages/login.html`](https://github.com/coder/code-server/blob/main/src/browser/pages/login.html), injecting internationalization strings, and returning the final HTML:

```typescript
router.get("/", async (req, res) => {
  res.send(await getRoot(req))
})

```

### POST /login Validation and Rate Limiting

The POST handler validates credentials using a **`RateLimiter`** defined at lines **[12‑25](https://github.com/coder/code-server/blob/main/src/node/routes/login.ts#L12-L25)**. This limiter allows 2 attempts per minute and 12 per hour. Lines **[77-L80](https://github.com/coder/code-server/blob/main/src/node/routes/login.ts#L77-L80)** check the limiter before processing:

```typescript
if (!limiter.canTry()) { throw new Error(i18n.t("LOGIN_RATE_LIMIT")); }

```

Lines **[86-L92](https://github.com/coder/code-server/blob/main/src/node/routes/login.ts#L86-L92)** validate the password against environment variables or configuration:

```typescript
const { isPasswordValid, hashedPassword } = await handlePasswordValidation(...);

```

### Session Cookie Management

Upon successful validation, lines **[97-L99](https://github.com/coder/code-server/blob/main/src/node/routes/login.ts#L97-L99)** set a signed session cookie and redirect the user:

```typescript
res.cookie(req.cookieSessionName, hashedPassword, getCookieOptions(req));
return redirect(req, res, to, { to: undefined });

```

Failed attempts invoke `limiter.removeToken()` at lines **[103-L106](https://github.com/coder/code-server/blob/main/src/node/routes/login.ts#L103-L106)** before throwing an "Incorrect Password" error.

## Health-Check Endpoint

The health module in **[`src/node/routes/health.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/health.ts)** provides both HTTP and WebSocket interfaces for monitoring server vitality, using the `Heart` class instantiated during registration.

### HTTP Health Probe

The HTTP endpoint at `GET /healthz` returns a JSON payload indicating whether the server is alive. Lines **[6‑11](https://github.com/coder/code-server/blob/main/src/node/routes/health.ts#L6-L11)** implement this check:

```typescript
router.get("/", (req, res) => {
  res.json({
    status: req.heart.alive() ? "alive" : "expired",
    lastHeartbeat: req.heart.lastHeartbeat,
  })
})

```

### WebSocket Health Updates

For real-time monitoring, the WebSocket endpoint at `ws://…/healthz` pushes health status updates whenever a client sends a message. Lines **[15‑28](https://github.com/coder/code-server/blob/main/src/node/routes/health.ts#L15-L28)** handle the upgrade and message events:

```typescript
wsRouter.ws("/", async (req) => {
  wss.handleUpgrade(req, req.ws, req.head, (ws) => {
    ws.addEventListener("message", () => {
      ws.send(JSON.stringify({
        event: "health",
        status: req.heart.alive() ? "alive" : "expired",
        lastHeartbeat: req.heart.lastHeartbeat,
      }))
    })
    req.ws.resume()
  })
})

```

## VS Code Proxy Endpoint

The VS Code router in **[`src/node/routes/vscode.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/vscode.ts)** dynamically loads the VS Code web server and proxies all remaining traffic after authentication checks.

### Dynamic Server Loading

The router uses **`ensureVSCodeLoaded`** middleware to lazy-load the VS Code server via dynamic `import()`. This occurs only when the first authenticated request arrives, reducing startup overhead. The middleware verifies the server is ready before calling `vscodeServer!.handleRequest()`.

### HTTP and WebSocket Proxying

Lines **[41-L44](https://github.com/coder/code-server/blob/main/src/node/routes/vscode.ts#L41-L44)** capture all remaining HTTP traffic using a catch-all regex:

```typescript
router.all(/.*/, ensureAuthenticated, ensureVSCodeLoaded, async (req, res) => {
  vscodeServer!.handleRequest(req, res)
})

```

For WebSocket connections, lines **[46-L52](https://github.com/coder/code-server/blob/main/src/node/routes/vscode.ts#L46-L52)** wrap the socket and forward it to VS Code:

```typescript
wsRouter.ws(/.*/, ensureOrigin, ensureAuthenticated, ensureVSCodeLoaded, async (req) => {
  const wrappedSocket = await socketProxyProvider.createProxy(req.ws)
  vscodeServer!.handleUpgrade(req, wrappedSocket as net.Socket)
  req.ws.resume()
})

```

Both paths enforce authentication via **`ensureAuthenticated`**, which validates the session cookie or redirects to `/login`.

### Auxiliary Routes

The same file provides supporting endpoints:
- **`GET /manifest.json`** (lines **[74-112](https://github.com/coder/code-server/blob/main/src/node/routes/vscode.ts#L74-L112)**): Generates a Progressive Web App manifest
- **`POST /mint-key`** (lines **[13-39](https://github.com/coder/code-server/blob/main/src/node/routes/vscode.ts#L13-L39)**): Supplies a per-instance secret for web serving

## Error Handling Strategy

The errors module in **[`src/node/routes/errors.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/errors.ts)** provides final catch-all handlers for both HTTP and WebSocket routes, ensuring consistent error formatting.

### HTTP Error Handler

The **`errorHandler`** function inspects thrown errors to determine the appropriate status code. It maps filesystem errors (`ENOENT`, `EISDIR`) to **404**, uses the error's `statusCode` property if present, and defaults to **500** for unknown errors. Lines **[48‑61](https://github.com/coder/code-server/blob/main/src/node/routes/errors.ts#L48-L61)** render an HTML error page from [`src/browser/pages/error.html`](https://github.com/coder/code-server/blob/main/src/browser/pages/error.html) when the client accepts `text/html`:

```typescript
// HTML rendering for browser requests
res.status(statusCode).send(html)

```

Lines **[63-66](https://github.com/coder/code-server/blob/main/src/node/routes/errors.ts#L63-66)** return JSON for API clients:

```typescript
// JSON response for XHR/fetch requests
res.status(statusCode).json({ error: message })

```

### WebSocket Error Handler

The **`wsErrorHandler`** (lines **[70-89](https://github.com/coder/code-server/blob/main/src/node/routes/errors.ts#L70-89)**) sends a plain HTTP response over the upgraded socket for protocol-level errors and logs severe exceptions to the console, preventing WebSocket crashes from destabilizing the server.

## Practical Usage Examples

### Authenticating via Login Endpoint

```bash

# Store cookies for the session

curl -c cookie.jar -b cookie.jar \
  -X POST \
  -d "password=mySecret" \
  "https://my-code-server/login?to=%2F"

```

### Checking Server Health

```bash

# HTTP health probe

curl https://my-code-server/healthz

# Output: {"status":"alive","lastHeartbeat":1698627200}

```

```javascript
// WebSocket health monitoring
const ws = new WebSocket("wss://my-code-server/healthz");
ws.onopen = () => ws.send("ping");
ws.onmessage = e => console.log(JSON.parse(e.data));
// Output: {event:"health",status:"alive",lastHeartbeat:...}

```

### Accessing VS Code After Authentication

```bash

# Use stored cookie to access IDE

curl -b cookie.jar https://my-code-server/

# Request proxied to VS Code server

```

### Triggering Error Responses

```bash

# JSON error for API clients

curl -H "Accept: application/json" https://my-code-server/nonexistent

# Output: {"error":"Not Found"}

# HTML error page for browsers

curl -H "Accept: text/html" https://my-code-server/nonexistent

# Returns rendered error.html

```

## Summary

- **Central Registration**: The `register` function in [`src/node/routes/index.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/index.ts) orchestrates all routing, mounting health checks first, login/logout when password auth is enabled, VS Code proxies on multiple paths, and error handlers last.
- **Authentication Flow**: Login routes enforce rate limiting (2/min, 12/hour) and set signed session cookies, while VS Code routes use `ensureAuthenticated` middleware to protect IDE access.
- **Dual Protocol Health Checks**: The `/healthz` endpoint provides stateless HTTP probes and stateful WebSocket push updates using the shared `Heart` instance.
- **Dynamic VS Code Loading**: The VS Code router lazy-loads the web server on first request and proxies all HTTP and WebSocket traffic through authenticated middleware chains.
- **Consistent Error Rendering**: Error handlers in [`src/node/routes/errors.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/errors.ts) map filesystem and HTTP errors to appropriate status codes, returning HTML for browsers and JSON for API clients.

## Frequently Asked Questions

### How does code-server protect the login endpoint from brute-force attacks?

The login router implements a **`RateLimiter`** class (lines **[12‑25](https://github.com/coder/code-server/blob/main/src/node/routes/login.ts#L12-L25)** in [`src/node/routes/login.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/login.ts)) that restricts attempts to 2 per minute and 12 per hour. Each failed validation consumes a token via `limiter.removeToken()`, and depleted tokens trigger a "Login Rate Limit" error before credential validation occurs.

### What distinguishes the HTTP health check from the WebSocket health check?

The HTTP endpoint at `/healthz` returns a static JSON snapshot of the server's alive status and last heartbeat timestamp (lines **[6‑11](https://github.com/coder/code-server/blob/main/src/node/routes/health.ts#L6-L11)**). The WebSocket endpoint (lines **[15‑28](https://github.com/coder/code-server/blob/main/src/node/routes/health.ts#L15-L28)**) maintains a persistent connection and pushes updated health status JSON only when the client sends a message, enabling real-time monitoring without polling overhead.

### How does the route system handle requests to undefined paths?

After mounting all specific routes, [`src/node/routes/index.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/index.ts) lines **[75-L80](https://github.com/coder/code-server/blob/main/src/node/routes/index.ts#L75-L80)** attach a catch-all middleware that throws an `HttpError` with status 404. This error propagates to the `errorHandler` in [`src/node/routes/errors.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/errors.ts), which renders a friendly HTML page or returns JSON depending on the `Accept` header.

### Why are VS Code routes mounted on both "/" and "/vscode" prefixes?

The registration loop in [`src/node/routes/index.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/index.ts) (lines **[70-L73](https://github.com/coder/code-server/blob/main/src/node/routes/index.ts#L70-L73)**) mounts the VS Code router on both paths to ensure backward compatibility and flexible deployment configurations. This allows users to access the IDE at the root domain or via the explicit `/vscode` subdirectory without changing the underlying route logic.