Integrating into an Existing Project
The scaffold produces a standalone project that starts as-is. When the target is a repository that already has a server, a directory structure and a workspace, the more common approach is not to move the entire generated tree in, but to take only the parts you need.
--integrate exists for exactly this:
pnpm create tealina my-app --integrateIt writes INTEGRATION.md at the project root. That file is not part of the runtime and nothing imports it; it is a merge playbook written for AI assistants, and you can delete it once the merge is done. The rest of this page describes the two shapes it covers and a few easy-to-miss details.
Two Shapes
| Shape | Copy | Leave in Place |
|---|---|---|
| A. Mounting onto an Existing Service | backend/core, backend/api-v1 | backend/server, packages/web, root scripts |
| B. Merging into an Existing Monorepo | backend/core, backend/api-v1, backend/server | packages/web (unless needed) |
A. Mounting onto an Existing Service
Use this when the target repository already has an Express/Fastify/Koa application, its own entry point and its own deployment. Copy core and api-v1, and call mount once on the existing app:
import { mount } from '<api>/mount'
const source = await mount(app)mount installs that version's routes, body parsers and JSON documentation route; the returned TealinaVdocSource is only needed by the documentation page. The documentation page is optional: copy the generated project's server/src/doc.ts and public/ over together, or skip it entirely and keep only <API_BASE_URL>/_doc/api.json.
backend/server is not something to copy. It shows how a host should wire things up; just borrow the approach from its app.ts.
B. Merging into an Existing Monorepo
Copy the three backend packages into the target repository, placing them under backend/ or apps/ according to existing convention. Then:
- Add the directories to the workspace globs, and merge
allowBuilds,catalogandoverridesfrompnpm-workspace.yamlrather than replacing the whole file. - Merge the root scripts rather than overwriting them.
devmust buildcorefirst and thenapi-v1: the host imports theirdist/. - Only if the target repository already uses Vite+, merge the
fmt/lintconfiguration from the rootvite.config.ts. - Do not overwrite the target repository's
README.mdorAGENTS.mdwith the scaffold's same-named files; only extract theapi-v1rules into them.
If the target is not Express/Fastify/Koa, stop and reconsider. core/types/alias.d.ts binds the request/response types of those three frameworks; switching frameworks means adding a new alias, not a mechanical migration.
Renaming
core, api-v1 and server are generic names, and the target repository usually already uses them. When renaming, beyond package.json and bare imports in the source, there are two places where type checking cannot see the problem:
handlerImportintealina.config.ts.- The strings returned by
generateTsCode/generateJsCodein the same file. They determine which package newly written handlers import aftercreate; if you miss them, the next endpoint you add breaks, with no error in between.
The fixed artifact names do not change with the rename: types/api.d.ts, exports["./api"], apiDir.
Builds and Generated Artifacts
The import conditions of core's exports["./access"] and api-v1's exports["./mount"] point at dist/, so the host must build these two packages before importing them (in the order core → api-v1).
Do not merge generated artifacts by hand:
| File | Generated by |
|---|---|
api-v1/src/api/index.*, api-v1/src/api/<method>/index.* | align |
api-v1/docs/api.json | gdoc |
Both commands support --check (compare only, exit code 1 when stale) and --json. Wire --check into the target repository's CI; otherwise, when the docs go stale the page still renders, just with old content.
Verification Checklist
At the target repository root, replace <core> and <api> with the renamed package names:
pnpm install
pnpm -F <core> build
pnpm -F <api> build
pnpm -F <api> align --check
pnpm -F <api> gdoc --check
# if either exits 1
pnpm -F <api> align
pnpm -F <api> gdocThen start the target service and confirm, in order:
curl -i <API_BASE_URL>/healthreturns 200.- A protected endpoint returns 401 without
Authorization— the scaffold's guard is a placeholder; replacecore/src/access/verifyToken.ts. curl <API_BASE_URL>/_doc/api.jsonreturns the documentation.- If the documentation page is wired up,
/api-doc/index.htmlopens. - The frontend still type-checks against
<api>/api.
The scaffold's get/health, post/login and post/article are examples. Once step 1 passes, delete the endpoints you do not need and rerun align and gdoc.
Where It Fails Silently
- Copied the packages but did not build them.
core/accessandapi-v1/mountresolve todist/at runtime; when missing, you getERR_MODULE_NOT_FOUND, while type checking stays green. handlerImportor the stub strings still say the oldcore. Type checking passes, and the nextcreatewrites a broken import.- Edited a generated barrel by hand. The next
alignsilently overwrites it. - Changed a handler but did not run
gdoc. The documentation page renders as usual, just with stale content. - Two routes claim the same prefix. Requests land on the one registered first, and the other returns 404, while both sides type-check.
- The target framework's major version differs from the template's.
alias.d.tsmay still compile, but it describes a different set ofreq/res; compare the installed types, not the version number. - Node < 22. Failures usually show up in a dependency's build or at runtime, not in type checking.
The full step-by-step playbook (copy scope for both shapes, rename search commands, mount examples for each framework) lives in the generated project's INTEGRATION.md, ready to hand to the AI assistant performing the merge.