Skip to content

Project Structure

Open in ChatGPTOpen in Claude

Every Axis project splits into three areas: typed configuration in config/, stack composition in cdk/, and handler code in lambdas/. Two files at the root describe the project to tools — STRUCTURE.md for humans and AI assistants, and axis.json as the machine-readable contract.

The rest-v1 starter in module layout is the canonical shape (leaner starters are subsets of it):

my-api/
├── axis.json ← starter + layout + paths/conventions (v1 schema)
├── STRUCTURE.md ← human-readable narrative of this tree
├── package.json · tsconfig.json · cdk.json · jest.config.js
├── .npmrc ← @axis-tech-co scope → registry mapping (no token)
├── cdk/
│ ├── bin/app.ts ← stack composition, gated by feature toggles
│ └── lib/stacks/
│ ├── api-stack.ts ← project subclass of the abstract ApiStack
│ ├── budget-stack.ts
│ └── observability-stack.ts
├── config/
│ ├── types.ts ← MyApiConfig extends BaseEnvConfig
│ ├── dev-config.ts / prod-config.ts
│ ├── loader.ts ← createEnvLoader — single env-selection source
│ ├── features.ts ← FeatureToggles + DEFAULT_FEATURES
│ ├── lambdas.ts ← apiConfigs[] registry
│ ├── lambda-configs/ ← one config file per route
│ ├── tables/ ← central table definitions + index.ts registry
│ ├── buckets/ keys/ queues/ secrets/ topics/ (per-construct config groups)
│ ├── security/{ses,waf}.ts
│ ├── eventbridge/index.ts
│ └── budget/index.ts
├── lambdas/
│ └── modules/
│ ├── health/api/check/index.ts
│ └── items/api/{create,get,update,patch,delete}/index.ts
└── tests/synth.test.ts ← snapshot + assertion tests over the synth output
  • types.ts declares your project’s config interface extending BaseEnvConfig (which carries appName and stageName, the two values that drive physical naming and SSM paths).
  • dev-config.ts / prod-config.ts hold the per-environment values; loader.ts (createEnvLoader) selects between them from the --context env=dev|prod flag. One selection mechanism, used everywhere.
  • features.ts declares which construct groups the app instantiates — see Feature Toggles.
  • lambdas.ts and tables/index.ts are the two registries — plain typed arrays (LambdaConfig[], table definitions) assembled from the per-item files around them.
  • The remaining directories (queues/, topics/, keys/, buckets/, secrets/, security/, eventbridge/, budget/) each configure one construct group, one file per resource.

cdk/bin/app.ts reads the resolved feature toggles and instantiates only the stacks whose groups are on. cdk/lib/stacks/ holds your project’s subclasses of the framework’s abstract stacks (ApiStack, ObservabilityStack, BudgetStack, …) — each subclass implements small hook methods (for example getLambdaConfigs()) that hand the registries to the framework. The stack topology and its wiring rules are covered in Stacks & SSM Wiring.

Handlers live under lambdas/modules/ (or lambdas/features/ in feature layout — see Layout). Each handler directory pairs an index.ts entry point with a service.ts for logic, and axs add lambda generates new ones in the right place by reading axis.json.

The framework itself is deliberately layout-agnostic: its only contract with your code is the config/lambdas.ts and config/tables/index.ts registries. Both layouts produce identical registries — only file locations and import paths differ — and the framework never reads axis.json (only tooling does). That is what keeps the directory conventions a tooling concern you can evolve, rather than a framework constraint you’re stuck with.

tests/synth.test.ts snapshot-tests the synthesized templates for dev and prod. It is the project-local version of the framework’s own examples-as-oracle discipline: infrastructure changes show up as reviewable template diffs before they reach AWS.

© 2026 Axis Tech. Powered by axis-tech.co.