# Adding a New App

How to add a new deployable service to the `apps/` directory. Apps are standalone services (Express APIs, microservices, frontend builds) that are deployed independently.

## Steps

1. **Create the app directory** under `apps/<name>/` with the following structure:

   ```
   apps/<name>/
     src/
       index.ts          # entry point
     package.json
     tsconfig.json
     tsdown.config.ts    # build config
     Dockerfile          # if deployed to ECS/Fargate
     .env.shadow         # env template (never commit .env)
   ```

2. **Configure `package.json`.** Use the `@coda/<name>` naming convention. Key fields:

   ```json
   {
     "name": "@coda/<name>",
     "version": "1.0.0",
     "private": true,
     "type": "module",
     "engines": { "node": ">=24.0.0" },
     "scripts": {
       "dev": "node --import tsx --watch --env-file=.env src/index.ts",
       "build": "tsdown",
       "start": "node dist/index.mjs",
       "typecheck": "tsc --noEmit",
       "lint": "eslint src/",
       "test": "pnpm lint && pnpm test:unit --coverage",
       "test:unit": "vitest run --passWithNoTests"
     }
   }
   ```

3. **Set up TypeScript.** Create a `tsconfig.json` that extends the root config:

   ```json
   {
     "extends": "../../tsconfig.base.json",
     "compilerOptions": {
       "outDir": "dist",
       "rootDir": "src"
     },
     "include": ["src"]
   }
   ```

4. **Add config files.** Follow the same patterns as library packages (see [Adding a Package](adding-a-package.md)) but extend `../../tsconfig.app-base.json` instead of `tsconfig.lib.json`:

   **`tsconfig.json`**:

   ```json
   {
     "extends": "../../tsconfig.app-base.json",
     "compilerOptions": {
       "paths": { "@<name>/*": ["./src/*"] }
     },
     "include": ["src/**/*"],
     "exclude": ["src/**/__tests__", "dist", "node_modules"]
   }
   ```

   **`tsconfig.test.json`**, **`eslint.config.mjs`**, **`.prettierignore`**, **`vitest.config.ts`** — same patterns as library packages, but use `tsconfig.app-base.json` as the tsconfig base. For apps with multiple test tiers, add `vitest.config.functional.ts` and `vitest.config.integration.ts`.

5. **Create `.env.shadow`.** Include all required environment variables with placeholder values. This serves as the template -- developers copy it to `.env` locally. Never commit `.env`.

6. **Register in the workspace.** The `pnpm-workspace.yaml` already includes `apps/*`, so new directories are picked up automatically. Run `pnpm install` to link the workspace.

7. **Add workspace dependencies.** Reference shared packages:

   ```json
   {
     "dependencies": {
       "@coda/common": "workspace:*"
     }
   }
   ```

8. **Update the Dockerfile.** In the `dev-deps` stage, add source COPY blocks in the **App source** section:

   ```dockerfile
   # <name> app source + configs
   COPY  --chown=node:node apps/<name>/tsconfig.json apps/<name>/tsdown.config.ts apps/<name>/eslint.config.mjs apps/<name>/.prettierignore ./apps/<name>/
   COPY  --chown=node:node apps/<name>/src ./apps/<name>/src
   ```

   The `test-base` stage auto-discovers vitest configs via `COPY --parents` globs — **no change needed**.

9. **If the app needs its own Docker image**, create `apps/<name>/Dockerfile` following the pattern in `apps/runner/Dockerfile`:
   - `deps` stage: use `COPY --parents` for the app + its workspace dependencies
   - `dev-deps` stage: copy configs + source, build dependencies (scope the `pnpm --filter` to only what this app needs)
   - Copy `tsconfig.lib.json tsconfig.app-base.json eslint.base.mjs` in root configs

   Add the app to `scripts/detect-changes.sh` deploy gates:

   ```bash
   # Either add to an existing gate:
   SERVER_GATE_RE='^(@coda/server|@coda/client|@coda/<name>)$'
   # Or create a new gate if deploying independently:
   <NAME>_GATE_RE='^@coda/<name>$'
   ```

   Add the corresponding `<NAME>_CHANGED` logic + Jenkinsfile stages.

10. **Set up infrastructure.** If deploying to AWS, add Terraform config in the `terraform-infra` repo:
    - ECR repo in `terraform-infra/shared/prod/ecr/repos/ows-coda-<name>/`
    - Fargate service in `terraform-infra/qa/ows-coda/`
    - Terraform changes go in a separate PR to that repo

11. **Add dev scripts.** If the app should be runnable alongside other services, add a `dev:<name>` script to the root `package.json`.

12. **Validate.**

    ```bash
    pnpm install
    pnpm --filter @coda/<name> typecheck
    pnpm --filter @coda/<name> lint
    pnpm --filter @coda/<name> test:unit
    bash scripts/validate-workspace-sync.sh
    ```

13. **Add tests and a README.**

### What's automatic (no manual steps needed)

- **`pnpm-workspace.yaml`** — uses `apps/*` / `packages/*` globs, auto-discovers
- **`detect-changes.sh`** — discovers packages dynamically from the filesystem
- **Dockerfile `deps` stage** — uses `COPY --parents apps/*/package.json packages/*/package.json`
- **`lint-staged`** — uses `apps/*/src/**` / `packages/*/src/**` globs

## Conventions

- **Hot reload** -- use `node --import tsx --watch --env-file=.env` for local development
- **Health checks** -- expose a `GET /health` endpoint for load balancer probes
- **Structured logging** -- use `pino` for JSON logs (Datadog parses these)
- **Graceful shutdown** -- handle `SIGTERM` to drain in-flight requests before exit
- **Sentry + Datadog** -- integrate `@sentry/node` and `dd-trace` for observability

## See also

- [CONTRIBUTING.md](../../CONTRIBUTING.md) for the full contribution workflow, PR process, and code conventions
- [Deployment](../operations/deployment.md) for CI/CD pipeline and deployment details
- [Docker](../guides/docker.md) for local Docker environment setup
