Introduction
Managing a photography business involves juggling clients, bookings, galleries, invoicing, and communication—all while trying to focus on what matters most: taking great photos. After working with several photography businesses, I noticed a common pain point: existing solutions were either too expensive, too complex, or lacked the specific features photographers needed.
That's why I built PhotoManagerAPI, a comprehensive full-stack solution using .NET Core 8, Angular 18, and PostgreSQL. In this post, I'll walk you through the architecture, key features, and technical decisions that make this system both powerful and maintainable.
The Problem: Why Build Custom Software?
Off-the-shelf photography management tools often fall short in several ways:
A custom solution addresses these issues while providing exactly the features needed—nothing more, nothing less.
Architecture Overview
The system follows a clean, modern architecture that separates concerns and enables independent scaling:
Backend: .NET Core 8 Web API
The API layer handles all business logic, data access, and security. Here's why .NET Core was the right choice:
Frontend: Angular 18
Angular provides the structure needed for a complex business application:
Database: PostgreSQL
PostgreSQL handles all data persistence with excellent performance:
Core Features
1. Client Management
Track everything about your clients in one place:
public class Client
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Address { get; set; }
public List<Booking> Bookings { get; set; }
public List<Invoice> Invoices { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? LastContactedAt { get; set; }
}
The client model includes relationships to bookings and invoices, making it easy to see the complete history at a glance.
2. Booking System
Schedule shoots with conflict detection:
public async Task<BookingResult> CreateBookingAsync(CreateBookingDto dto)
{
// Check for scheduling conflicts
var hasConflict = await _context.Bookings
.AnyAsync(b =>
b.PhotographerId == dto.PhotographerId &&
b.Date == dto.Date &&
b.Status != BookingStatus.Cancelled);
if (hasConflict)
{
return BookingResult.Conflict("Time slot already booked");
}
var booking = _mapper.Map<Booking>(dto);
booking.Status = BookingStatus.Pending;
_context.Bookings.Add(booking);
await _context.SaveChangesAsync();
// Send confirmation email
await _emailService.SendBookingConfirmationAsync(booking);
return BookingResult.Success(booking);
}
3. Gallery Management
Organize and share photos with clients:
4. Invoice Generation
Create professional invoices with payment tracking:
public class Invoice
{
public Guid Id { get; set; }
public Guid ClientId { get; set; }
public string InvoiceNumber { get; set; }
public DateTime IssueDate { get; set; }
public DateTime DueDate { get; set; }
public List<InvoiceItem> Items { get; set; }
public decimal Subtotal { get; set; }
public decimal Tax { get; set; }
public decimal Total { get; set; }
public InvoiceStatus Status { get; set; }
public DateTime? PaidAt { get; set; }
}
5. Role-Based Access Control
Different permissions for different team members:
Implementation using ASP.NET Core Identity:
[Authorize(Roles = "Admin,Photographer")] [HttpPost("bookings")] public async Task<IActionResult> CreateBooking([FromBody] CreateBookingDto dto) { var result = await _bookingService.CreateBookingAsync(dto); return Ok(result); }
[Authorize(Roles = "Admin")] [HttpDelete("users/{id}")] public async Task<IActionResult> DeleteUser(Guid id) { await _userService.DeleteUserAsync(id); return NoContent(); }
Technical Highlights
Performance Optimization
I achieved significant performance improvements through:
Database Indexing:
CREATE INDEX idx_bookings_photographer_date ON bookings(photographer_id, date) WHERE status != 'Cancelled';
CREATE INDEX idx_invoices_client_status ON invoices(client_id, status);
Query Optimization:
AsNoTracking() for read-only queriesResults:
Security Best Practices
Security was a top priority throughout development:
Deployment Strategy
The application deploys in minutes using:
Lessons Learned
What Worked Well
1. Clean Architecture: Separating concerns made testing and maintenance easy 2. TypeScript Everywhere: Catching errors at compile time saved hours 3. PostgreSQL: Never had performance issues, even with complex queries 4. Angular Signals: The new reactive system is a game-changer
What I'd Do Differently
1. Start with Docker: Would have saved setup time for team members 2. More automated testing: Integration tests would catch regressions earlier 3. GraphQL instead of REST: Would reduce over-fetching on complex queries 4. Microservices for galleries: S3 operations could be a separate service
Getting Started
Want to build something similar? Here's my recommended approach:
Phase 1: MVP (2-3 weeks)
Phase 2: Core Features (3-4 weeks)
Phase 3: Polish (2-3 weeks)
Tech Stack Recommendations
For Small Teams (1-2 photographers):
For Growing Businesses (3-10 photographers):
For Agencies (10+ photographers):
Conclusion
Building PhotoManagerAPI taught me that custom software isn't just about writing code—it's about understanding the business problem and crafting a solution that fits perfectly. The combination of .NET Core, Angular, and PostgreSQL proved to be powerful, reliable, and maintainable.
Whether you're building for photographers, consultants, or any service business, the principles remain the same: clean architecture, security first, and always optimize based on real-world usage data.
Key Takeaways:
Have questions about building a similar system? Feel free to reach out—I love talking about architecture and solving real business problems with code.
Tech Stack:
Source Code: Available on GitHub (link in my portfolio)
Related Posts
Building with Angular + Firebase? Check out my Photography E-Commerce Platform that combines Firebase auth with Stripe payments and AWS S3 storage.
Self-hosting your projects? Learn how I host production apps on a Raspberry Pi with PM2, Nginx, and Tailscale.
About the Author: Ricardo Gil is a full-stack software engineer specializing in .NET/C#, Angular, and cloud platforms. Read more or subscribe for updates.
