Guidelines for AI agents working on the Cookiecutter API repository.
This is a Copier template repository that generates REST API projects across multiple languages and cloud platforms. Each generated project follows the controller-service-repository pattern with full CRUD operations.
cookiecutter-api/
├── python/ # Python template (Azure + GCP)
├── typescript/ # TypeScript/Node.js template (Azure + GCP)
├── dotnet/ # .NET/C# template (Azure only)
├── .github/
│ ├── actions/ # Shared composite actions
│ └── workflows/ # CI pipelines per language
├── .docs/ # Documentation assets (images, SVGs)
└── README.md # Support matrix and usage docs
Each language directory contains:
copier.yml— Questions, derived values, validators, and Jinja extension configtemplate/— The template project root (declared via_subdirectory: template); its contents are rendered directly into the destination directory
All templates follow this layered architecture:
Entry Point (functions/ or main.ts/main.py)
→ Controller (request validation, routing)
→ Service (business logic, schema validation)
→ Repository (database operations)
→ Database (Cosmos DB or Firestore)
- Dependency Injection: TypeScript uses Inversify, Python uses manual wiring in the blueprint/entry point
- Schema Validation: TypeScript uses Zod, Python uses Pydantic, .NET uses FluentValidation
- Soft Deletes: All templates use an
isDeletedflag rather than hard deletes - Base Records: All entities extend a base schema with
id,isDeleted,createdTimestamp,updatedTimestamp
Cloud-specific code is handled through:
- Jinja2 conditionals —
{% if cloud_service == '...' %}blocks within shared files (repositories, configs, package manifests) - Separate entry point files — Each cloud has its own entry point (e.g.,
functions/*.tsfor Azure,main.tsfor GCP) - Conditional file/directory names — Cloud-specific files and directories are named with a Jinja conditional (e.g.
{% if cloud_service == 'AWS Lambda' %}lambda.ts{% endif %}). Copier skips any path that renders to an empty string, which replaces Cookiecutter's post-generation cleanup hooks.
| Cloud Provider | Database | TypeScript Client | Python Client |
|---|---|---|---|
| Azure Function App | Cosmos DB | @azure/cosmos |
azure-cosmos |
| GCP Cloud Function | Firestore | @google-cloud/firestore |
google-cloud-firestore |
To add a new cloud provider (e.g., AWS Lambda) to an existing language template:
- Update
copier.yml— Add the new option to thecloud_servicequestion'schoices - Create the entry point — Add the cloud-specific function entry point file(s), naming them with a
{% if cloud_service == '...' %}...{% endif %}conditional so they are only generated for that cloud - Add Jinja2 conditionals to these files:
package.json/pyproject.toml/.csproj— Cloud-specific dependenciesrepositories/base.repository— Database client implementationrepositories/{project}.repository— DI binding for the database clientconfig/inversity.config.ts(TypeScript) or blueprint wiring (Python) — DI container setuptypes/models/baseEnv.schema— Environment variable definitions
- Name any cloud-specific files/directories conditionally so they are omitted for the other clouds
- Update CI pipeline — Add the new cloud service to the
cloud-servicematrix in the workflow YAML - Update
README.md— Change the support table cell from planned to complete
- Create a new top-level directory (e.g.,
go/) - Add
copier.ymlwith the standard questions (project_name,project_endpoint,project_class_name,cloud_service, etc.), the_jinja_extensions,_templates_suffix: "", and_subdirectory: templatesettings - Put the template project under
template/ - Add input validation as a
validator:on the promptedproject_namequestion (Copier only runs validators for prompted questions, not forwhen: falsederived values) - Use conditional file/directory names if supporting multiple cloud providers
- Create
.github/workflows/build-{language}-pipeline.yaml - Update the root
README.mdsupport table
| Variable | Description | Example |
|---|---|---|
project_name |
Human-readable name | "My API" |
project_endpoint |
REST endpoint (kebab-case) | "my-api" |
project_class_name |
PascalCase class name | "MyApi" |
project_lower_camel_name |
lowerCamelCase (TS/C#) | "myApi" |
project_slug |
snake_case (Python only) | "my_api" |
cloud_service |
Target cloud platform | "Azure Function App" |
author |
Project author | "Your Name" |
open_source_license |
License type | "MIT license" |
project_slug, project_endpoint, project_class_name, and project_lower_camel_name
are derived from project_name via when: false questions, so they are computed
automatically and never prompted. The jinja2_strcase extension provides to_camel and
to_lower_camel filters for case conversion, and jinja2_time provides the {% now %}
tag used in LICENSE.
Each language has a GitHub Actions workflow that:
- Generates a project with
copier copy --defaults --trust - Installs dependencies
- Builds the project
- Runs unit tests
The shared composite action at .github/actions/setup-copier-template/action.yaml handles steps 1-2.
Pipelines use a matrix strategy to test across:
- Multiple operating systems (ubuntu, macOS, Windows)
- Multiple runtime versions (Node 18/20, Python 3.12/3.13)
- All supported cloud services
To verify changes locally, generate a template and test it:
# Install dependencies
pip install copier jinja2-strcase jinja2-time
# Generate a project
copier copy --defaults --trust \
--data project_name="TestProject" \
--data cloud_service="GCP Cloud Function" \
./typescript ./TestProject
# Build and test
cd TestProject
yarn install --no-immutable
yarn build
yarn test:unit- TypeScript: Yarn for packages, Jest for tests, path aliases (
@controllers,@services, etc.) viatsconfig.jsonpaths - Python: Poetry for packages, pytest for tests, blueprint pattern for route registration
- .NET: NuGet for packages, xUnit for tests, solution/project structure
- Template files use
{{ variable_name }}in both filenames and content - Keep controllers, services, and error types cloud-agnostic — only repositories and entry points should contain cloud-specific code