A basic (framework-less) web application that downloads candidates' data with their job applications from the Teamtailor API in a CSV file format.
- connect to the Teamtailor API and fetch all candidates and their job applications
- convert the response into a CSV with columns:
candidate_id,first_name,last_name,email,job_application_id, andjob_application_created_at - let the user download the file by clicking a button
- HTML page with button that triggers CSV file download
- API pagination implemented as a consecutive user-triggered process. Each button click prepares CSV with next batch of data until all records are saved. Page reload resets the pagination.
pnpm install # install dependencies
pnpm build:dev # compile ts files into js
pnpm dev # run development serverOpen http://localhost:3000 and click Download CSV button.
All components and diagram mindmap are layed out separately in ARCHITECTURE.md.
Uses both Node.js and Ruby for the sake of test assignment. Each technology handles what it does best:
Node.js: HTTP server, webpage, static filesRuby: Teamtailor API integration, data transformation, CSV generation
Rather a debatable solution for production code. It was more of an experiment to try something new outside conventional frameworks approach.
Uses stdin/stdout/stderr for data exchange between Node.js and Ruby:
Benefits:
- Universal interface: Works across any language/platform
- No file I/O: Avoids temporary file management
- Decoupled: Ruby script has no dependency on Node.js internals
// Node writes JSON to Ruby's stdin
csvProcess.stdin.write(candidatesJson);
csvProcess.stdin.end();
// Node reads CSV from Ruby's stdout
csvProcess.stdout.on("data", (data) => (csvOutput += data.toString()));Benefits:
- UI and API on same domain (localhost:3000)
- No CORS configuration needed
- Simpler deployment
app.use(express.static(path.join(__dirname, <path>)));Single API request fetches candidates AND their applications, with required fields only.
Benefits:
includereduces API calls from N+1 to 1 requestfieldsfilters away noise (only required data pulled)pageavoids long polling API calls
Ruby (integration layer):
- Validates API responses and data structure
- Exits with code 1 on errors, logs details to stderr
- Includes error context (backtrace, input preview)
Node.js (orchestration layer):
- Catches Ruby process errors via exit codes
- Returns HTTP 500 with error details for debugging
- Logs all errors (stderr + exceptions) to console
Frontend (UI layer):
- Displays user-friendly error messages
- Shows API error details when available
Benefits:
- encrypts sensitive API keys
- commits safely encrypted
.envto git - is decrypted at runtime
- No build step for CSS (no pre-processors)
- Single-page, single-purpose interface
- Simple, no framework overhead
Implemented tools:
- Prettier
- Rubocop
- TSDoc
concurrently(helps maintaining live-reload in development mode)
Generates and downloads a CSV file with candidate data.
Response:
- Content-Type:
text/csv - Filename:
candidates-<yyyy-mm-dd>.csv
CSV Columns:
candidate_id- Candidate IDfirst_name- Candidate's first namelast_name- Candidate's last nameemail- Candidate's email addressjob_application_id- Application ID (nullif no applications)job_application_created_at- Application creation timestamp (nullif no applications)
Health check endpoint.
Backend:
- Node.js v20+ with Express 5
- Ruby v4.0
Frontend:
- Vanilla
JavaScriptTypeScript (no frameworks) - Native CSS (no CSS pre-processors)
API Client:
HTTParty(Ruby)JSON:APIprotocol
Security:
dotenvxfor secrets management
# Ruby tests
pnpm test:ruby
# Node tests
pnpm test:nodepnpm dev- Start development server with auto-reloadpnpm test- Run all tests (forrubyandnode)pnpm build- Build TypeScriptpnpm start- Run production build
