Creating the Project with CoPilot
As part of the API tutorials we decided we needed a Database with two tables: Book and Author. Books will join Author and there can be as many authors as we want. Rather than writing this however, I went on GitHub and used the agents.
I prompted:
ASP.net API with a sqlserver backend. SQLServer will have two tables: bookList and Authors, with the booklist id as a foreign key in Authors. Unit tests using xunit and moq. Set up Azurite to provide a message queue, have the GET endpoint create a durable function to listen to the message queue, and have POST use an Azure function to add records to the database. Create service classes for the logic and Repository classes for interacting with the Database
CoPilot responded with a huge amount of work. Here is its description of what it did in reaction to this prompt.
Bootstrapped a complete ASP.NET Core Web API with SQL Server backend, Azure Functions for async processing, and Azurite-backed message queues. The architecture separates GET operations (handled by durable functions) from POST operations (handled by queue-triggered functions).
Database Schema
- Books table: Id, Title, ISBN, PublishedDate, Price
- Authors table: Id, FirstName, LastName, Email, BookId (FK → Books)
- One-to-many relationship via BookId foreign key
- EF Core migrations included
Architecture Layers
Repository Pattern
IBookRepository/BookRepository– Book data accessIAuthorRepository/AuthorRepository– Author data access- Async operations, includes navigation properties
Service Layer
IBookService/BookService– Book business logicIAuthorService/AuthorService– Author business logicQueueService– Azure Queue message dispatch
API Controllers
BooksController– GET triggers durable function viabook-get-queue, POST queues tobook-queueAuthorsController– Standard CRUD operations
Azure Functions
BookQueueProcessor (Queue-triggered)
[Function("ProcessBookCreation")]
public async Task Run(
[QueueTrigger("book-queue", Connection = "AzureWebJobsStorage")] string queueMessage)
{
var bookMessage = JsonSerializer.Deserialize<BookMessage>(queueMessage);
// Creates book + authors in single transaction
}
BookDurableFunction (Orchestrator)
[Function("BookDurableOrchestrator")]
public async Task<string> RunOrchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
{
var result = await context.CallActivityAsync<string>("ProcessGetRequest", input);
return result;
}
Message Queue Flow
GET /api/books → book-get-queue → Durable Function → Orchestration
POST /api/books → book-queue → Azure Function → DB Insert
Testing
16 xUnit tests with Moq covering:
- Service layer behavior (BookService)
- Repository CRUD operations (InMemory EF provider)
Configuration
appsettings.json– SQL Server connection, Azurite queue configlocal.settings.json– Azure Functions runtime settings- Two queues:
book-queue,book-get-queue - Connection string:
UseDevelopmentStorage=true(Azurite)
Documentation
- SETUP.md – Installation and database setup
- QUICKSTART.md – Three-terminal startup sequence
- ARCHITECTURE.md – System diagrams and data flows
Note that along the way I added to my prompt, and CoPilot adjusted what it was doing accordingly.
Merge info
Review requested
Review has been requested on this pull request. It is not required to merge. Learn more about requesting a pull request review.
No conflicts with base branch
Merging can be performed automatically.
This pull request is still a work in progress
CoPilot then asked me to review the code (Pull Request):

I then clicked on Merge and it provided me with a form for the title and description, already filled out for me to modify and approve
Readme file
CoPilot documented what it did in the ReadMe…
Architecture Overview
This is an ASP.NET Core Web API with a SQL Server backend, using Azure Functions and Azure Storage Queues (via Azurite) for asynchronous processing.
Key Components
- ASP.NET Web API – RESTful API with controllers
- SQL Server Database – Two tables: Books and Authors
- Azure Functions – Asynchronous processing
- Durable Function for GET requests
- Queue-triggered Function for POST requests
- Azure Storage Queues (via Azurite) – Message bus
- Service Layer – Business logic
- Repository Pattern – Data access layer
- Unit Tests – xUnit with Moq
Database Schema
- Books Table: Id, Title, ISBN, PublishedDate, Price
- Authors Table: Id, FirstName, LastName, Email, BookId (FK)
Relationship: One Book can have many Authors
Request Flow
GET /api/books
- API receives request
- Sends message to
book-get-queue(Azurite) - Durable Function reads from queue and orchestrates processing
- API returns book data from database
POST /api/books
- API receives BookMessage
- Sends message to
book-queue(Azurite) - Azure Function (queue-triggered) reads from queue
- Function creates book and authors in database
- API returns 202 Accepted
Quick Start
See SETUP.md for detailed setup instructions.
Running Tests
cd BookstoreAPI.Tests dotnet test
All 16 unit tests cover:
- Service layer (8 tests)
- Repository layer (8 tests)
Starting
CoPilot gave me step by step instructions on how to use this…
Bookstore API Setup Guide
Prerequisites
- .NET 8.0 or later SDK
- SQL Server or LocalDB
- Node.js (for Azurite)
- Azurite (Azure Storage Emulator)
Setup Instructions
1. Install Azurite
npm install -g azurite
2. Start Azurite
Run Azurite to provide local Azure Storage emulation (including queues):
azurite --silent --location ./azurite --debug ./azurite/debug.log
This will start:
- Blob service on
http://127.0.0.1:10000 - Queue service on
http://127.0.0.1:10001(message bus) - Table service on
http://127.0.0.1:10002
3. Database Setup
Update the connection string in appsettings.json if needed:
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=BookstoreDb;Trusted_Connection=True;MultipleActiveResultSets=true"
Create the database:
cd BookstoreAPI dotnet ef migrations add InitialCreate dotnet ef database update
4. Run the Application
Start the API
cd BookstoreAPI dotnet run
Start the Azure Functions
cd BookstoreAPI.Functions func start
Or if using dotnet directly:
cd BookstoreAPI.Functions dotnet run
5. Run Tests
cd BookstoreAPI.Tests dotnet test
Architecture
Components
- ASP.NET Web API (
BookstoreAPI)- Controllers for Books and Authors
- Service layer for business logic
- Repository layer for data access
- Queue service for message bus integration
- Azure Functions (
BookstoreAPI.Functions)- GET Durable Function: Listens to
book-get-queuevia Azurite - POST Azure Function: Processes book creation from
book-queue
- GET Durable Function: Listens to
- Database
- SQL Server with two tables:
BooksandAuthors Authorshas a foreign key toBooks(BookId)
- SQL Server with two tables:
- Client →
GET /api/books - API sends message to
book-get-queue(Azurite) - Durable Function reads from queue
- API returns book data from database
POST Request Flow
- Client →
POST /api/bookswith BookMessage - API sends message to
book-queue(Azurite) - Azure Function reads from queue
- Azure Function adds book to database
- API returns 202 Accepted
Queue Configuration
- GET Queue:
book-get-queue– Used by Durable Function - POST Queue:
book-queue– Used by regular Azure Function
Both queues use Azurite as the message bus with connection string:
UseDevelopmentStorage=true
API Endpoints
Books
GET /api/books– Get all books (triggers durable function)GET /api/books/{id}– Get book by IDPOST /api/books– Create book (via Azure Function)PUT /api/books/{id}– Update bookDELETE /api/books/{id}– Delete book
Authors
GET /api/authors– Get all authorsGET /api/authors/{id}– Get author by IDGET /api/authors/book/{bookId}– Get authors by bookPOST /api/authors– Create authorPUT /api/authors/{id}– Update authorDELETE /api/authors/{id}– Delete author
Testing
The solution includes unit tests using xUnit and Moq for:
- Service layer
- Repository layer
- Controllers
Clone
Once I got over being stunned by all this, I cloned the repository
Quick Start
I then took a look at the Quick Start Guid
Prerequisites Check
# Check .NET SDK dotnet --version # Should be 10.0 or later # Check Node.js (for Azurite) node --version # Install Azurite globally npm install -g azurite
Oops, didn’t have node. No problem, a quick trip to Google revealed where I could get it and installation was a snap. Note that the Node installer also installed chocolatey, which took a few minutes.
Step-by-Step Startup
1. Start Azurite (Terminal 1)
cd BookstoreAPI mkdir -p azurite azurite --silent --location ./azurite --debug ./azurite/debug.log
Azurite will start on:
- Blob: http://127.0.0.1:10000
- Queue: http://127.0.0.1:10001 ← Message bus for our app
- Table: http://127.0.0.1:10002
2. Start Azure Functions (Terminal 2)
cd BookstoreAPI.Functions # Copy template if needed cp local.settings.template.json local.settings.json # Run functions dotnet run
Functions will listen to:
book-queue– For POST requestsbook-get-queue– For GET requests
3. Start Web API (Terminal 3)
cd BookstoreAPI # Run the API dotnet run
API will start on: http://localhost:5000 (or check console for actual port)
4. Test the API
Using curl:
# GET all books (triggers durable function)
curl http://localhost:5000/api/books
# POST a new book (uses Azure Function)
curl -X POST http://localhost:5000/api/books \
-H "Content-Type: application/json" \
-d '{
"title": "Test Book",
"isbn": "123-456-789",
"publishedDate": "2024-01-01T00:00:00",
"price": 29.99,
"authors": [
{
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com"
}
]
}'
# GET a specific book
curl http://localhost:5000/api/books/1
# GET all authors
curl http://localhost:5000/api/authors
# GET authors for a specific book
curl http://localhost:5000/api/authors/book/1
Using the .http file (VS Code with REST Client extension):
# Open BookstoreAPI/BookstoreAPI.http
# Click "Send Request" on any request
Troubleshooting
Database Not Found
cd BookstoreAPI # Install EF Core tools dotnet tool install --global dotnet-ef # Create/update database dotnet ef database update
Azurite Not Starting
# Kill any running instances pkill azurite # Clear data and restart rm -rf azurite mkdir azurite azurite --silent --location ./azurite
Functions Not Triggering
- Check Azurite is running
- Check connection string in
local.settings.json:UseDevelopmentStorage=true - Check function logs for errors
- Verify queues exist in Azurite (can use Azure Storage Explorer)
Build Errors
# Clean and rebuild dotnet clean dotnet restore dotnet build
Running Tests
cd BookstoreAPI.Tests dotnet test --verbosity normal
Expected output:
Passed! - Failed: 0, Passed: 16, Skipped: 0, Total: 16
Monitoring
View Queue Messages
Install Azure Storage Explorer to view:
- Queue messages in
book-queueandbook-get-queue - Connection: Use “Attach to local emulator”
View Function Logs
Azure Functions will output logs to the console showing:
- Queue triggers firing
- Durable function orchestrations
- Database operations
View API Logs
ASP.NET Core will output logs showing:
- HTTP requests
- Controller actions
- Queue message sends
Next Steps
- Add Swagger/OpenAPI – Already configured, visit
/openapiendpoints - Add Authentication – Implement JWT or OAuth
- Add Validation – Data annotations on models
- Add More Tests – Controller tests, integration tests
- Deploy to Azure – Use Azure App Service + Azure Functions + Azure SQL
Project Structure
BookstoreAPI/
├── BookstoreAPI/ # Web API
│ ├── Controllers/ # API endpoints
│ ├── Models/ # Data models
│ ├── Services/ # Business logic
│ ├── Data/ # EF Core + Repositories
│ └── Migrations/ # Database migrations
├── BookstoreAPI.Functions/ # Azure Functions
│ ├── BookQueueProcessor.cs # POST handler
│ └── BookDurableFunction.cs # GET handler
└── BookstoreAPI.Tests/ # Unit tests
├── Services/ # Service tests
└── Repositories/ # Repository tests
Pretty impresive. More than impressive. If I were in my 20s and programming, I would think long and hard about the implications. It is still early days for LLMs, one can only imagine what will be in 5 years.
We’ll get this going and put in a bit of data in the next blog post.





































