A REST API portfolio project for managing books and authors. It demonstrates resource-oriented HTTP endpoints, JSON request validation, SQLite relationships, consistent error responses, OpenAPI documentation and request tracing.
The API runs locally and supports the documented book lifecycle. It is not currently deployed to a public environment, so all examples below use http://localhost:3000.
- Create, list, retrieve, replace, update and delete books
- List the authors seeded into the local database
- Enforce author relationships and unique ISBN values with SQLite
- Validate JSON bodies and reject unknown fields
- Return consistent error codes, messages and request IDs
- Publish interactive OpenAPI documentation through Swagger UI
- Log request IDs, response status codes and request duration
- Include a Postman collection for exploring the local endpoints
- Node.js 22
- Express 5
- SQLite
- OpenAPI 3.0 and Swagger UI
- Postman and Newman
| Method | Endpoint | Purpose | Success status |
|---|---|---|---|
GET |
/health |
Check service health | 200 |
GET |
/authors |
List authors | 200 |
GET |
/books |
List books | 200 |
POST |
/books |
Create a book | 201 |
GET |
/books/{id} |
Retrieve a book | 200 |
PUT |
/books/{id} |
Replace a book's editable fields | 200 |
PATCH |
/books/{id} |
Update selected book fields | 200 |
DELETE |
/books/{id} |
Delete a book | 204 |
Interactive documentation is available at /api-docs after the server starts. The source specification is in docs/openapi.yaml.
Requirements:
- Node.js 22 or later
- npm
git clone https://github.com/macrovise/bookstore-api.git
cd bookstore-api
npm ci
cp .env.example .env
npm startThe API starts on http://localhost:3000 by default. The SQLite database is created in data/bookstore.db on first start and is excluded from Git.
The only supported environment variable is:
| Variable | Default | Purpose |
|---|---|---|
PORT |
3000 |
HTTP port used by the local server |
The committed .env.example contains no credentials.
Create a book using an existing author_id from GET /authors:
POST /books
Content-Type: application/json{
"title": "1984",
"isbn": "9780451524935",
"price": 9.99,
"published_year": 1949,
"author_id": 1
}Example 201 Created response:
{
"data": {
"id": 1,
"title": "1984",
"isbn": "9780451524935",
"price": 9.99,
"published_year": 1949,
"author_id": 1,
"author": {
"id": 1,
"name": "George Orwell"
},
"created_at": "2026-07-26 20:00:00",
"updated_at": "2026-07-26 20:00:00"
}
}Invalid input returns a structured response with an application error code and the request ID used in logs:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request contains invalid fields.",
"request_id": "04675389-9f9b-4e67-9c05-8a43139a7315",
"details": {
"price": "Price must be 0 or greater."
}
}
}Expected client error statuses include:
400for invalid JSON, IDs, fields or relationships404for an unknown route or book409for a duplicate ISBN
The local collection and environment are stored in:
Start the API in one terminal, then run the current collection in another:
npm run test:postmanThe current collection is a work in progress. It contains CRUD requests, but it does not yet include assertion scripts or reliable response-variable hand-off between requests. A Newman run can therefore complete even when individual requests return unexpected statuses. It is useful for manual exploration, but it must not be described as automated test coverage.
bookstore-api/
|-- data/ # Local SQLite database location
|-- docs/openapi.yaml # OpenAPI specification
|-- postman/ # Collection and local environment
|-- src/
| |-- database/ # Connection and schema initialisation
| |-- middleware/ # Validation, logging and error handling
| |-- routes/ # Book and author endpoints
| |-- utils/ # Application error type
| |-- app.js # Express application
| `-- server.js # Service entry point
|-- .env.example
|-- package.json
`-- README.md
PUTrequires the complete editable book representation;PATCHaccepts only changed fields.- Unknown request fields are rejected instead of silently ignored.
- ISBN values are unique.
- Author IDs must reference an existing author.
- Book IDs must be positive integers.
- Successful collection responses include both
dataandcount.
- Complete response-variable hand-off and add assertion scripts to the Postman collection
- Add isolated unit and integration tests
- Add pagination and filtering to
GET /books - Add authentication and role-based authorisation
- Deploy to a verified public environment