Home Features Pricing Security Changelog About Us Blog Contact Sign In →
← Back to Blog

Why we chose an async Python API for an enterprise HR platform

Building an enterprise HR platform from scratch means making a lot of architectural decisions early — decisions that are hard to reverse once you have real users and real data depending on them. Here's how we approached the technology stack for PinoX, and why we made the choices we did.

Why Python 3.11?

Python wasn't the obvious choice for an enterprise backend in 2025. You could make a reasonable case for Go, for TypeScript/Node, or for Java. We chose Python for three reasons: the team's expertise, the quality of the async ecosystem, and the speed of iteration it enables when requirements are still evolving.

Python 3.11 specifically brought meaningful performance improvements — roughly 10–60% faster than 3.10 in our benchmarks for typical API request handling — and better error messages that genuinely help during development. It also introduced task groups for structured concurrency, which we use in several places where we need to run multiple database queries in parallel.

Why FastAPI?

FastAPI hits a sweet spot that few frameworks match: it's fast, it's type-safe by default, and it produces OpenAPI documentation automatically. For an API that needs to be maintainable by a team over years, the combination of Pydantic models for request/response validation and automatic schema generation is genuinely valuable.

The async-first design of FastAPI meant we could write non-blocking I/O handlers without bolting on a separate async library after the fact. Every route handler in PinoX is async — database queries, external HTTP calls, file operations — none of them block the event loop.

"We've seen enterprise Python APIs handle tens of thousands of concurrent requests with sub-100ms p95 latency. The async model matters more than the language when it comes to throughput."

The database: PostgreSQL with asyncpg

We chose PostgreSQL 15 for its maturity, its robust JSON support (useful for flexible schema fields), and its row-level security features that we use to enforce data isolation in our multi-tenant configuration.

For the connection layer, we use asyncpg — a high-performance PostgreSQL driver written specifically for Python's async I/O model. Unlike psycopg2 (which blocks the event loop during queries), asyncpg communicates with PostgreSQL using PostgreSQL's native binary protocol without blocking. This means a single PinoX server process can handle hundreds of concurrent database operations without spawning additional threads.

Our connection pool is initialized with lifespan management — connections are established at startup and reused across requests, eliminating the overhead of connection negotiation on every API call.

The schema: 40+ tables, designed once

We took the time to design the complete database schema before writing any application code. This isn't always the right approach — it works best when you have a clear domain model and stable requirements. HR, it turns out, is a domain with decades of established patterns. The schema for leave management doesn't change much year to year.

40+ tables covering 7 distinct domains: Employee, Leave, Expense, Inventory, Workflow, Admin, and Auth. Every table has created_at, updated_at, created_by, and updated_by fields — audit metadata baked in at the schema level, not the application level.

Frontend: React with a utility CSS system

The PinoX frontend is a React application with client-side routing and component-driven architecture. We made a deliberate decision early on not to use a component library — Material UI, Chakra, or similar. The reason was control: enterprise HR applications have specific interaction patterns that generic component libraries handle awkwardly. Custom components, built once and reused everywhere, gave us the design consistency we needed without fighting against someone else's defaults.

Infrastructure: Microsoft Azure

Hosting on Azure was an easy decision given our CALIGO's existing Azure partnership and our target market's familiarity with Microsoft's enterprise cloud. Azure App Service for the API, Azure Database for PostgreSQL Flexible Server, private VNet for isolation, Azure Monitor for observability, and Azure Key Vault for secret management.

Everything is containerized with Docker. Local development, staging, and production run identical containers — the only difference is environment variables.

What we'd do the same, and what we'd do differently

We'd make the same core technology choices again. Python 3.11, FastAPI, asyncpg, PostgreSQL, React, Azure — all of these have proven to be the right tools for this problem.

What we'd do differently: we underestimated the complexity of the workflow engine. A configurable multi-step approval system with conditional routing and escalation is significantly more complex than it sounds. We'd allocate more design time to that system before implementation if we were starting over.

But that's the nature of building real software: you learn what's hard by building it. The workflow engine is now one of the most powerful parts of PinoX — and it's one of the things our early users mention most frequently as a differentiator.

Ready to simplify your HR?

Join organizations that have moved from spreadsheets to a single intelligent HR platform — and never looked back.