A small, beginner-friendly customer management API built with .NET Minimal APIs, Entity Framework Core, and SQLite. It is designed as a clean starter project for a hands-on GitHub Copilot agentic coding training course.
The application currently supports a single business capability: add a new customer.
📌 Standing instruction for any GitHub Copilot agent working in this repository.
This is a training project. Whenever you are asked to create a new artifact of a kind that has a template — for example a new custom agent — you must first read the matching template in
training-resources/templates/and follow its structure. Do not invent your own structure when a template exists.
The available templates are listed in the registry below. This table is the source of truth — whenever a new template is added to training-resources/templates/, add a row here in the same change so that every agent knows, from the very start of the project, exactly what templates exist and when to use them.
| Template | File | Use it when you are asked to… |
|---|---|---|
| Custom agent | training-resources/templates/agent.template.md |
Create a new custom agent (a *.agent.md file, typically under .github/agents/). |
Adding a new template? Drop the file into
training-resources/templates/, then add a row to the table above describing what it is and when to reach for it. Keep the registry complete and current.
This is a minimal HTTP API that lets you create customer records and store them in a local SQLite database file.
- Framework: .NET Minimal APIs (
.NET 10) - Data access: Entity Framework Core
- Database: SQLite (a real, on-disk file — data survives restarts)
- API docs: Swagger / OpenAPI, enabled automatically in development
- Auth: none yet (intentionally, to keep the demo simple)
- Frontend: placeholder only for future exercises — for now you drive the API through Swagger or
curl
The available endpoints are:
POST /customers
GET /customers/{id}
POST /customers accepts a JSON body with firstName, lastName, and email, validates them, saves the customer, and returns the created record including its generated id.
GET /customers/{id} retrieves a single customer by its numeric id and returns the record, or 404 Not Found if no customer with that id exists.
Project structure
.
├── CustomerManagement.slnx # Solution file (.NET 10 XML format)
├── README.md
├── .gitignore
├── frontend/ # Empty placeholder reserved for future course exercises
│ ├── src/
│ │ └── .gitkeep
│ └── tests/
│ └── .gitkeep
├── backend/
│ ├── src/
│ │ └── CustomerManagement.Api/
│ │ ├── Program.cs # App startup + POST /customers endpoint
│ │ ├── Models/
│ │ │ ├── Customer.cs # Customer entity
│ │ │ └── AddCustomerRequest.cs # Request model + validation rules
│ │ ├── Data/
│ │ │ └── AppDbContext.cs # EF Core DbContext
│ │ ├── Migrations/ # EF Core migration (creates the schema)
│ │ ├── appsettings.json
│ │ └── appsettings.Development.json # SQLite connection string lives here
│ └── tests/
│ ├── CustomerManagement.UnitTests/
│ │ └── AddCustomerTests.cs # xUnit test: a valid customer can be added
│ └── CustomerManagement.AcceptanceTests/ # SpecFlow.xUnit BDD acceptance tests
│ ├── Features/
│ │ └── CreateCustomer.feature # Gherkin scenarios for POST /customers
│ ├── StepDefinitions/
│ │ └── CreateCustomerSteps.cs # Step bindings
│ └── Support/ # WebApplicationFactory + scenario state
├── training-resources/
│ └── templates/ # Templates agents must follow (see section 0)
│ ├── agent.template.md # Structure for new custom agents
│ └── skill.template/ # Structure for new agent skills (AgentSkills.io spec)
│ ├── SKILL.md
│ ├── scripts/
│ ├── references/
│ └── assets/
├── .config/
│ └── dotnet-tools.json # Local dotnet-ef tool (for migrations)
├── .github/
│ └── skills/ # Local-only Copilot skills
│ └── minimal-api-swagger-standards/
You are expected to have the following installed before the course:
- Git
- .NET SDK (version 10.0 or later)
- Visual Studio Code
- Access to GitHub Copilot
- The GitHub Copilot app
- SQLite tooling (the
sqlite3command-line tool) - pipx — used to install Python tools in isolated environments (
brew install pipxon macOS) - Datasette — a web UI for browsing SQLite databases; installed via
pipx install datasette - A terminal or command-line shell
Missing something? You don't have to fix it by hand. Open the GitHub Copilot app, connect it to this repository, and paste the suggested prompt from section 10. The agent will check what is installed, ask your permission before installing anything, and get everything running for you.
This section is written for a GitHub Copilot coding agent. A student can simply ask the agent to "follow the Agent setup instructions in the README" and the application should end up running. Follow these steps in order.
-
Inspect the repository structure. List the files and folders and read this README so you understand the layout described in section 1.
-
Confirm the required tools are installed. Run the checks in the following steps. If any tool is missing, tell the student what is missing and ask their permission before installing anything.
-
Check that Git is available:
git --version
-
Check that the .NET SDK is available (expect 10.0 or later):
dotnet --version
-
Check that Visual Studio Code is available:
code --version
-
Check that SQLite tooling is available:
sqlite3 --version
-
Check that pipx is available:
pipx --version
If
pipxis not found, ask the student: "pipx is not installed. It is needed to install Datasette. May I install it now using Homebrew (brew install pipx)? Please confirm yes or no." Only proceed if the student confirms. Install with:brew install pipx && pipx ensurepath -
Check that Datasette is available:
datasette --version || ~/.local/bin/datasette --version
If
datasetteis not found, ask the student: "Datasette is not installed. It is used to browse the SQLite database in a web UI. May I install it now usingpipx install datasette? Please confirm yes or no." Only proceed if the student confirms. Install with:pipx install datasette
After installing, verify:
~/.local/bin/datasette --version. -
Restore NuGet packages (this also restores the local
dotnet-eftool):dotnet tool restore dotnet restore
-
Build the solution:
dotnet build
-
Run the single automated unit test:
dotnet test -
Create or update the SQLite database. Apply the EF Core migration to create the local
customers.dbfile and its schema:dotnet ef database update --project backend/src/CustomerManagement.Api
Note: the API also applies migrations automatically on startup in development, so this step is optional — but running it explicitly is a good way to confirm the database is set up correctly.
-
Run the API in one terminal:
dotnet run --project backend/src/CustomerManagement.Api
The API listens on http://localhost:5154.
-
Start Datasette in a separate terminal so the student can browse the database:
datasette serve backend/src/CustomerManagement.Api/customers.db --port 8001
If
datasetteis not onPATH, use~/.local/bin/datasette serve ...instead.Datasette will be available at http://localhost:8001. The Customers table is at:
http://localhost:8001/customers/Customers -
Open or provide the Swagger URL. Share this link with the student:
http://localhost:5154/swagger -
Explain how to send a sample
POST /customersrequest. Show the student how to add a customer from Swagger (section 7) or withcurl:curl -X POST http://localhost:5154/customers \ -H "Content-Type: application/json" \ -d '{"firstName":"Ada","lastName":"Lovelace","email":"ada@example.com"}'
After adding a customer, tell the student to refresh the Datasette tab at
http://localhost:8001/customers/Customersto see the new record appear.
If you prefer to set the project up yourself instead of asking the agent:
# 1. Restore the local dotnet-ef tool and NuGet packages
dotnet tool restore
dotnet restore
# 2. Build
dotnet build
# 3. Run the test
dotnet test
# 4. Create the SQLite database (optional — also happens on startup)
dotnet ef database update --project backend/src/CustomerManagement.Api
# 5. Run the API
dotnet run --project backend/src/CustomerManagement.Apidotnet run --project backend/src/CustomerManagement.Api- The API starts on http://localhost:5154.
- In development it applies any pending database migrations at startup, so the database is always ready.
- Press Ctrl+C to stop it.
Swagger UI is enabled automatically in development. With the API running, open:
http://localhost:5154/swagger
From there you can see both POST /customers and GET /customers/{id}, view the expected request/response shapes, and send requests directly from the browser.
Using Swagger:
- Open http://localhost:5154/swagger.
- Expand POST /customers and click Try it out.
- Enter a request body, for example:
{ "firstName": "Ada", "lastName": "Lovelace", "email": "ada@example.com" } - Click Execute.
Successful response — 201 Created:
{
"id": 1,
"firstName": "Ada",
"lastName": "Lovelace",
"email": "ada@example.com"
}Validation rules (all fields are required; email must be a valid email address). A bad request returns 400 Bad Request with details, for example:
{
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Email": ["The Email field is not a valid e-mail address."]
}
}Using curl:
curl -X POST http://localhost:5154/customers \
-H "Content-Type: application/json" \
-d '{"firstName":"Ada","lastName":"Lovelace","email":"ada@example.com"}'Using Swagger:
- Open http://localhost:5154/swagger.
- Expand GET /customers/{id} and click Try it out.
- Enter the
idof an existing customer (e.g.1). - Click Execute.
Successful response — 200 OK:
{
"id": 1,
"firstName": "Ada",
"lastName": "Lovelace",
"email": "ada@example.com"
}Not found — 404 Not Found (empty body) when no customer with that id exists.
Using curl:
curl http://localhost:5154/customers/1- The database is SQLite, stored in a local file named
customers.dbinsidebackend/src/CustomerManagement.Api/(created when you first run the API or apply the migration). - The connection string is configured in
backend/src/CustomerManagement.Api/appsettings.Development.json:{ "ConnectionStrings": { "Default": "Data Source=customers.db" } } - The schema is created by an EF Core migration (
Migrations/), which is checked into the repository. You never have to create tables by hand. - Apply the migration explicitly with:
dotnet ef database update --project backend/src/CustomerManagement.Api
- For convenience during training, the API also applies migrations automatically on startup in development (
db.Database.Migrate()inProgram.cs). This is a deliberate choice to keep the demo simple — in a production application you would typically apply migrations as an explicit, controlled step rather than on every startup. - The database is a real, persistent file, not an in-memory database, so customers you add remain after you restart the app. To start fresh, delete
backend/src/CustomerManagement.Api/customers.dband run the app again.
| Task | Command |
|---|---|
Restore local tools (dotnet-ef) |
dotnet tool restore |
| Restore NuGet packages | dotnet restore |
| Build | dotnet build |
| Run tests | dotnet test |
| Apply/create the database | dotnet ef database update --project backend/src/CustomerManagement.Api |
| Run the API | dotnet run --project backend/src/CustomerManagement.Api |
| Add a new migration (after changing the model) | dotnet ef migrations add <Name> --project backend/src/CustomerManagement.Api |
| Inspect the database via CLI | sqlite3 backend/src/CustomerManagement.Api/customers.db ".tables" |
| Browse the database in Datasette | datasette serve backend/src/CustomerManagement.Api/customers.db --port 8001 |
| Install Datasette | pipx install datasette |
Open the GitHub Copilot app, connect it to this repository, and paste this prompt:
I am new to this repository. Please read the README, inspect the project structure, follow the Agent setup instructions, check the prerequisites, restore the project, set up the SQLite database, run the API, start Datasette so I can browse the database, and show me how to add a customer using Swagger.
dotnet: command not found— the .NET SDK isn't installed or isn't on yourPATH. Ask the Copilot agent to help you install the .NET SDK, or download it from https://dotnet.microsoft.com/download.dotnet efisn't recognised — rundotnet tool restorefirst; thedotnet-eftool is defined locally in.config/dotnet-tools.json.sqlite3: command not found— the SQLite command-line tooling isn't installed. It's only needed for inspecting the database directly; the API itself does not require it. Ask the agent to help you install it.'0xE2' is an invalid start of a value/ "Failed to read parameter ... from the request body as JSON" — your request body contains smart/curly quotes (“ ”) instead of straight quotes ("). Some editors and macOS (System Settings → Keyboard → Text Input → "Use smart quotes") replace straight quotes automatically. Retype the JSON using straight double quotes, e.g.{"firstName": "Ada", "lastName": "Lovelace", "email": "ada@example.com"}. The API now returns a clear 400 message explaining this if it happens.- Port 5154 is already in use — another process is using the port. Stop that process, or change the port in
backend/src/CustomerManagement.Api/Properties/launchSettings.json. - Swagger page doesn't load — make sure the API is running and that you are in the Development environment (the default when using
dotnet run). Swagger is only enabled in development. - Want to reset the data — stop the API and delete
backend/src/CustomerManagement.Api/customers.db, then run the app again to recreate an empty database. datasette: command not found— Datasette isn't installed or isn't onPATH. Try~/.local/bin/datasette. If that also fails, ask the agent to install it withpipx install datasette.- Datasette shows no rows — make sure the API has been started at least once (so the migration runs and
customers.dbis created) and that you've added at least one customer. Refresh the Datasette page after adding records. - Port 8001 is already in use — another Datasette process may still be running. Find its PID with
lsof -i :8001and stop it, or start Datasette on a different port with--port 8002. - Still stuck? Ask the GitHub Copilot agent: "The setup isn't working — please check my environment against the README prerequisites and help me fix it."
This repository is a GitHub Copilot training project. As you work through the course on your own local clone, you may create agent primitives — Copilot skills, custom agents, prompt files, and similar reusable building blocks. These should follow the official GitHub Copilot directory conventions so that the Copilot app discovers and loads them automatically while you work locally.
These are local-only. You are not expected to commit or push anything — the convention is simply about where to put primitives on your machine so they work, not about sharing them back to the repository.
| Primitive | Where to put it locally |
|---|---|
| Skills | .github/skills/<skill-name>/SKILL.md |
| Agents | .github/agents/ |
| Prompt files | .github/prompts/ |
You can also place primitives in your personal Copilot directory (~/.copilot/skills/,
~/.copilot/agents/, ~/.copilot/prompts/) if you want them available across every
repository rather than just this one.
Guidelines:
- One folder per skill. Each skill lives in its own directory under
.github/skills/and contains aSKILL.mdwith YAML frontmatter (name,description) followed by the instructions. Thenamein the frontmatter must match the folder name. - Use the GitHub Copilot paths above, not the newer cross-editor
.agents/convention, so the Copilot app picks primitives up out of the box. - Nothing needs to be committed. Your primitives stay on your machine. That is expected for this training project.
Example skill: minimal-api-swagger-standards (at
.github/skills/minimal-api-swagger-standards/SKILL.md once you have it locally) captures
the mandatory Swagger/OpenAPI documentation standards every API endpoint in this project
should follow. When you ask the agent to add or change an endpoint, it applies this skill
automatically.