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.
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.
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.
Cognito integration, JWT tokens, user creation, password resets, role management
Core CRUD, status workflow, field flagging, review progress, version tracking
S3 presigned URLs for secure uploads and downloads — Lambda never touches the file
Six email templates — submission confirmations, rework requests, welcome emails
Read-only audit trail — every field change, every status transition, every action logged
Dynamic CSV and Excel generation with selectable fields and role-based access
Job position reference data — titles, departments, availability status
Frontend error reporting piped to team notifications with environment-aware webhooks
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.
Eight functions running Node.js. Each function handles one domain — auth, employees, files, notifications, audit, export, positions, monitoring. Pay-per-invocation.
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.
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.
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.
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.
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 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.
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.
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 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.
We build systems that do exactly what they need to — no more, no less. Minimal code, managed infrastructure, and architecture that scales itself.