Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
**/bin/
**/obj/
.vscode/
.git/
.gitignore
.env
README.md
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SA_PASSWORD=your-strong-password-here
58 changes: 46 additions & 12 deletions .github/workflows/dotnet-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,62 @@ on:
pull_request:
branches: [main]

env:
DOTNET_VERSION: "10.0.x"
SA_PASSWORD: ${{ secrets.BOOKSAPI_SA_PASSWORD }}
ConnectionStrings__DefaultConnection: "Server=localhost,14333;Database=BooksDb;User Id=sa;Password=${{ secrets.BOOKSAPI_SA_PASSWORD }};Encrypt=False;TrustServerCertificate=True"

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "9.0.x"
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: Start SQL Server container
run: docker compose up -d sqlserver

- name: Wait for SQL Server
run: |
for i in {1..30}; do
if docker exec booksapi-sqlserver /opt/mssql-tools18/bin/sqlcmd \
-S localhost \
-U sa \
-P "$SA_PASSWORD" \
-C \
-Q "SELECT 1" > /dev/null 2>&1; then
echo "SQL Server is ready."
exit 0
fi

echo "Waiting for SQL Server..."
sleep 5
done

echo "SQL Server did not become ready in time."
docker logs booksapi-sqlserver
exit 1

- name: Initialize database
run: |
docker exec -i booksapi-sqlserver /opt/mssql-tools18/bin/sqlcmd \
-S localhost \
-U sa \
-P "$SA_PASSWORD" \
-C \
-i /dev/stdin < sql/init/01-create-books-db.sql

- name: Restore
run: dotnet restore
working-directory: ./BooksAPIDapper
run: dotnet restore BooksAPIDapper.sln

- name: Build
run: dotnet build --configuration Release --no-restore
working-directory: ./BooksAPIDapper

# CI TODO
# Remove this step if you don't have tests yet
# - name: Test
# run: dotnet test --no-build --verbosity normal
# working-directory: ./BooksAPIDapper
run: dotnet build BooksAPIDapper.sln --configuration Release --no-restore

- name: Test
run: dotnet test BooksAPIDapper.sln --configuration Release --no-build --verbosity normal
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,5 @@ MigrationBackup/
.ionide/

# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
.env
2 changes: 1 addition & 1 deletion BooksAPIDapper.Tests/BooksAPIDapper.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
Expand Down
2 changes: 1 addition & 1 deletion BooksAPIDapper/BooksAPIDapper.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
Expand Down
4 changes: 2 additions & 2 deletions BooksAPIDapper/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Data Source=localhost\\SQLEXPRESS;Initial Catalog=BooksDb3;Integrated Security=True;Pooling=False;Encrypt=False;Trust Server Certificate=True"
"DefaultConnection": ""
}
}
}
24 changes: 24 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

COPY BooksAPIDapper.sln ./
COPY BooksAPIDapper/BooksAPIDapper.csproj BooksAPIDapper/
COPY BooksAPIDapper.Tests/BooksAPIDapper.Tests.csproj BooksAPIDapper.Tests/

RUN dotnet restore

COPY . .

RUN dotnet publish BooksAPIDapper/BooksAPIDapper.csproj \
-c Release \
-o /app/publish \
--no-restore

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app

COPY --from=build /app/publish .

EXPOSE 8080

ENTRYPOINT ["dotnet", "BooksAPIDapper.dll"]
205 changes: 183 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,210 @@
# 📚 Books API (Dapper, .NET 9)
# 📚 Books API (Dapper, .NET 10)

![.NET](https://img.shields.io/badge/.NET-9.0-blueviolet)
![.NET](https://img.shields.io/badge/.NET-10.0-blueviolet)
![License](https://img.shields.io/badge/License-MIT-green)
![Dapper](https://img.shields.io/badge/Dapper-ORM-orange)
![Docker](https://img.shields.io/badge/Docker-Compose-blue)
![Status](https://img.shields.io/badge/Status-Stable-brightgreen)
![Build](https://github.com/edwardgnt/BooksAPI/actions/workflows/dotnet-ci.yml/badge.svg)

Production-style REST API with **DTOs**, **Repository Pattern**, and **Filtering + Sorting + Pagination**.
- Data access: **Dapper** (`Microsoft.Data.SqlClient`)
- Error shape: **ProblemDetails** (RFC 7807)
- Extras: **Soft delete**, **Search**, **Date-range guardrails**
Production-style REST API built with **.NET 10**, **Dapper**, **DTOs**, **Repository Pattern**, and **Filtering + Sorting + Pagination**.

This project also includes **xUnit integration tests**, **GitHub Actions CI**, and **Docker Compose support** for running the API with a containerized SQL Server database.

## 🧰 Tech Stack

- **.NET 9 Web API** — Backend framework
- **Dapper** — Lightweight data access
- **SQL Server** — Database
- **Repository Pattern** — Clean architecture & separation of concerns
- **DTOs** — Safe data transfer between layers
- **Dependency Injection** — For maintainable, testable code
- **OpenAPI** — For API exploration and testing
- **.NET 10 Web API** — Backend framework
- **Dapper** — Lightweight data access
- **SQL Server** — Relational database
- **Docker Compose** — Local API + SQL Server orchestration
- **Repository Pattern** — Clean architecture and separation of concerns
- **DTOs** — Safe data transfer between layers
- **Dependency Injection** — Maintainable and testable services
- **ProblemDetails** — Standards-based API error responses
- **xUnit** — Automated integration testing
- **GitHub Actions** — CI pipeline for restore, build, and test

## 🚀 Features
- Thin controllers, repository behind `IBookRepository`

- Thin controllers with repository abstraction behind `IBookRepository`
- DTOs: `BookCreateDto`, `BookUpdateDto`, `BookReadDto`, `BookFilterDto`
- Query params: `search`, `minPrice`, `maxPrice`, `sort`, `start`, `end`, `page`, `pageSize`
- Sorting: `price_asc|price_desc|title_asc|title_desc|year_asc|year_desc|created_asc|created_desc`
- Sorting: `price_asc`, `price_desc`, `title_asc`, `title_desc`, `year_asc`, `year_desc`, `created_asc`, `created_desc`
- Pagination wrapper: `PagedResult<T>` → `{ items, totalCount, page, pageSize }`
- Soft delete via `IsArchived`
- SQL Server container with persistent Docker volume
- Repeatable database setup script with seed data
- Integration tests using `WebApplicationFactory`

## 🧭 Example Endpoints

```http
GET /api/books
GET /api/books?search=king&sort=price_desc
GET /api/books?search=clean&sort=price_desc
GET /api/books?minPrice=10&maxPrice=50
GET /api/books?startDate=2024-01-01&endDate=2024-12-31&page=1&pageSize=10
GET /api/books?start=2024-01-01&end=2024-12-31&page=1&pageSize=10
POST /api/books
PUT /api/books/{id}
DELETE /api/books/{id}
```

## 🏁 Getting Started

Clone the repository:

```bash
git clone https://github.com/edwardgnt/BooksAPI.git
cd BooksAPI
```

Restore and build:

🏁 Getting Started
git clone https://github.com/<your-username>/BooksAPIDapper.git
cd BooksAPIDapper
```bash
dotnet restore
dotnet run
# App will print e.g. https://localhost:7205
dotnet build
```

Run tests:

```bash
dotnet test
```

## 🐳 Running with Docker Compose

This project includes a Docker Compose setup for running the API with SQL Server.

### Services

- `api` — .NET 10 Web API container
- `sqlserver` — SQL Server 2022 Developer container
- `booksapi_sql_data` — Persistent Docker volume for SQL Server data

### 1. Create a `.env` file

Create a `.env` file in the solution root:

```env
SA_PASSWORD=YourStrongPassword123!
```

> `.env` is ignored by Git and should not be committed.

### 2. Start SQL Server

```bash
docker compose up -d sqlserver
```

Wait for SQL Server to finish starting. You can check the logs with:

```bash
docker logs booksapi-sqlserver
```

Look for a message indicating SQL Server is ready for client connections.

### 3. Load the `.env` value into your shell

```bash
set -a
source .env
set +a
```

### 4. Initialize the database

Run the database setup script:

```bash
docker exec -i booksapi-sqlserver /opt/mssql-tools18/bin/sqlcmd \
-S localhost \
-U sa \
-P "$SA_PASSWORD" \
-C \
-i /dev/stdin < sql/init/01-create-books-db.sql
```

This creates:

- `BooksDb`
- `dbo.Books`
- Seed book records

### 5. Start the API

```bash
docker compose up --build
```

The API will be available at:

```text
http://localhost:8080
```

Example request:

```bash
curl http://localhost:8080/api/books
```

## 🗄️ Connecting with SQL Server Management Studio

To view the Docker SQL Server database from SSMS:

```text
Server: localhost,14333
Authentication: SQL Server Authentication
Login: sa
Password: your .env password
Database: BooksDb
```

Then run:

```sql
SELECT Id, Title, Author, YearPublished, CreatedAt, IsArchived, Price
FROM dbo.Books;
```

## 🧪 Testing

Run the test suite:

```bash
dotnet test
```

The project includes integration tests that validate API behavior through the ASP.NET Core test host.

## 🏗️ Architecture

The project follows a clean layered structure:

```text
Controllers
DTOs
IBookRepository
BookRepository
Dapper
SQL Server
```

This keeps API contracts, business flow, and data access responsibilities separated and easier to maintain.

## 📌 Notes

- The app uses Dapper instead of Entity Framework Core for explicit SQL and lightweight data access.
- SQL Server runs in Docker for a repeatable local development environment.
- The API container connects to SQL Server through the Docker Compose service name: `sqlserver`.
- Local tools such as SSMS can connect through the mapped host port: `localhost,14333`.

## 📄 License

MIT
Loading
Loading