> ## Documentation Index
> Fetch the complete documentation index at: https://docs.folksbase.joselito.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Backend Architecture

> The layered architecture pattern used in the Hono API and why it exists.

The folksbase API (`apps/api`) is built with Hono v4 and follows a strict layered architecture. Every request flows through the same path: route → service → repository → database. No shortcuts, no exceptions.

## Why Layers?

Without clear boundaries, codebases tend to accumulate "god functions" — route handlers that parse requests, run business logic, construct SQL queries, and format responses all in one place. That works for a while, until:

* You need to reuse business logic in a background job (but it's tangled with HTTP response formatting)
* You need to test a query (but it's buried inside a 200-line route handler)
* A new developer can't tell where to put their code

The layered pattern solves this by giving each concern a home. It's not the only way to structure a backend, but it's predictable and easy to enforce.

## The Layers

```mermaid theme={"dark"}
graph TD
    A[HTTP Request] --> B[Hono Route Handler]
    B --> C[Service Layer]
    C --> D[Repository Layer]
    D --> E[Drizzle + Neon Postgres]
    
    B -.- B1["routes/*.ts — HTTP concerns only"]
    C -.- C1["services/*.ts — business logic"]
    D -.- D1["repositories/*.ts — SQL queries only"]
```

### Route Handlers (`routes/*.ts`)

Routes handle HTTP concerns and nothing else:

* Parse and validate the request (using Zod via `zValidator`)
* Call the appropriate service method
* Return the HTTP response with `c.json()`

Routes never import from `@folksbase/db`. They never contain business logic beyond basic input parsing.

```typescript theme={"dark"}
// ✅ Route handler — thin, delegates to service
.get("/:id", zValidator("param", z.object({ id: z.string().uuid() })),
  async (c) => {
    const { id } = c.req.valid("param");
    const { workspaceId } = c.get("user");
    const contact = await contactsService.findById(workspaceId, id);
    return c.json(contact);
  }
)
```

### Service Layer (`services/*.ts`)

Services contain business logic and orchestration:

* Validate business rules (e.g., "can this user perform this action?")
* Coordinate between multiple repositories
* Transform data between layers

Services never construct HTTP responses. They return data or throw errors — the route handler decides how to format the response.

### Repository Layer (`repositories/*.ts`)

Repositories are the only layer that talks to the database:

* Construct SQL queries using the Drizzle query builder
* Return typed results
* No business logic — just data access

```typescript theme={"dark"}
// ✅ Repository — SQL only, no business logic
export async function findById(workspaceId: string, id: string) {
  const result = await db.query.contacts.findFirst({
    where: and(eq(contacts.id, id), eq(contacts.workspace_id, workspaceId)),
    with: { tags: true },
  });
  return result ?? null;
}
```

### Layer Rules

These rules are enforced in code review. Violating them means a rejected PR.

| Rule                                      | Why                                         |
| ----------------------------------------- | ------------------------------------------- |
| Routes never import from `@folksbase/db`  | Forces all data access through repositories |
| Services never construct HTTP responses   | Keeps services reusable in background jobs  |
| Repositories never contain business logic | Keeps queries simple and testable           |
| Routes never contain business logic       | Keeps route handlers thin and readable      |

## Middleware Stack

Middleware runs in a specific order for every request:

```mermaid theme={"dark"}
graph LR
    A[Error Handler] --> B[CORS]
    B --> C[Hono Logger]
    C --> D[Rate Limiter]
    D --> E[Route Handler]
```

1. **Error Handler** (`app.onError`) — catches all unhandled errors and returns consistent `{ code, message }` responses. Handles Zod validation errors and auth errors specifically.
2. **CORS** — validates the request origin against a configurable allowlist (supports wildcard subdomains).
3. **Hono Logger** — logs every request with method, path, and response time.
4. **Rate Limiter** — applied to all `/api/*` routes. 100 requests per 60 seconds per user. A stricter upload limiter (5 per 10 minutes) applies to CSV uploads.

### Auth Middleware

Auth is applied per-route, not globally. This is intentional — some routes (health check, OpenAPI spec, webhooks) don't need authentication.

When applied, the auth middleware validates the Supabase JWT from the `Authorization` header and attaches the user context:

```typescript theme={"dark"}
c.set('user', { userId: user.id, workspaceId })
// Access in route handlers:
const { userId, workspaceId } = c.get('user')
```

## OpenAPI Documentation

Every API route is self-documenting. The API auto-generates an OpenAPI 3.1 spec from route definitions using `hono-openapi`:

* **`GET /api/openapi.json`** — machine-readable OpenAPI spec
* **`GET /api/docs`** — interactive Scalar API reference UI

Routes use `describeRoute()` for response schemas and `zValidator()` for request validation. This means the documentation is always in sync with the actual code — there's no separate spec file to maintain.

## Why Hono?

A few reasons folksbase uses Hono instead of Express or Fastify:

1. **TypeScript-native.** Hono was built for TypeScript from day one. Route handlers, middleware, and context are all fully typed.
2. **Lightweight.** No heavy dependency tree. The core is tiny and fast.
3. **Portable.** Hono runs on Node, Bun, Deno, and Cloudflare Workers. If the deployment target changes, the code doesn't.
4. **Built-in OpenAPI support.** The `hono-openapi` integration generates specs directly from route definitions — no decorators or separate config files.

## Why Neon with HTTP Driver?

The database package uses Neon's HTTP driver (`drizzle-orm/neon-http`) instead of the WebSocket driver. This was a deliberate choice:

* The API runs on **Render.com**, where persistent WebSocket connections to Neon were unreliable
* The HTTP driver works over standard HTTPS requests — simpler, more compatible with Render's infrastructure
* For a request-response API (not a long-running connection pool), HTTP is the right fit

## What's Next?

<CardGroup cols={2}>
  <Card title="Frontend Architecture" icon="browser" href="/architecture/frontend">
    RSC-first approach and data fetching patterns.
  </Card>

  <Card title="Database Schema" icon="database" href="/architecture/database">
    Tables, relationships, and naming conventions.
  </Card>
</CardGroup>
