A Node.js REST API built as a technical practice project for a Client Success Engineer role. The project simulates a backend service for managing artist stream data, and was built progressively across 10 sections. Each shipped as its own pull request to practice real-world engineering workflows including SQLite persistence, input validation, authentication, rate limiting, search, logging, and a frontend dashboard.
Make sure the following are installed before getting started:
- Node.js (v18 or higher)
- Git
- Postman - for testing API endpoints
- A terminal running Git Bash (Windows) or any Unix-style shell
git clone https://github.com/jmetzler-data/chartmetric-practice.git
cd chartmetric-practice
npm installThis installs all dependencies: express, better-sqlite3, and express-rate-limit.
node index.jsYou should see:
Running on port 3000
The server runs at http://localhost:3000. Leave this terminal open while testing. Open a second Git Bash terminal for any additional commands.
Note: The SQLite database file (
artists.db) is created automatically on first run and seeded with 3 artists. It is excluded from version control via.gitignore.
All endpoints accept and return JSON. Test using Postman or any HTTP client.
Returns all artists sorted by stream count descending. Optionally filter by minimum streams.
GET http://localhost:3000/artists
GET http://localhost:3000/artists?minStreams=900000
Success response:
[
{ "id": 1, "name": "Kanye West", "streams": 1000000 },
{ "id": 3, "name": "Twenty One Pilots", "streams": 920000 }
]Returns a single artist by ID.
GET http://localhost:3000/artists/1
Success response:
{ "id": 1, "name": "Kanye West", "streams": 1000000 }Error responses:
400 — id must be a number (e.g. /artists/abc)
404 — Artist not found (e.g. /artists/99)
Searches artists by name. Supports partial matches.
GET http://localhost:3000/artists/search?name=kan
GET http://localhost:3000/artists/search?name=one
Success response:
[{ "id": 1, "name": "Kanye West", "streams": 1000000 }]Error responses:
400 - name query parameter is required (no ?name= provided)
404 - No artists found matching '...' (no results)
Adds a new artist. Requires a valid name and non-negative stream count.
POST http://localhost:3000/artists
Request body:
{ "name": "Arctic Monkeys", "streams": 780000 }Success response:
{ "id": 4, "name": "Arctic Monkeys", "streams": 780000 }Error responses:
400 - name is required and must be a non-empty string
400 - streams is required and must be a non-negative number
Updates an existing artist's name, streams, or both. Any field not provided keeps its current value.
PUT http://localhost:3000/artists/2
Request body (partial update is fine):
{ "streams": 999999 }Success response:
{ "id": 2, "name": "Twenty One Pilots", "streams": 999999 }Error responses:
400 - id must be a number
400 - name must be a non-empty string
400 - streams must be a non-negative number
404 - Artist not found
Removes an artist by ID.
DELETE http://localhost:3000/artists/1
Success response:
{ "message": "Artist 1 deleted" }Error responses:
400 - id must be a number
404 - Artist not found
All endpoints are rate limited to 5 requests per minute per IP. Exceeding this returns:
{ "error": "Too many requests - please wait before trying again" }HTTP status: 429 Too Many Requests
API key auth is implemented but commented out by default to allow the HTML dashboard to function without headers. To enable it, uncomment the auth middleware block in index.js and include the following header in all Postman requests:
Key: x-api-key
Value: secret123
Requests without a valid key return:
{ "error": "Unauthorized - missing or invalid API key" }HTTP status: 401 Unauthorized
A browser-based dashboard displays all artists in a styled table, fetched live from the API.
http://localhost:3000
Requires the API key middleware to remain commented out, as browsers cannot send custom headers on page load.
All significant events are written to logs.txt in the project root with timestamps and log levels (INFO, WARN, ERROR). The file is created automatically on first write.
To view logs:
cat logs.txtExample output:
[2026-06-08T14:22:01.000Z] [INFO] GET /artists returned 3 artist(s) with minStreams=0
[2026-06-08T14:22:10.000Z] [WARN] Artist not found: id=99
[2026-06-08T14:22:18.000Z] [INFO] New artist created: 'Radiohead' (id=4, streams=670000)
[2026-06-08T14:22:25.000Z] [INFO] Artist deleted: 'Kanye West' (id=1)
logs.txtandartists.dbare excluded from version control via.gitignore.
A standalone script that compares two datasets and flags mismatches — simulating a real-world investigation when a client reports incorrect data.
node discrepancy.jsExample output:
===== Data Discrepancy Report =====
[MISMATCH] ID 2 'Twenty One Pilots'
Internal: 920,000
External: 875,000
Diff: -45,000
[MISSING] ID 4 'Arctic Monkeys' exists externally but not internally
===================================
chartmetric-practice/
├── index.js # Main Express server - all routes, middleware, and database logic
├── discrepancy.js # Standalone data comparison script
├── dashboard.html # Browser-based artist stream dashboard
├── package.json # Project config and dependencies
├── .gitignore # Excludes artists.db and logs.txt
└── README.md
| Package | Purpose |
|---|---|
| express | HTTP server and routing |
| better-sqlite3 | SQLite database (synchronous Node.js driver) |
| express-rate-limit | Per-IP request rate limiting |
- REST API design with full CRUD operations
- SQLite database integration with seeding and persistence
- Input validation and descriptive error responses
- API key authentication middleware
- Rate limiting
- Name-based search with partial matching
- Timestamped file-based logging across all routes
- Frontend-to-backend integration via fetch
- Data discrepancy detection scripting
- Git feature branch workflow with pull requests throughout