I built a small Node.js/Express API with GET /me that returns a JSON profile object plus a live cat fact. The endpoint always returns a consistent schema, a UTC ISO-8601 timestamp, and fetches a new cat fact per request (2s timeout, graceful fallback).
Repository:
https://github.com/yahaiii/hng13-stage0-profile-endpoint
Tech stack
- Node.js + Express
- axios (external API fetch)
- express-rate-limit + morgan (rate limiting + logging)
- dotenv (env vars)
- Jest + Supertest (tests)
Goals
The /me endpoint must:
- Respond to GET /me with HTTP 200
- Content-Type: application/json
- Return JSON exactly in this shape:
{
"status": "success",
"user": {
"email": "<your email>",
"name": "<your full name>",
"stack": "<your backend stack>"
},
"timestamp": "<current UTC time in ISO 8601 format>",
"fact": "<random cat fact from Cat Facts API>"
}
-
timestampis the current UTC time in ISO 8601 and updates each request -
factis fetched fromhttps://catfact.ninja/facton every request (no caching) - Graceful fallback when external API is down
Implementation (high level)
- Express app is exported from
index.js.server.jsstarts the listener — this makes tests import the app directly for CI-friendly tests. - The
/meroute:- Reads
EMAIL,FULL_NAME,STACKfrom.env - Timestamp:
new Date().toISOString() - Calls
https://catfact.ninja/factwithaxiosand a 2000ms timeout - On success: use
response.data.fact - On error/timeout: fallback string
"Cat fact unavailable at the moment." - Returns JSON with
status: "success",user,timestamp, andfact
- Reads
- Basic rate limiting and request logging applied for public deployments
Example handler (illustrative):
// example snippet
const axios = require('axios');
async function getMe(req, res) {
const user = {
email: process.env.EMAIL || 'your-email@example.com',
name: process.env.FULL_NAME || 'Your Full Name',
stack: process.env.STACK || 'Node.js/Express'
};
const timestamp = new Date().toISOString();
let fact = 'Cat fact unavailable at the moment.';
try {
const r = await axios.get('https://catfact.ninja/fact', { timeout: 2000 });
if (r?.data?.fact) fact = r.data.fact;
} catch (err) {
console.error('Cat fact fetch failed:', err.message);
}
return res.json({ status: 'success', user, timestamp, fact });
}
How I tested locally (PowerShell)
cd 'c:\path\to\the\project\'
npm install
copy .env.example .env
# edit .env -> set EMAIL and FULL_NAME (and STACK if desired)
npm start
# verify
curl http://localhost:3000/me
# run tests (no server start required; tests import the app)
npm test
Challenges & tradeoffs
- Returned HTTP 200 with a fallback fact when the external API fails. This keeps the consumer schema stable; alternative is a 502 when downstream fails.
- Short 2s timeout avoids hanging requests; production may need retry/backoff or circuit breaker.
- Integration tests call the real external API; for deterministic CI, mock axios responses.
What I learned
- Exporting the app for tests simplifies CI and avoids starting/stopping servers.
- Keep API response schemas stable for client reliability.
- Simple defensive code (timeouts, fallbacks) improves resilience.
Next steps
- Add axios mocks for deterministic unit tests
- Improve observability (structured logs, tracing)
- Add a short TTL cache if external API rate is a concern
- Add GitHub Actions to run tests on push
Top comments (2)
Really solid work! The structure is clean, your fallback handling is thoughtful, and I like how you kept the schema consistent for reliability. Curious, did you run into any tricky issues with axios timeouts or environment variable loading during testing?
Thanks @roshan_sharma_7deae5e0742 for pointing those out.
For axios timeouts, I wrapped the call in try/catch, set a 2s timeout, logged errors, and return a safe fallback so the JSON schema stays consistent.
And for the other issue with
envloading in tests it was tackled by ensuring dotenv runs before any module reads process.env.Fixed :D