itsjorigoBLOG
←︎ Back to BlogEngineering

10 min read

Designing an HRIS from Scratch with .NET Core and React

SCROLL

A walkthrough of the system design decisions behind building an in-house HR information system — from requirements gathering to Azure deployment.

Building an internal HR system from scratch is one of those projects that sounds simple — it is just CRUD for employees, right? — until you are three weeks in and discovering that leave management alone has seventeen edge cases. Here is how I approached the design and what I would do the same or differently.

Requirements Gathering

I spent the first week entirely on requirements — on-site with the HR team, walking through their existing spreadsheets and manual processes. This was the most valuable week of the project. I discovered that the leave approval workflow had four distinct paths depending on employee type, that attendance was tracked against shift patterns that could change monthly, and that the performance review cycle had different rules for probationary employees. None of this was in the initial brief.

Architecture Decisions

I chose a clean architecture approach for the .NET Core backend — domain, application, infrastructure, and API layers with explicit dependency direction. This was more structure than strictly necessary for the scope, but it made the codebase navigable for the junior engineers who would maintain it after I handed over. The React frontend was a SPA with role-based routing — different navigation and feature sets for HR admin, line managers, and employees.

Database Design

MS SQL Server with a normalised schema. I wrote stored procedures for the complex reporting queries — leave balance calculations, attendance summaries, headcount by department — because the query optimiser handles them better than ORM-generated SQL at that complexity level. The application layer uses Entity Framework Core for standard CRUD and calls stored procedures for reports.

CI/CD on Azure DevOps

The pipeline runs on every push to the main branch: build, unit tests, integration tests against a seeded test database, then deploy to Azure App Service via a deployment slot with swap. This meant the team could merge features with confidence that the deployment would not break the running system. Setting up the pipeline was a two-day investment that paid back immediately in the first deployment that would have gone wrong.