Note: This is a coding challenge submission built to demonstrate clean architecture, security best practices, and production-ready patterns—not a generic boilerplate.
This API handles user registration and authentication. It validates password complexity, enforces unique usernames, and generally keeps the bad guys out while letting the good guys in.
- Runtime: Node.js (Primary Backend)
- Framework: Express.js (HTTP Server)
- Data Store: Redis (User Storage)
- Security: bcrypt (for hashing passwords into gibberish), helmet (for HTTP headers)
- Validation: express-validator (the bouncer)
- Logging: Pino (because
console.logis so 2015)
I chose a Layered Architecture (Routes → Controller → Service → Data) to ensure separation of concerns. This makes the codebase modular, testable, and easy to maintain.
- Controller Layer: Handles HTTP requests, validation results, and sending responses. It knows nothing about business logic.
- Service Layer: Contains the core business logic (hashing passwords, checking uniqueness). It knows nothing about HTTP.
- Data Utility: A thin wrapper around the Redis client to manage connections.
For this specific assignment, Redis was chosen as the primary data store for speed and simplicity.
- Performance: In memory lookups are lightning fast.
- Structure: We use
HMSETto store user objects and a simple key-value strategy for uniqueness checks.
- Statelessness: The API is currently stateless. A successful login confirms credentials but does not issue a session (JWT) yet. This keeps the initial implementation lightweight while being ready for JWT integration.
- Storage: We use
bcryptbecause it is computationally expensive, making brute-force attacks on the database unfeasible.
- Node.js (v18+)
- Redis Server (must be running locally or on a server)
macOS (Homebrew):
brew install redis
brew services start redisTip: brew services start redis runs it in the background so you don't need a separate terminal window cluttering your life.
Windows:
- Download from redis.io (or use WSL).
- Run
redis-serverin a separate terminal window.
git clone https://github.com/ShivaRap/lendesk-coding-challenge.git
cd auth-api
npm installCopy the example environment file:
cp .env.example .envEdit .env if your Redis is hiding somewhere other than localhost:6379.
Ensure Redis is running (seriously, check it), then:
Development Mode (with hot reload): Open a new terminal window!
npm run devProduction Mode:
npm startCreate a new user account.
- URL:
POST /users - Body:
{ "username": "jdoe123", "password": "Password123!" } - Password Rules: At least 8 characters with uppercase, lowercase, number, and special character. No compliance, no entry.
- Success Response (200 OK):
{ "success": true, "message": "User registered successfully", "data": { ... } }
Verify user credentials.
- URL:
POST /auth - Body:
{ "username": "jdoe123", "password": "Password123!" } - Success Response (200 OK):
{ "success": true, "message": "Authentication successful", "data": { "authenticated": true, ... } } - Error Response (401 Unauthorized):
{ "success": false, "error": { "message": "Invalid credentials", ... } }
Translation: The app tried to call Redis, but nobody answered.
Fix: You forgot to start Redis. Run brew services start redis or redis-server.
Translation: You used a special character like _ or -.
Fix: Stick to letters and numbers. cooluser1 is fine, cool_user is illegal.
-
Password Hashing: We use
bcrypt(salt rounds 10), so even if the DB leaks, the passwords are just noise. -
Strict Input Sanitation:
express-validatorrejects "dirty" inputs before they even reach business logic. -
Graceful Error Handling: Centralized error middleware catches crashes and returns clean
500JSON errors instead of killing the server. -
Password Complexity: Weak passwords like "password123" are rejected immediately.
-
Security Headers:
helmetkeeps the HTTP headers tight. -
No Plaintext: We never save passwords in plain text. Ever.
-
Graceful Shutdown: The app handles termination signals (SIGTERM/SIGINT) to close connections cleanly.
We follow the 12-Factor App pattern—configuration lives in .env, not hardcoded. The cp .env.example .env command just gives you a starting template.
Things we should totally add before going to actual production:
- Rate Limiting: Stop brute-force attacks on
/auth. - HTTPS: Encrypt the transport layer (SSL/TLS).
- Account Lockout: Lock account after 5 failed attempts (needs a Redis counter).
- JWT/Session: Implementing tokens for stateful management if needed.
Run the automated tests (if you want to see green checkmarks):
npm test