Case Study

Trigon Employee
Onboarding

A fully serverless employee onboarding system. Eight Lambda functions, DynamoDB, Cognito, S3 — all defined in a single CloudFormation template. No servers. No databases to manage. No ops overhead. Just business logic.

Trigon
Namibia
AWS + React + TypeScript
Scroll to explore
The Philosophy

What if you could deploy an entire system with one command?

Most onboarding systems are bloated enterprise software. Over-engineered, over-featured, and running on servers that someone has to manage 24/7. We took the opposite approach: what's the leanest, cleanest way to solve this?

The answer: everything is serverless. The entire system — API, database, auth, file storage, email, monitoring — is defined in a single AWS SAM template. One file. One deploy command. The infrastructure creates itself. Lambda functions run only when someone makes a request. DynamoDB scales on demand. S3 stores documents. Cognito handles authentication. There are zero servers to manage, zero databases to patch, zero midnight alerts.

The codebase is intentionally minimal. No ORM. No heavyweight framework. Just TypeScript, the AWS SDK, and focused business logic. Eight Lambda functions, each doing one thing well. Every dependency earns its place.

The Architecture

Eight Lambda functions. One template. That's the entire backend.

Every piece of the system is a separate Lambda function with a single responsibility. Auth handles authentication. Employee handles the core CRUD and workflow. FileUpload generates presigned S3 URLs. Notification sends emails via the email service. Audit serves the change history. Export generates CSV and Excel files. Positions manages reference data. Monitoring captures frontend errors and pipes them to Slack.

They share a common utilities layer — a typed DatabaseService class for DynamoDB, shared TypeScript interfaces, and an error reporter that sends critical failures to team notifications. But each function is independently deployable, independently scalable, and independently debuggable.

Auth 1,095 LOC

Cognito integration, JWT tokens, user creation, password resets, role management

Employee 2,393 LOC

Core CRUD, status workflow, field flagging, review progress, version tracking

FileUpload 279 LOC

S3 presigned URLs for secure uploads and downloads — Lambda never touches the file

Notification 723 LOC

Six email templates — submission confirmations, rework requests, welcome emails

Audit 118 LOC

Read-only audit trail — every field change, every status transition, every action logged

Export 287 LOC

Dynamic CSV and Excel generation with selectable fields and role-based access

Positions 415 LOC

Job position reference data — titles, departments, availability status

Monitoring 111 LOC

Frontend error reporting piped to team notifications with environment-aware webhooks

template.yaml — 1,029 lines of infrastructure
$ sam deploy --config-env prod
Creating CloudFormation stack...
Provisioning DynamoDB tables (4 tables, on-demand)...
Creating Cognito User Pool (5 role groups)...
Deploying 8 Lambda functions (Node.js)...
Configuring API Gateway with Cognito authorizer...
Creating S3 buckets (frontend + uploads)...
Setting up CloudFront distribution...
✓ Stack deployed: trigon-onboarding-prod
✓ API: https://api.trigon.com.na
✓ Frontend: https://onboarding.trigon.com.na
Total infrastructure cost at idle: $0.00
AWS Services

Every service is managed. Every service scales to zero.

The beauty of this architecture is that nothing runs when nobody's using it. Lambda functions sleep. DynamoDB is on-demand billing. S3 charges per byte. CloudFront caches at the edge. The system costs virtually nothing at idle and scales automatically under load — no capacity planning, no provisioning, no guessing.

Compute

Lambda

Eight functions running Node.js. Each function handles one domain — auth, employees, files, notifications, audit, export, positions, monitoring. Pay-per-invocation.

Database

DynamoDB

Four tables — employee records, audit logs, field flags, and positions. On-demand capacity means zero provisioning. Global secondary indexes on status, department, and email for fast queries. Point-in-time recovery enabled.

Auth

Cognito

User pool with five role groups — admin, HR clerk, manager, candidate, and viewer. Custom attributes for role and login method. JWT tokens with automatic refresh. No user database to manage — Cognito IS the user database.

Storage + CDN

S3 + CloudFront

Two buckets: one for the React frontend (served through CloudFront CDN), one for document uploads. All file access uses presigned URLs — the Lambda generates a temporary, signed permission slip and the browser uploads directly. No file ever passes through the API.

API Layer

API Gateway

REST API with Cognito authorizer — every protected endpoint validates the JWT automatically. CORS configured per environment. Routes map directly to Lambda functions. No middleware layer, no reverse proxy, no Nginx config.

Monitoring

CloudWatch

Every Lambda execution is logged automatically. Combined with the error reporter, critical failures surface in real-time with full request context — path, user, status code, and stack trace. Environment-aware notifications separate dev noise from prod alerts.

The Workflow

From application to employee — 13 status states.

The onboarding workflow isn't a simple "apply and wait." It's a 13-state pipeline with clear transitions, role-based permissions, and full audit trails. A candidate submits their application. HR reviews it, flags specific fields that need fixing, sends it back for rework. The candidate resubmits (versioned, so nothing is lost). HR verifies. The candidate moves to the hiring pool. A manager hires them. They become an employee.

Every transition is logged with who, what, and when. Five roles — admin, HR clerk, manager, candidate, and viewer — each see exactly what they need and nothing more.

onboarding-workflow
# Candidate submits application
draft → new_submission → candidate
# HR reviews and flags issues
candidate → under_review
> Field flag: residentialAddress.streetAddress — "Missing"
> Field flag: bankingDetails.accountNumber — "Invalid format"
under_review → requested_rework
# Candidate fixes and resubmits (v2)
requested_rework → candidate (version: 2)
# HR approves → Manager hires
candidate → profile_verifiedavailable_for_hire
available_for_hire → hiredcurrent_employee

Field-level flagging

When HR reviews a submission, they don't just reject it with a vague "please fix." They flag specific fields with specific messages — "Street address is missing", "Account number is invalid." Each flag has a severity level (error, warning, info) and is stored in a separate DynamoDB table with TTL for automatic cleanup. When the candidate resubmits, they see exactly what needs fixing.

Anonymous draft submissions

Candidates don't need an account to start their application. They fill out the form, save it as a draft, and get a link emailed to them. They can come back anytime, finish it, and submit. The anonymous API instance handles draft operations without authentication — no friction, no barriers to starting the process.

The Codebase

Intentionally minimal.

The entire backend is ~7,400 lines of TypeScript. No ORM — just raw DynamoDB SDK calls with typed marshalling. No Express, no Fastify — just Lambda handler functions with switch-case routing. No state management library on the frontend — just React Context and hooks.

Every dependency was a deliberate choice. Form management that's lightweight and performant. UI components for dropdowns and modals that are unstyled and composable. Tailwind because utility classes mean zero custom CSS. Schema validation for runtime checks because TypeScript only validates at compile time.

The result is a system that does everything it needs to — multi-role onboarding, document management, field-level validation, audit trails, data exports, email notifications — in a codebase that any developer can understand in an afternoon.

~7.4K
Backend LOC
8
Lambda Functions
13
Status States
5
User Roles
Under the Hood

The full tech stack

AWS Lambda
DynamoDB
Cognito
S3
CloudFront
API Gateway
CloudWatch
AWS SAM
CloudFormation
Node.js
AWS Lambda
DynamoDB
Cognito
S3
CloudFront
API Gateway
CloudWatch
AWS SAM
CloudFormation
Node.js
TypeScript
React
Tailwind CSS
UI Components
Form Management
Validation
Email Service
Team Notifications
Secure Uploads
Single-page App
TypeScript
React
Tailwind CSS
UI Components
Form Management
Validation
Email Service
Team Notifications
Secure Uploads
Single-page App

Want a system that's lean and serverless?

We build systems that do exactly what they need to — no more, no less. Minimal code, managed infrastructure, and architecture that scales itself.