A small Express and PostgreSQL REST API for managing books, genres, and authors.
- Node.js
- Express
- PostgreSQL
zodpgdotenv- EJS
Install dependencies:
npm installCreate a .env file with your local PostgreSQL connection details:
DB_HOST=localhost
DB_USER=your_postgres_user
DB_NAME=your_database_name
DB_PORT=5432If your local Postgres user requires a password, add:
DB_PASSWORD=your_passwordYou can also use a connection string instead:
DATABASE_URL=postgres://your_postgres_user@localhost:5432/your_database_nameStart the dev server:
npm run devBy default, the app runs at:
http://localhost:3000
All request bodies should be sent as JSON with this header:
Content-Type: application/json
Empty collection responses use []. Missing single resources return {} with a 404 status code.
Request bodies and route IDs are validated with Zod before they reach the controllers. Route IDs must be positive integers. Required string fields are trimmed and must not be empty.
Validation failures return 400 with details:
{
"status": "error",
"message": "Validation Failed",
"errors": [
{
"field": "genreName",
"message": "Genre name is required"
}
]
}Unknown routes return 404:
{
"message": "Route doesn't exist. See API Documentation"
}GET /booksReturns an array of books:
[
{
"id": 1,
"title": "Dune",
"genreId": 1,
"genreName": "Science Fiction"
}
]GET /books/:idExample:
GET /books/1Returns:
{
"id": 1,
"title": "Dune",
"genreId": 1,
"genreName": "Science Fiction"
}POST /booksRequired fields:
titlegenreName
Optional fields:
authorNamemust not be empty if included
Body:
{
"title": "Dune",
"genreName": "Science Fiction",
"authorName": "Frank Herbert"
}If the genre or author does not exist yet, the app creates it before linking it to the book.
Success response:
{
"id": 1,
"title": "Dune",
"genreId": 1,
"genreName": "Science Fiction"
}PUT /books/:idRequired fields:
titlegenreName
Body:
{
"title": "Dune Messiah",
"genreName": "Science Fiction"
}If the genre does not exist yet, the app creates it before updating the book.
Success response:
{
"id": 1,
"title": "Dune Messiah",
"genreId": 1,
"genreName": "Science Fiction"
}DELETE /books/:idExample:
DELETE /books/1Success response:
{
"id": 1,
"title": "Dune",
"genreId": 1,
"genreName": "Science Fiction"
}GET /genresReturns an array of genre objects:
[
{
"id": 1,
"genreName": "Science Fiction"
},
{
"id": 2,
"genreName": "Fantasy"
}
]GET /genres/:idExample:
GET /genres/1Returns:
{
"id": 1,
"genreName": "Science Fiction"
}POST /genresRequired fields:
genreName
Body:
{
"genreName": "Science Fiction"
}Success response:
{
"id": 1,
"genreName": "Science Fiction"
}PUT /genres/:idRequired fields:
genreName
Body:
{
"genreName": "Speculative Fiction"
}Success response:
{
"id": 1,
"genreName": "Speculative Fiction"
}DELETE /genres/:idExample:
DELETE /genres/1Success response:
{
"id": 1,
"genreName": "Science Fiction"
}GET /authorsReturns an array of author objects:
[
{
"id": 1,
"authorName": "Frank Herbert"
},
{
"id": 2,
"authorName": "Ursula K. Le Guin"
}
]GET /authors/:idExample:
GET /authors/1Returns:
{
"id": 1,
"authorName": "Frank Herbert"
}POST /authorsRequired fields:
authorName
Body:
{
"authorName": "Frank Herbert"
}Success response:
{
"id": 1,
"authorName": "Frank Herbert"
}PUT /authors/:idRequired fields:
authorName
Body:
{
"authorName": "Franklin Herbert"
}Success response:
{
"id": 1,
"authorName": "Franklin Herbert"
}DELETE /authors/:idExample:
DELETE /authors/1Success response:
{
"id": 1,
"authorName": "Frank Herbert"
}Create a genre:
Method: POST
URL: http://localhost:3000/genres
Headers:
Content-Type: application/json
Body:
{
"genreName": "Science Fiction"
}Create an author:
Method: POST
URL: http://localhost:3000/authors
Headers:
Content-Type: application/json
Body:
{
"authorName": "Frank Herbert"
}Create a book with a genre and author:
Method: POST
URL: http://localhost:3000/books
Headers:
Content-Type: application/json
Body:
{
"title": "Dune",
"genreName": "Science Fiction",
"authorName": "Frank Herbert"
}Update a book:
Method: PUT
URL: http://localhost:3000/books/1
Headers:
Content-Type: application/json
Body:
{
"title": "Dune Messiah",
"genreName": "Science Fiction"
}Delete a book:
Method: DELETE
URL: http://localhost:3000/books/1
Body: none