Skip to content
Amygdal
Case study5 min read

Digital Arrow: Building an HR and Recruiting Platform

One workspace for the hiring lifecycle: CVs, async video interviews, timesheets, contracts and invoices. Here is how the platform is put together.

Digital Arrow brings recruiting and HR into one workspace. A company posts a role, builds candidate CVs, runs asynchronous video interviews, and once someone is hired, handles their timesheets, time-off, contracts and client invoices in the same place. Most teams stitch this together from four or five tools; Digital Arrow is the attempt to make it one.

We architected and built the platform. This post covers how it is structured and the engineering decisions behind the parts that are harder than they look: search, video, documents and signatures.

The shape of the platform

The backend is Node.js with NestJS, backed by PostgreSQL through Prisma. The frontend is Next.js and React with Tailwind and shadcn/ui. Around that core sit four supporting services, each chosen for one job:

  • Elasticsearch for full-text candidate and CV search.
  • MinIO as S3-compatible object storage for CVs, video answers and generated documents.
  • Browserless for headless-browser PDF generation — CVs, contracts and invoices.
  • Redis for caching, queues and short-lived state.

NestJS suits this kind of product because the domain is wide. Jobs, applicants, interviews, timesheets, contracts and invoices are separate modules with their own services and controllers, but they share auth, permissions and a single database. NestJS's module system keeps those boundaries visible without forcing a microservice split the team does not need yet. We wrote more about that pattern in NestJS, Prisma and PostgreSQL architecture.

The other early decision was multi-tenancy. Several companies use the same platform, and recruiters, hiring managers, candidates and contractors all see different slices of the same records. Every query is scoped to a company, and role checks live in guards that run before any controller code. Getting that into the foundation in week one is far cheaper than retrofitting it after the first customer asks why they can see someone else's applicant.

Candidate data is relational: a candidate has applications, applications belong to jobs, jobs belong to companies, and permissions follow that chain. That belongs in PostgreSQL. Searching that data the way a recruiter wants to — free text across skills, past roles and CV content, with typos and partial matches — is a different problem, and Postgres full-text search runs out of road quickly.

So Postgres stays the source of truth and Elasticsearch holds a denormalised search document per candidate. Writes go to Postgres first; an indexing step updates Elasticsearch after. The rule we hold to is simple: if the two ever disagree, Postgres wins, and the index can be rebuilt from it at any time. Search results return IDs, and the detail view always reads from Postgres, so permissions are enforced in one place.

CVs arrive in every format imaginable, so text extraction belongs in a background job rather than inside the upload request. The recruiter sees the upload succeed immediately, and the candidate becomes searchable a moment later. Queueing that work through Redis also gives retries for free when a file is slow or malformed.

Async video interviews

Asynchronous interviews let a recruiter send a set of questions and let candidates record answers when it suits them. It removes scheduling from the first round, which matters when candidates and hiring managers are in different time zones.

The engineering work is mostly about the media path. Candidates record in the browser, uploads go directly to object storage rather than through the API server, and the platform keeps metadata — question, attempt, duration, status — in Postgres. Reviewers watch answers in sequence and leave structured feedback. The platform also supports video requests, where a recruiter asks for a short follow-up clip outside a formal interview.

Video files should never be public. Playback goes through short-lived signed URLs issued by the API after a permission check, so a link copied out of the browser stops working within minutes. Candidates can see what they submitted, reviewers can see what they are assigned, and nobody else can see anything. For a product that stores people's faces and voices, that is the minimum.

On top of that sits AI-assisted scoring of interview answers. We treat it as an aid to the reviewer, not a decision-maker: it summarises and highlights, and a person decides. In hiring, that is both the responsible position and, in several of the markets we work in, increasingly a regulatory expectation.

Documents, PDFs and online signatures

HR runs on documents: formatted CVs to send to clients, employment and contractor agreements, invoices. We render these as HTML templates and turn them into PDFs with Browserless, a hosted headless Chrome. Using a real browser engine means the same templates and CSS the team already knows produce the PDF, rather than a separate PDF layout language that only one person understands.

Contracts add online signatures. The signing flow records who signed, when, and against which exact version of the document, and the signed PDF is stored immutably in object storage. Once signed, a document is never regenerated from its template — the template might have changed since.

PDF rendering belongs in a queue too. A headless browser is a heavy dependency, and generating a batch of CVs or month-end invoices synchronously would tie up API workers. The better shape is for the API to record a job, a worker to render the document through Browserless and store it, and the user to be notified when it is ready.

Timesheets, time-off and invoicing

After hiring, the platform tracks time. Timesheet entries roll up into invoices for clients, and time-off requests flow through approval. The lesson here is the one every billing system teaches: store the inputs, not just the totals. An invoice keeps a reference to the exact timesheet entries it was built from, so a disputed line can be traced back to the hours behind it.

The same thinking applies to time-off. Balances are best calculated from the history of requests and approvals rather than stored as a single number someone can edit, so the answer to "why do I have four days left?" is always a list, not a guess.

What carried the project

  1. One relational source of truth, with search as a rebuildable projection.
  2. Media uploaded straight to object storage, never streamed through the API.
  3. HTML-to-PDF with a real browser, so documents use the same skills as the rest of the frontend.
  4. Signed documents stored as immutable files, separate from their templates.
  5. AI that assists reviewers rather than replacing their judgement.

Planning an HR or workflow platform?

Platforms like Digital Arrow are mostly custom software development: a wide domain, several data stores and a lot of document handling. See the Digital Arrow project page for more, or talk to us about yours.

NestJSPostgreSQLElasticsearchNext.jsHR tech

Frequently asked questions

What tech stack does Digital Arrow use?

NestJS on Node.js with PostgreSQL and Prisma, Next.js and React on the frontend, Elasticsearch for search, MinIO for file storage, Browserless for PDF generation and Redis for caching and queues.

Why use Elasticsearch alongside PostgreSQL?

PostgreSQL holds the relational data and permissions. Elasticsearch handles fuzzy, full-text candidate search. The index is a projection that can be rebuilt from Postgres at any time.

Does the AI decide which candidates move forward?

No. AI scoring summarises and highlights interview answers to help reviewers, but a person makes the hiring decision.

Project

About Digital Arrow

Keep reading

Have a project in mind?

Tell us what you are building. You will hear back from an engineer — not a sales team — within one business day.

Email us