Skip to content

Project Structure ​

The scaffold's backend consists of three workspace packages, each doing one thing. Dependencies flow one way:

text
core → api-v1 → server
              ↗
packages/web (only with --web)

pnpm -r build follows the topological order, so a package is always built before the packages that depend on it.

Generated Tree ​

text
my-app/
  package.json            scripts delegate to the three backend packages
  pnpm-workspace.yaml     backend/* and packages/*
  backend/
    core/                 contract layer
      types/              handler types, common shapes, framework aliases
      src/access/         openApi / authedApi, and the guard installed by authedApi
    api-v1/               one API version
      src/api/            get/health.ts, post/login.ts, post/article.ts
      src/                router.ts, doc.ts, mount.ts
      version.js          API_VERSION and API_BASE_URL
      types/api.d.ts      the contract's entry point (exports["./api"])
      tealina.config.ts   apiDir / apiBaseURL / typesDir / handlerImport
      docs/api.json       documentation generated by gdoc
    server/               host
      src/                app.ts, doc.ts, index.ts, static asset routes
      public/             served by the static asset routes
  packages/web/           only with --web
    src/
      main.ts             the page
      api/client.ts       req derived from the api-v1 handlers

backend/core: The Contract Layer ​

core contains no routes. It defines what a handler is, and exposes that definition to the other packages through its own exports.

FileRole
types/handler.d.tsOpenHandler / AuthedHandler, plus the projections that derive documentation and client types
types/alias.d.tsThe only framework-specific file: binds the Express / Fastify / Koa request and response types to the generic handler shape
types/common.d.tsShared shapes such as AuthedLocals, AuthHeaders, JsonHeaders
src/access/index.tsThe two ways to end a file: openApi / authedApi
src/access/verifyToken.tsThe guard authedApi puts at the head of the chain

exports exposes three entry points: ./handler and ./common (both pure types), and ./access (runtime). So wherever a handler sits in the route tree, its import specifier is core/handler or core/access; there are no relative paths to count.

backend/api-v1: One API Version ​

api-v1 owns the full lifecycle of one version: the route tree, the router, the documentation source and mounting.

FileRole
src/api/The route tree. The first level is the HTTP method, and the file path is the URL
src/router.tsHands the barrel file generated by align to @tealina/server, turning it into framework routes
src/doc.tsPublishes this version's docSource and serves the JSON file at <API_BASE_URL>/_doc/api.json
src/mount.tsMounts the body parsers, the routes and the documentation JSON file onto the framework instance, and returns docSource
version.jsThe single place API_VERSION and API_BASE_URL are written
types/api.d.tsThe contract entry point. ApiTypesForDoc must be the first export, with ApiTypesForClient immediately after it
tealina.config.tsapiDir, apiBaseURL, typesDir, handlerImport

exports exposes three entry points:

  • ./api: only the types condition, pointing at types/api.d.ts. The frontend must use import type.
  • ./version: the runtime API_VERSION / API_BASE_URL.
  • ./mount: types points at src/mount.ts, and import points at dist/mount.js. The host uses it to compile and mount the entire route tree.

The names of the public artifacts are fixed: types/api.d.ts, docs/api.json, exports["./api"]. Moving or renaming apiDir only changes route discovery and the handlers' own imports; it changes no public name.

backend/server: The Host ​

server creates the framework instance, reads .env, mounts each API version, and serves the documentation page and static assets. It owns no routes:

  • src/app.ts calls each version's mount and collects the returned docSource.
  • src/doc.ts uses assembleHTML from @tealina/doc-ui to generate a single documentation page listing every version the host mounts.
  • src/index.ts starts listening on PORT.

Error handling and 404 responses are not provided by the scaffold; they are left to framework defaults (Express's finalhandler, Koa's ctx.onerror), and the application can take over with its own strategy.

packages/web: Optional Frontend ​

Generated only with --web. It is a Vite project with no UI framework, demonstrating the client rather than the page:

  • src/api/client.ts imports ApiTypesForClient from api-v1/api and creates req.
  • src/main.ts calls GET /health and writes the result into the page; you can delete it and rewrite it freely.
  • vite.config.ts proxies API_BASE_URL to http://localhost:8000, so the backend template needs no CORS.

A Second API Version ​

Add backend/api-v2, make it depend on core, and have server mount it alongside api-v1. Neither core nor the frontend hard-codes a version number; each version publishes its own documentation JSON file, and the host hands every docSource to the same documentation page.