title: "Pet Database API Reference" description: "Reference documentation for the Pet Database API, including CRUD operations and architecture details" category: reference tags:
- api
- database
- pets
- repository related:
- database-api.md
- ../patterns/repository-pattern.md
- ../../02_examples/database-service-example.md last_updated: April 9, 2025 version: 1.0
Pet Database API
Overview
The Pet Database API provides a complete set of CRUD (Create, Read, Update, Delete) operations for managing pet records in the database. This API is built following clean architecture principles, with proper separation between database abstractions in the core layer and pet-specific implementations in the application layer.
This reference document details all endpoints, data structures, request/response formats, and integration patterns for working with pet data in Navius applications.
Endpoints
Get All Pets
Retrieves a list of all pets in the database.
URL: /petdb
Method: GET
Query Parameters:
limit
(optional): Maximum number of records to return (default: 100)offset
(optional): Number of records to skip (default: 0)species
(optional): Filter by pet speciessort
(optional): Sort field (e.g.,name
,age
,created_at
)order
(optional): Sort order (asc
ordesc
, default:asc
)
Authentication: Public
Response:
{
"data": [
{
"id": "uuid-string",
"name": "Pet Name",
"species": "Pet Species",
"age": 5,
"created_at": "2024-06-01T12:00:00.000Z",
"updated_at": "2024-06-01T12:00:00.000Z"
},
...
],
"pagination": {
"total": 150,
"limit": 100,
"offset": 0,
"next_offset": 100
}
}
Status Codes:
200 OK
: Successfully retrieved the list of pets400 Bad Request
: Invalid query parameters500 Internal Server Error
: Server encountered an error
Curl Example:
# Get all pets
curl -X GET http://localhost:3000/petdb
# Get pets with pagination and filtering
curl -X GET "http://localhost:3000/petdb?limit=10&offset=20&species=dog&sort=age&order=desc"
Code Example:
#![allow(unused)] fn main() { // Client-side request to get all pets async fn get_all_pets(client: &Client, limit: Option<u32>, species: Option<&str>) -> Result<PetListResponse> { let mut req = client.get("http://localhost:3000/petdb"); if let Some(limit) = limit { req = req.query(&[("limit", limit.to_string())]); } if let Some(species) = species { req = req.query(&[("species", species)]); } let response = req.send().await?; if response.status().is_success() { Ok(response.json::<PetListResponse>().await?) } else { Err(format!("Failed to get pets: {}", response.status()).into()) } } }
Get Pet by ID
Retrieves a specific pet by its unique identifier.
URL: /petdb/:id
Method: GET
URL Parameters:
id
: UUID of the pet to retrieve
Authentication: Public
Response:
{
"id": "uuid-string",
"name": "Pet Name",
"species": "Pet Species",
"age": 5,
"created_at": "2024-06-01T12:00:00.000Z",
"updated_at": "2024-06-01T12:00:00.000Z"
}
Status Codes:
200 OK
: Successfully retrieved the pet400 Bad Request
: Invalid UUID format404 Not Found
: Pet with the given ID was not found500 Internal Server Error
: Server encountered an error
Curl Example:
curl -X GET http://localhost:3000/petdb/550e8400-e29b-41d4-a716-446655440000
Code Example:
#![allow(unused)] fn main() { // Client-side request to get a pet by ID async fn get_pet_by_id(client: &Client, id: &str) -> Result<Pet> { let response = client .get(&format!("http://localhost:3000/petdb/{}", id)) .send() .await?; match response.status() { StatusCode::OK => Ok(response.json::<Pet>().await?), StatusCode::NOT_FOUND => Err("Pet not found".into()), _ => Err(format!("Failed to get pet: {}", response.status()).into()) } } }
Create Pet
Creates a new pet in the database.
URL: /petdb
Method: POST
Authentication: Required
Request Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
Request Body:
{
"name": "Pet Name",
"species": "Pet Species",
"age": 5
}
Validation Rules:
name
: Required, cannot be empty, maximum 50 charactersspecies
: Required, cannot be emptyage
: Required, must be non-negative, must be realistic (0-100)
Response:
{
"id": "uuid-string",
"name": "Pet Name",
"species": "Pet Species",
"age": 5,
"created_at": "2024-06-01T12:00:00.000Z",
"updated_at": "2024-06-01T12:00:00.000Z"
}
Status Codes:
201 Created
: Successfully created the pet400 Bad Request
: Validation error in the request data401 Unauthorized
: Authentication required500 Internal Server Error
: Server encountered an error
Curl Example:
curl -X POST http://localhost:3000/petdb \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-d '{"name":"Fluffy","species":"cat","age":3}'
Code Example:
#![allow(unused)] fn main() { // Client-side request to create a pet async fn create_pet(client: &Client, token: &str, pet: &CreatePetRequest) -> Result<Pet> { let response = client .post("http://localhost:3000/petdb") .header("Authorization", format!("Bearer {}", token)) .json(pet) .send() .await?; match response.status() { StatusCode::CREATED => Ok(response.json::<Pet>().await?), StatusCode::BAD_REQUEST => { let error = response.json::<ErrorResponse>().await?; Err(format!("Validation error: {}", error.message).into()) }, _ => Err(format!("Failed to create pet: {}", response.status()).into()) } } }
Update Pet
Updates an existing pet in the database.
URL: /petdb/:id
Method: PUT
URL Parameters:
id
: UUID of the pet to update
Authentication: Required
Request Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
Request Body:
{
"name": "Updated Name", // Optional
"species": "Updated Species", // Optional
"age": 6 // Optional
}
Validation Rules:
name
(if provided): Cannot be empty, maximum 50 charactersspecies
(if provided): Cannot be emptyage
(if provided): Must be non-negative, must be realistic (0-100)
Response:
{
"id": "uuid-string",
"name": "Updated Name",
"species": "Updated Species",
"age": 6,
"created_at": "2024-06-01T12:00:00.000Z",
"updated_at": "2024-06-01T13:00:00.000Z"
}
Status Codes:
200 OK
: Successfully updated the pet400 Bad Request
: Invalid UUID format or validation error401 Unauthorized
: Authentication required404 Not Found
: Pet with the given ID was not found500 Internal Server Error
: Server encountered an error
Curl Example:
curl -X PUT http://localhost:3000/petdb/550e8400-e29b-41d4-a716-446655440000 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-d '{"name":"Fluffy Jr.","age":4}'
Code Example:
#![allow(unused)] fn main() { // Client-side request to update a pet async fn update_pet(client: &Client, token: &str, id: &str, update: &UpdatePetRequest) -> Result<Pet> { let response = client .put(&format!("http://localhost:3000/petdb/{}", id)) .header("Authorization", format!("Bearer {}", token)) .json(update) .send() .await?; match response.status() { StatusCode::OK => Ok(response.json::<Pet>().await?), StatusCode::NOT_FOUND => Err("Pet not found".into()), StatusCode::BAD_REQUEST => { let error = response.json::<ErrorResponse>().await?; Err(format!("Validation error: {}", error.message).into()) }, _ => Err(format!("Failed to update pet: {}", response.status()).into()) } } }
Delete Pet
Deletes a pet from the database.
URL: /petdb/:id
Method: DELETE
URL Parameters:
id
: UUID of the pet to delete
Authentication: Required
Request Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Response: No content
Status Codes:
204 No Content
: Successfully deleted the pet400 Bad Request
: Invalid UUID format401 Unauthorized
: Authentication required404 Not Found
: Pet with the given ID was not found500 Internal Server Error
: Server encountered an error
Curl Example:
curl -X DELETE http://localhost:3000/petdb/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Code Example:
#![allow(unused)] fn main() { // Client-side request to delete a pet async fn delete_pet(client: &Client, token: &str, id: &str) -> Result<()> { let response = client .delete(&format!("http://localhost:3000/petdb/{}", id)) .header("Authorization", format!("Bearer {}", token)) .send() .await?; match response.status() { StatusCode::NO_CONTENT => Ok(()), StatusCode::NOT_FOUND => Err("Pet not found".into()), _ => Err(format!("Failed to delete pet: {}", response.status()).into()) } } }
Data Models
Pet
#![allow(unused)] fn main() { /// Represents a pet entity in the database #[derive(Debug, Serialize, Deserialize)] pub struct Pet { /// Unique identifier pub id: String, /// Pet name pub name: String, /// Pet species pub species: String, /// Pet age in years pub age: u32, /// Creation timestamp pub created_at: DateTime<Utc>, /// Last update timestamp pub updated_at: DateTime<Utc>, } }
CreatePetRequest
#![allow(unused)] fn main() { /// Request to create a new pet #[derive(Debug, Serialize, Deserialize, Validate)] pub struct CreatePetRequest { /// Pet name #[validate(required, length(min = 1, max = 50))] pub name: String, /// Pet species #[validate(required, length(min = 1))] pub species: String, /// Pet age in years #[validate(range(min = 0, max = 100))] pub age: u32, } }
UpdatePetRequest
#![allow(unused)] fn main() { /// Request to update an existing pet #[derive(Debug, Serialize, Deserialize, Validate)] pub struct UpdatePetRequest { /// Optional updated pet name #[validate(length(min = 1, max = 50))] pub name: Option<String>, /// Optional updated pet species #[validate(length(min = 1))] pub species: Option<String>, /// Optional updated pet age #[validate(range(min = 0, max = 100))] pub age: Option<u32>, } }
PetListResponse
#![allow(unused)] fn main() { /// Response containing a list of pets with pagination #[derive(Debug, Serialize, Deserialize)] pub struct PetListResponse { /// List of pet records pub data: Vec<Pet>, /// Pagination information pub pagination: Pagination, } /// Pagination metadata #[derive(Debug, Serialize, Deserialize)] pub struct Pagination { /// Total number of records pub total: u64, /// Number of records in current page pub limit: u32, /// Starting offset pub offset: u32, /// Next page offset (null if last page) pub next_offset: Option<u32>, } }
Architecture
The Pet API follows a clean architecture approach with the following layers:
Core Layer
- EntityRepository: Generic repository interface in
core/database/repository.rs
that defines standard CRUD operations - Database Utilities: Common database functions in
core/database/utils.rs
for transaction management and error handling
#![allow(unused)] fn main() { /// Generic repository interface for database entities pub trait EntityRepository<T, ID, C, U> { /// Create a new entity async fn create(&self, data: &C) -> Result<T>; /// Find an entity by ID async fn find_by_id(&self, id: &ID) -> Result<Option<T>>; /// Find all entities matching criteria async fn find_all(&self, criteria: &HashMap<String, Value>) -> Result<Vec<T>>; /// Update an entity async fn update(&self, id: &ID, data: &U) -> Result<T>; /// Delete an entity async fn delete(&self, id: &ID) -> Result<bool>; } }
Application Layer
- PetRepository: Implementation of the
EntityRepository
for Pet entities inapp/database/repositories/pet_repository.rs
- PetService: Business logic and validation in
app/services/pet_service.rs
- API Endpoints: HTTP handlers in
app/api/pet_db.rs
that expose the functionality via REST API
#![allow(unused)] fn main() { /// Implementation of the Pet repository pub struct PetRepository { pool: Pool<Postgres>, } impl PetRepository { /// Create a new PetRepository pub fn new(pool: Pool<Postgres>) -> Self { Self { pool } } } impl EntityRepository<Pet, String, CreatePetRequest, UpdatePetRequest> for PetRepository { async fn create(&self, data: &CreatePetRequest) -> Result<Pet> { // Implementation... } async fn find_by_id(&self, id: &String) -> Result<Option<Pet>> { // Implementation... } // Other implementations... } }
#![allow(unused)] fn main() { /// Pet service handling business logic pub struct PetService { repository: Arc<dyn EntityRepository<Pet, String, CreatePetRequest, UpdatePetRequest>>, } impl PetService { pub fn new(repository: Arc<dyn EntityRepository<Pet, String, CreatePetRequest, UpdatePetRequest>>) -> Self { Self { repository } } /// Create a new pet with validation pub async fn create_pet(&self, data: CreatePetRequest) -> Result<Pet> { // Validate data data.validate()?; // Create pet in database self.repository.create(&data).await } // Other methods... } }
This separation allows for clear responsibilities:
- Core Layer: Generic interfaces and abstractions
- App Layer: Pet-specific implementations
Implementation
HTTP Handlers
#![allow(unused)] fn main() { /// Pet database API endpoints pub fn pet_routes() -> Router { Router::new() .route("/", get(get_all_pets).post(create_pet)) .route("/:id", get(get_pet_by_id).put(update_pet).delete(delete_pet)) } /// Handler for GET /petdb async fn get_all_pets( State(state): State<AppState>, Query(params): Query<GetAllPetsParams>, ) -> Result<Json<PetListResponse>, AppError> { let pet_service = &state.pet_service; let criteria = HashMap::new(); // Convert query params to criteria... let pets = pet_service.find_all_pets(criteria, params.limit, params.offset).await?; Ok(Json(pets)) } /// Handler for POST /petdb async fn create_pet( State(state): State<AppState>, auth: AuthExtractor, Json(data): Json<CreatePetRequest>, ) -> Result<(StatusCode, Json<Pet>), AppError> { // Verify permissions if !auth.has_permission("create:pets") { return Err(AppError::forbidden("Insufficient permissions")); } let pet_service = &state.pet_service; let pet = pet_service.create_pet(data).await?; Ok((StatusCode::CREATED, Json(pet))) } // Other handlers... }
Integration with Router
#![allow(unused)] fn main() { // In app/api/router.rs pub fn api_routes() -> Router { Router::new() // Other routes... .nest("/petdb", pet_routes()) // Other routes... } }
Error Handling
The API follows a consistent error handling approach:
- 400 Bad Request: Input validation errors (invalid data, format issues)
- 401 Unauthorized: Authentication issues
- 404 Not Found: Resource not found
- 500 Internal Server Error: Database or server-side errors
All error responses include:
- HTTP status code
- Error message
- Error type
Example error response:
{
"code": 400,
"message": "Pet name cannot be empty",
"error_type": "validation_error",
"details": [
{
"field": "name",
"error": "Cannot be empty"
}
]
}
Error Types
#![allow(unused)] fn main() { /// Application error types pub enum AppErrorType { /// Invalid input data ValidationError, /// Resource not found NotFound, /// Authentication error Unauthorized, /// Permission error Forbidden, /// Database error DatabaseError, /// Server error InternalError, } /// Application error structure pub struct AppError { /// HTTP status code pub code: StatusCode, /// Error message pub message: String, /// Error type pub error_type: AppErrorType, /// Additional error details pub details: Option<Vec<ErrorDetail>>, } /// Detailed error information pub struct ErrorDetail { /// Field name (for validation errors) pub field: String, /// Error description pub error: String, } }
Error Conversion
#![allow(unused)] fn main() { // Example of converting validation errors impl From<ValidationError> for AppError { fn from(error: ValidationError) -> Self { let details = error.field_errors().iter() .map(|(field, errors)| { ErrorDetail { field: field.to_string(), error: errors[0].message.clone().unwrap_or_default(), } }) .collect(); AppError { code: StatusCode::BAD_REQUEST, message: "Validation failed".to_string(), error_type: AppErrorType::ValidationError, details: Some(details), } } } }
Testing
The API includes comprehensive tests:
- Unit Tests: For core database abstractions and PetService business logic
- API Endpoint Tests: Testing the HTTP layer and response handling
Example Unit Test
#![allow(unused)] fn main() { #[tokio::test] async fn test_pet_service_create() { // Setup mock repository let mock_repo = MockPetRepository::new(); mock_repo.expect_create() .with(predicate::function(|req: &CreatePetRequest| { req.name == "Fluffy" && req.species == "cat" && req.age == 3 })) .returning(|_| { Ok(Pet { id: "test-uuid".to_string(), name: "Fluffy".to_string(), species: "cat".to_string(), age: 3, created_at: Utc::now(), updated_at: Utc::now(), }) }); let service = PetService::new(Arc::new(mock_repo)); // Call service method let create_req = CreatePetRequest { name: "Fluffy".to_string(), species: "cat".to_string(), age: 3, }; let result = service.create_pet(create_req).await; // Verify result assert!(result.is_ok()); let pet = result.unwrap(); assert_eq!(pet.name, "Fluffy"); assert_eq!(pet.species, "cat"); assert_eq!(pet.age, 3); } }
Example API Test
#![allow(unused)] fn main() { #[tokio::test] async fn test_create_pet_endpoint() { // Setup test app with mocked dependencies let app = test_app().await; // Test valid request let response = app .client .post("/petdb") .header("Authorization", "Bearer test-token") .json(&json!({ "name": "Fluffy", "species": "cat", "age": 3 })) .send() .await; assert_eq!(response.status(), StatusCode::CREATED); let pet: Pet = response.json().await.unwrap(); assert_eq!(pet.name, "Fluffy"); // Test invalid request let response = app .client .post("/petdb") .header("Authorization", "Bearer test-token") .json(&json!({ "name": "", // Empty name (invalid) "species": "cat", "age": 3 })) .send() .await; assert_eq!(response.status(), StatusCode::BAD_REQUEST); } }