THIS REPO IS COMPLETELY MADE AND MAINTAINED BY CLAUDE CODE, AS AN ATTEMPT TO MAXIMIZE IT'S ABILITY TO DEVELOP.
A production-style demo REST API for a library catalog.
This project is intentionally designed to showcase backend engineering practices, not just CRUD logic. It demonstrates:
- Layered architecture (Route -> Controller -> Service -> Repository)
- Repository Pattern for data-access abstraction
- Immutable data updates in the in-memory store
- Input and environment validation with Zod
- Security middleware (Helmet, rate limiting, JSON body limits)
- Centralized error handling and consistent API response envelopes
- Multi-level testing (unit, integration, e2e) with coverage thresholds
- What This Project Is
- What This Project Is Not
- Tech Stack
- Architecture
- Project Structure
- Getting Started
- Environment Variables
- Run the Application
- Scripts
- API Reference
- Validation Rules
- Response Format
- Error Handling
- Testing
- Current Known Caveats
- Security Features
- Design Decisions
- Roadmap Ideas
- License
This is a backend API for managing book records in a library catalog.
You can:
- Create books
- List books (with pagination)
- Get one book by id
- Replace a book
- Partially update a book
- Delete a book
- Check health status
This is not an e-reader or digital book delivery service.
- It does not store PDF/EPUB files
- It does not render pages for reading
- It does not include user accounts/authentication yet
- It currently uses in-memory storage (data resets on restart)
- Runtime: Node.js
- Language: TypeScript (strict mode)
- Web framework: Express 5
- Validation: Zod
- Security middleware: Helmet, express-rate-limit
- Logging: Winston
- Tests: Jest + Supertest + ts-jest
- Formatting and linting: Prettier + ESLint
Request flow:
- Route receives HTTP request
- Controller validates input and maps request/response
- Service applies business rules
- Repository handles persistence operations
- Response helpers return a consistent envelope
Dependency direction:
- Routes depend on Controllers
- Controllers depend on Services
- Services depend on Repository interface
- Repository implementation can be swapped (in-memory today, DB tomorrow)
src/
app.ts # Express app composition (middleware + routes)
server.ts # Process entrypoint and graceful shutdown
config/
env.ts # Env schema + parser (Zod)
controllers/
book.controller.ts # HTTP handlers for books
lib/
logger.ts # Winston logger factory
response.ts # Standard API response builders
uuid.ts # UUID helper (if needed by future layers)
middleware/
request-logger.ts # Request logging middleware
not-found.ts # 404 handler
error-handler.ts # Centralized error handling
repositories/
book.repository.ts # Repository contract
in-memory-book.repository.ts
routes/
health.route.ts
book.route.ts
schemas/
book.schema.ts # Request validation schemas
services/
book.service.ts # Business logic + NotFound domain error
types/
book.ts
api-response.ts
__tests__/
unit/
integration/
e2e/
- Node.js 20+ recommended
- npm 10+ recommended
npm installCopy .env.example to .env and adjust values if needed:
cp .env.example .env.env.example contains:
PORT=3000
NODE_ENV=development
RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX=100
LOG_LEVEL=infoVariable details:
PORT: server port (required)NODE_ENV:development|test|productionRATE_LIMIT_WINDOW_MS: rate limit window in millisecondsRATE_LIMIT_MAX: max requests per IP in a windowLOG_LEVEL: Winston level (error,warn,info,debug)
npm run devIf your shell does not load .env automatically, run with explicit variables:
PORT=3000 NODE_ENV=development npm run devnpm run build
npm startcurl -sS http://localhost:3000/healthExpected response:
{
"success": true,
"data": {
"status": "ok"
},
"error": null
}npm run dev: run server with ts-nodenpm run build: compile TypeScript intodist/npm start: run built server fromdist/server.jsnpm test: run Jest with coveragenpm run test:watch: run Jest in watch modenpm run lint: lint TypeScript source filesnpm run format: format TypeScript source files
Base URL:
http://localhost:3000
Books base path:
/api/v1/books
- Method:
GET - Path:
/health - Success:
200
- Method:
GET - Path:
/api/v1/books - Query params:
page(optional, integer >= 1)limit(optional, integer >= 1)
Example:
curl -sS "http://localhost:3000/api/v1/books?page=1&limit=10"Success response (200):
{
"success": true,
"data": [
{
"id": "2f2dc2af-4447-44df-a7a5-8e303de8fb74",
"title": "Dune",
"author": "Frank Herbert",
"isbn": "9780441013593",
"publishedYear": 1965,
"genre": "Science Fiction",
"createdAt": "2026-03-17T10:10:10.100Z",
"updatedAt": "2026-03-17T10:10:10.100Z"
}
],
"error": null,
"meta": {
"total": 1,
"page": 1,
"limit": 10
}
}- Method:
GET - Path:
/api/v1/books/:id
Example:
curl -sS http://localhost:3000/api/v1/books/<BOOK_ID>Success response (200):
{
"success": true,
"data": {
"id": "2f2dc2af-4447-44df-a7a5-8e303de8fb74",
"title": "Dune",
"author": "Frank Herbert",
"isbn": "9780441013593",
"publishedYear": 1965,
"genre": "Science Fiction",
"createdAt": "2026-03-17T10:10:10.100Z",
"updatedAt": "2026-03-17T10:10:10.100Z"
},
"error": null
}- Method:
POST - Path:
/api/v1/books - Body: JSON
Request body:
{
"title": "Dune",
"author": "Frank Herbert",
"isbn": "9780441013593",
"publishedYear": 1965,
"genre": "Science Fiction"
}Example:
curl -sS -X POST http://localhost:3000/api/v1/books \
-H "Content-Type: application/json" \
-d '{
"title": "Dune",
"author": "Frank Herbert",
"isbn": "9780441013593",
"publishedYear": 1965,
"genre": "Science Fiction"
}'Success response (201): same envelope as Get Book.
- Method:
PUT - Path:
/api/v1/books/:id - Body: full book payload required
Example:
curl -sS -X PUT http://localhost:3000/api/v1/books/<BOOK_ID> \
-H "Content-Type: application/json" \
-d '{
"title": "Dune Messiah",
"author": "Frank Herbert",
"isbn": "9780441172696",
"publishedYear": 1969,
"genre": "Science Fiction"
}'Success: 200
- Method:
PATCH - Path:
/api/v1/books/:id - Body: one or more optional fields
Example:
curl -sS -X PATCH http://localhost:3000/api/v1/books/<BOOK_ID> \
-H "Content-Type: application/json" \
-d '{
"title": "Dune (Updated)"
}'Success: 200
- Method:
DELETE - Path:
/api/v1/books/:id
Example:
curl -i -X DELETE http://localhost:3000/api/v1/books/<BOOK_ID>Success: 204 No Content
Book payload rules:
title: string, min 1, max 200 charsauthor: string, min 1, max 100 charsisbn: string, exactly 13 digits (^\d{13}$)publishedYear: integer between 1000 and current yeargenre: one of:- Fiction
- Non-Fiction
- Science Fiction
- Fantasy
- Mystery
- Thriller
- Romance
- Horror
- Biography
- History
Path rules:
id: must be a valid UUID
Query rules:
page: integer >= 1limit: integer >= 1
Invalid input returns 422 with a message in the standard error envelope.
The API uses a consistent envelope:
{
"success": true,
"data": {},
"error": null,
"meta": {
"total": 1,
"page": 1,
"limit": 10
}
}{
"success": false,
"data": null,
"error": "Human readable message"
}Status codes used:
200: successful read/update201: resource created204: resource deleted, no body404: route not found or book not found422: validation errors500: unexpected server error
Behavior:
- Domain not-found errors are converted into
404responses - Unknown errors are logged and returned as
500 Internal Server Error - Unmatched routes return
404 Not Found
Use env vars so app initialization passes in tests:
PORT=3000 NODE_ENV=test npm testCoverage threshold target (configured in jest.config.ts):
- Lines: 80%
- Branches: 80%
- Functions: 80%
- Statements: 80%
Current observed result on this repository state:
- 9/9 suites passed
- 173 tests passed
- Global coverage above threshold
PORT=3000 NODE_ENV=test npm run test:watch- Build currently fails in strict TypeScript mode
At the moment, npm run build reports type errors related to exactOptionalPropertyTypes interactions in:
src/controllers/book.controller.tssrc/repositories/in-memory-book.repository.ts
- Workaround for running the app during development
If you hit those type-check issues in local dev, this command runs via ts-node transpile-only mode:
PORT=3000 NODE_ENV=development TS_NODE_TRANSPILE_ONLY=1 npm run dev- In-memory persistence only
- Data is lost whenever the process restarts
- This is suitable for demos/tests, not production persistence
Implemented safeguards:
- Helmet security headers
- Rate limiting with configurable window/max values
- JSON payload limit (
10kb) - Input validation at request boundaries
- Structured error responses
- Repository interface separates business logic from storage implementation
- Service layer throws domain-specific errors (
NotFoundError) - Middleware pipeline keeps cross-cutting concerns isolated
- Response helper utilities ensure API consistency
- Immutable update patterns reduce accidental side effects
Possible improvements to evolve this into a production-ready service:
- Replace in-memory repository with PostgreSQL (or another persistent DB)
- Add authentication/authorization (JWT or session-based)
- Add OpenAPI/Swagger docs generation
- Add request id and structured tracing for observability
- Add pagination links and sorting/filtering support
- Add CI workflow for lint/test/build gates
- Add containerization (
Dockerfile+ compose profile) - Fix strict TypeScript build issues and enforce build in CI