Book Lending API
Build a REST API in PHP for a small library. It manages books, members and loans, and answers questions like which books are overdue right now. Clients can add books, register members, lend a book (only if a copy is available), return it, and list overdue loans. All input is validated, and errors come back as clear JSON with the right HTTP status codes. You'll use Composer for dependencies and autoloading, PDO with prepared statements for PostgreSQL, SQL with joins and date calculations, and a clean structure of routing, validation and data access without a large framework.
- Estimate
- ~14h
- Steps
- 7
- Completed by
- 0
- Proposed by
- codeseed.app
Composer · PostgreSQL
Project roadmap
- 01
Set up the project
~1.5hRun composer init and set up PSR-4 autoloading for src/ under the App namespace. Add nikic/fast-route for routing, vlucas/phpdotenv for configuration, and phpunit/phpunit as a dev dependency. Create public/index.php as the single entry point, reading database settings from .env (commit a .env.example, never the real .env). Done when: php -S localhost:8000 -t public serves GET /health, which returns {"status":"ok"}.
- 02
Create the database schema
~2hWrite migrations/001_schema.sql with three tables: books (id, unique isbn, title, author, total_copies), members (id, name, unique email, created_at) and loans (id, book_id, member_id, loaned_at, due_at, and a nullable returned_at), with foreign keys and a check that total_copies is at least 1. Add a script that runs the SQL and inserts a few books and members. Done when: the schema and sample data load into PostgreSQL with one command.
- 03
Books and members endpoints
~2.5hImplement GET /books (with an ?author= filter), GET /books/{id}, POST /books, POST /members and GET /members/{id}, using only PDO prepared statements. Put validation in its own class: required fields, an ISBN of 10 or 13 digits, a valid email, positive copy counts. Return 201 for created records, 404 when not found, 409 for a duplicate ISBN or email, and 422 with field errors for invalid input. Done when: each endpoint works with curl and returns these codes.
- 04
Lend and return books
~2.5hImplement POST /loans (book_id, member_id and an optional loan period in days, default 14) and POST /loans/{id}/return. A book can only be lent if it has available copies: total_copies minus its loans that aren't returned. Lock the book row with SELECT ... FOR UPDATE and do the check and insert in one transaction, so two requests can't lend the last copy twice. Returning a loan twice gives 409. Done when: lending the last copy works once and the next attempt is refused.
- 05
Overdue and history queries
~2hAdd GET /loans/overdue, listing loans that are not returned and past due_at, with the book title, member name and days overdue, most overdue first. Add GET /members/{id}/loans with a member's loans, current ones first. Use SQL joins and calculate days overdue in SQL. Done when: with sample loans that are past due, both endpoints return the correct rows.
- 06
Consistent errors and documentation
~1.5hAdd one error handler in index.php so every error returns JSON like {"error":"..."} with the correct status code, and unexpected exceptions return 500 without exposing SQL or stack traces. Log unexpected errors to a file. Document every endpoint in the README with an example request and response. Done when: a failing query returns a clean 500 response and the error is written to the log file.
- 07
Write tests
~2hWrite PHPUnit tests for the validation class and the lending rules: lending when copies are available, refusing when none are left, returning a loan, returning it twice, and calculating days overdue. Run the database tests against a separate test database, never the development one. Done when: vendor/bin/phpunit passes.
Resources
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack