Files
claudecodeui/docs/backend/architecture.md

14 KiB

Backend Architecture

Goal

This structure keeps the Day 1 runtime stable while giving the backend a clear home for shared HTTP concerns, shared types, OpenAPI work, and feature modules. The current runtime still lives in server/legacy-runtime.js, but everything new should be shaped around the layout below.

Structure

server/
  legacy-runtime.js
  src/
    app.ts
    bootstrap.ts
    config/
      runtime.ts
    shared/
      http/
        api-response.ts
        async-handler.ts
        error-handler.ts
        not-found-handler.ts
        request-context.ts
      types/
        app.ts
        http.ts
      docs/
        openapi.ts
      utils/
        app-error.ts
        logger.ts
    modules/
      auth/
      cli-auth/
      user/
      settings/
      projects/
      files/
      sessions/
      git/
      taskmaster/
      agent/
      providers/
        claude/
        codex/
        cursor/
        gemini/
        mcp/
        plugins/

File And Folder Roles

  • server/legacy-runtime.js Temporary compatibility boundary for the old monolith. Day 1 keeps behavior here so the new TypeScript layout can grow around a stable runtime. Example: the existing websocket handlers, inline routes, and provider startup still live here until they are migrated module by module.

  • src/bootstrap.ts Executable backend entrypoint used by npm run server and npm run server:dev. Example: bootstrap.ts should stay thin and do nothing except start the app, so later it remains safe to call in dev, prod, tests, or worker modes.

  • src/app.ts Composition root for the backend application. Example: today it bridges into legacy-runtime.js; later it will create the Express app, apply shared middleware, register modules, attach websocket setup, and return the running application shape.

  • src/config/ Runtime configuration helpers and environment-aware path logic. Example: config/runtime.ts resolves the project root, server root, legacy runtime path, and built bootstrap path without scattering path math across the app.

  • src/shared/http/ Shared HTTP-level behavior that every module can reuse. Example: api-response.ts is where standard API response builders live. Example: error-handler.ts is where thrown AppError instances get translated into JSON payloads. Example: request-context.ts is where request IDs, timestamps, and per-request metadata are attached. Example: async-handler.ts removes repeated try/catch(next) wrappers in controllers. Example: not-found-handler.ts is the generic fallback for unknown API routes.

  • src/shared/types/ Global type aliases that are safe to share across modules. This layer uses type, not interface. Example: types/http.ts defines ApiMeta, ApiErrorShape, RequestContext, AuthenticatedRequest, and EndpointInventoryRecord. Example: types/app.ts defines RuntimePaths, AppLocals, and ServerApplication.

  • src/shared/docs/ Shared documentation helpers and future OpenAPI registry code. Example: docs/openapi.ts is the future home for global tags like Auth, Projects, Files, Git, and Providers, plus reusable schema registration.

  • src/shared/utils/ Shared non-HTTP utilities that stay generic and reusable. Example: utils/app-error.ts defines AppError, which feature modules can throw without knowing how HTTP serialization works. Example: utils/logger.ts is the centralized logger surface so modules do not ad-hoc console.log everywhere.

  • src/modules/ Feature boundaries. Every business area gets its own folder so request schemas, controllers, services, serializers, and docs stay close to the feature they belong to.

  • src/modules/auth/ Local authentication flows. Example: login, register, logout, and auth-status endpoints belong here.

  • src/modules/cli-auth/ CLI/provider authentication status flows for Claude, Cursor, Codex, and Gemini CLIs. Example: /api/cli/claude/status belongs here because it checks local CLI auth rather than app-user auth.

  • src/modules/user/ User-specific settings and onboarding state. Example: git identity setup and onboarding completion endpoints belong here.

  • src/modules/settings/ App-level stored secrets and toggles. Example: API keys and credential storage endpoints belong here because they configure backend access rather than user identity.

  • src/modules/projects/ Workspace and project registration concerns. Example: project listing, project creation, workspace creation, and project rename/delete flows belong here.

  • src/modules/files/ File tree and workspace file operations only. Example: read file, save file, upload file, create file, rename file, delete file, and image upload endpoints belong here. Example boundary rule: this module should not decide how projects are discovered; it only operates inside an already resolved project/workspace.

  • src/modules/sessions/ Conversation and provider session history concerns. Example: list sessions, fetch session messages, rename sessions, delete sessions, token-usage lookups, and conversation search belong here.

  • src/modules/git/ Repository operations and git intelligence. Example: status, diff, branch listing, checkout, commit, push, publish, discard, and AI commit-message generation endpoints belong here.

  • src/modules/taskmaster/ TaskMaster-specific project workflows. Example: detect installation, initialize TaskMaster, manage PRDs, add/update tasks, parse PRDs, and apply templates belong here.

  • src/modules/agent/ External agent execution API. Example: /api/agent belongs here because it orchestrates provider selection, cloning, project reuse, branch creation, streaming, and optional PR creation.

  • src/modules/providers/ Provider-specific integrations that are narrower than the general agent API. Example: provider session readers or provider-specific config endpoints should live here so Claude, Codex, Cursor, and Gemini logic do not bleed into unrelated modules.

  • src/modules/providers/claude/ Claude-specific runtime concerns. Example: if Claude gets module-specific schemas or adapters later, they belong here rather than inside generic session code.

  • src/modules/providers/codex/ Codex-specific config, session, and MCP-adjacent logic. Example: Codex MCP CLI endpoints and session history parsing can move here over time.

  • src/modules/providers/cursor/ Cursor-specific config, MCP, and stored session behavior. Example: Cursor config reads, MCP server mutation, and SQLite-backed session history belong here.

  • src/modules/providers/gemini/ Gemini-specific config and session behavior. Example: Gemini session message history and provider CLI lifecycle hooks belong here.

  • src/modules/providers/mcp/ MCP surfaces that are shared across providers or not owned by a single provider module. Example: generic Claude MCP CLI/config endpoints and helper endpoints belong here.

  • src/modules/providers/plugins/ Plugin runtime and plugin asset delivery. Example: plugin listing, installation, update, enable/disable, and asset serving can move here even though plugins are not an LLM provider; this keeps third-party integration surfaces grouped together.

Boundary Rules

  • app.ts wires modules together; it should not contain feature logic.
  • config/ resolves environment and filesystem context; it should not know HTTP payload details.
  • shared/http/ owns transport concerns; it should not know feature rules like how to rename a project.
  • shared/types/ only contains reusable type aliases; avoid feature-specific types here unless multiple modules truly share them.
  • modules/<feature>/ owns its own future routes, controllers, services, schemas, and docs.
  • projects, files, and sessions stay separate on purpose: projects decides what a workspace/project is. files operates inside a resolved workspace. sessions manages chat/session history and search.
  • agent stays separate from providers: agent is orchestration for external callers. providers/* are provider-specific adapters and APIs.

Day 1 Notes

  • The runtime still executes through server/legacy-runtime.js for safety.
  • The new src/ structure is now the required home for all new backend code.
  • The generated inventory in docs/backend/endpoint-inventory.* is the source of truth for what must be migrated into these folders next.

Package Scripts

These scripts live in package.json. The key distinction is:

  • server and server:dev run the backend directly from TypeScript.
  • server:start runs the compiled backend through server/index.js.
  • build only builds the frontend.
  • server:build only builds the backend.
  • start runs the full production-style flow.

Development Scripts

  • npm run dev Starts the frontend and backend together. Use this for normal full-stack development when you want Vite and the API server running at the same time. Example: you are editing a React screen that calls /api/projects and also changing the backend route behavior.

  • npm run server:dev Starts the backend in watch mode with tsx watch server/src/bootstrap.ts. Use this for backend-only development. Example: you are refactoring request handling, logging, module structure, or shared HTTP utilities and want automatic restarts.

  • npm run server Starts the backend once from TypeScript without watch mode. Use this when you want a stable one-shot backend process. Example: you want to reproduce a startup bug, inspect logs without reload noise, or test one backend flow manually.

  • npm run client Starts only the Vite frontend dev server. Use this for frontend-only work when a backend is already running elsewhere. Example: you are polishing UI layout or fixing a component state bug and do not need to restart the API server.

Build And Runtime Scripts

  • npm run build Builds the frontend into dist/. Use this to verify production frontend bundling. Example: you changed React routing, code-splitting, or CSS and want to confirm the frontend still builds.

  • npm run server:build Compiles the backend TypeScript using server/tsconfig.json into server/dist/. Use this to verify backend build correctness. Example: you changed server/src/app.ts, shared types, or future module imports and want to confirm compiled output is valid.

  • npm run server:start Starts the built backend through server/index.js. Use this after npm run server:build when you want to run compiled backend output only. Example: dev mode works, but you want to make sure the production entrypoint and compiled files also work correctly.

  • npm run start Runs npm run build, then npm run server:build, then npm run server:start. Use this as the closest local equivalent to a production run. Example: before shipping, you want to confirm the built frontend and built backend work together, not just the watch-mode setup.

  • npm run preview Serves the built frontend bundle with Vite preview. Use this when you want to inspect the built frontend output specifically. Example: you want to check whether a client-side issue only appears in production assets. Note: this does not replace the backend server. API routes still require the backend to be running separately.

Validation Scripts

  • npm run typecheck:client Runs TypeScript checking for the frontend only. Use this after frontend code changes. Example: you changed hook types, component props, or frontend shared models.

  • npm run typecheck:server Runs TypeScript checking for the backend only. Use this after backend code changes. Example: you changed shared HTTP helpers, backend imports, or module boundaries.

  • npm run typecheck Runs both frontend and backend typechecks. Use this as the default correctness check before commit or PR. Example: you changed src/ and server/src/ in the same branch and want one command to validate both.

  • npm run lint Runs ESLint on src/ only. Use this for frontend lint validation. Example: you changed React files and want to catch unused imports, hook issues, or style violations.

  • npm run lint:fix Runs ESLint on src/ and auto-fixes what it can. Use this after frontend edits when you want quick cleanup. Example: you renamed components and want ESLint to remove stale imports and apply automatic fixes.

Release And Lifecycle Scripts

  • npm run release Runs release.sh, which loads GITHUB_TOKEN from .env and then executes release-it. Use this only when intentionally creating a release. Example: you are cutting a new tagged version and want versioning/changelog automation.

  • prepublishOnly Runs automatically before npm publish. It builds both frontend and backend first. Example: this prevents publishing a broken package that was never built.

  • postinstall Runs automatically after npm install. It executes scripts/fix-node-pty.js. Example: this helps keep native terminal integration working after dependency installation.

  • prepare Runs automatically during install in development contexts. It sets up Husky hooks. Example: this ensures local git hooks are installed without requiring a separate setup command.

  • Full-stack local development: npm install then npm run dev

  • Backend-only refactor work: npm run server:dev then npm run typecheck:server

  • Frontend-only work: npm run client, npm run typecheck:client, and npm run lint

  • Pre-PR validation: npm run typecheck, npm run lint, npm run build, and npm run server:build

  • Production-style local verification: npm run start

  • Release preparation: npm run typecheck, npm run build, npm run server:build, and npm run release