A REST API for managing a library's books, users, and book borrowings. The project is built with Django REST Framework and uses JWT authentication. It is intended to replace manual tracking of book inventory and borrowing records.
This is a learning project built as part of a Python backend course, as practice for a technical assessment. The current implementation covers the selected FLEX tasks for the Books, Users, Borrowings, and Telegram notification services. Stripe payments and scheduled overdue checks are not implemented.
- Book inventory management with create, read, update, and delete operations
- Email-based user registration and authentication
- JWT access and refresh tokens
- Custom
Authorizerequest header for JWT authentication - Borrowing creation with automatic inventory decrement
- Borrowing return with automatic inventory increment
- Borrowing date validation
- Active and returned borrowing filters
- User-specific borrowing access with staff-level access to all records
- Telegram notifications when a new borrowing is created
- Environment-based storage for Telegram credentials
- OpenAPI schema, Swagger UI, and ReDoc documentation
- Automated tests for the Books, Users, and Borrowings services
- Test coverage reporting with
coverage
- Python 3.12
- Django 5.2
- Django REST Framework
- Simple JWT
- drf-spectacular
- python-dotenv
- Telegram Bot API
- coverage
- SQLite
- Docker and Docker Compose
git clone https://github.com/Lemonch1ks/Library-Service-Project.git
cd Library-Service-ProjectLinux and macOS:
python3 -m venv venv
source venv/bin/activateWindows PowerShell:
python -m venv venv
venv\Scripts\Activate.ps1pip install -r requirements.txtCopy the environment variable template:
Linux and macOS:
cp .env.sample .envWindows PowerShell:
Copy-Item .env.sample .envSet the credentials in .env:
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_CHAT_ID=your_chat_idThe .env file is ignored by Git and must never be committed. If Telegram
credentials are not configured, borrowings can still be created, but the
notification is skipped and a warning is written to the application log.
python manage.py migratepython manage.py createsuperuserpython manage.py runserverThe API will be available at http://127.0.0.1:8000/.
Create .env from the provided template and replace its placeholder values.
Then build and start the application:
docker compose up --buildDocker Compose applies migrations automatically and starts the API at
http://127.0.0.1:8000/. The SQLite database is stored in the named
sqlite_data volume, so it is preserved when the application container is
recreated.
Stop the application with:
docker compose downThe project uses JWT authentication with email and password. Register a user,
obtain an access token, and send it in the custom Authorize header.
POST /users/
Content-Type: application/json{
"email": "reader@example.com",
"first_name": "Alex",
"last_name": "Reader",
"password": "strong-password"
}POST /users/token/
Content-Type: application/json{
"email": "reader@example.com",
"password": "strong-password"
}The response contains access and refresh tokens.
Use Authorize, not the standard Authorization header:
Authorize: Bearer <access-token>Example with curl:
curl http://127.0.0.1:8000/borrowings/ \
-H "Authorize: Bearer YOUR_ACCESS_TOKEN"POST /users/token/refresh/
Content-Type: application/json{
"refresh": "your-refresh-token"
}| Method | Endpoint | Description | Access |
|---|---|---|---|
GET |
/books/ |
List all books | Public |
POST |
/books/ |
Create a book | Staff only |
GET |
/books/{id}/ |
Retrieve a book | Public |
PUT |
/books/{id}/ |
Replace a book | Staff only |
PATCH |
/books/{id}/ |
Partially update a book | Staff only |
DELETE |
/books/{id}/ |
Delete a book | Staff only |
Book creation example:
{
"title": "Clean Code",
"author": "Robert C. Martin",
"cover": "hard",
"inventory": 5,
"daily_fee": "2.50"
}The supported cover values are hard and soft. Inventory and daily fee
cannot be negative.
| Method | Endpoint | Description | Access |
|---|---|---|---|
POST |
/users/ |
Register a user | Public |
POST |
/users/token/ |
Obtain JWT tokens | Public |
POST |
/users/token/refresh/ |
Refresh an access token | Public |
GET |
/users/me/ |
Retrieve the current profile | Authenticated |
PUT |
/users/me/ |
Replace the current profile | Authenticated |
PATCH |
/users/me/ |
Partially update the current profile | Authenticated |
| Method | Endpoint | Description | Access |
|---|---|---|---|
GET |
/borrowings/ |
List borrowings | Authenticated |
POST |
/borrowings/ |
Create a borrowing | Authenticated |
GET |
/borrowings/{id}/ |
Retrieve a borrowing | Owner or staff |
POST |
/borrowings/{id}/return/ |
Return a borrowed book | Owner or staff |
Non-staff users can access only their own borrowings. Staff users can access all borrowings.
POST /borrowings/
Authorize: Bearer <access-token>
Content-Type: application/json{
"borrow_date": "2026-08-08",
"expected_return_date": "2026-08-15",
"book": 1
}The authenticated user is attached automatically. The selected book must have
available inventory. After successful creation, its inventory is decreased by
one. If borrow_date is omitted, it defaults to the current date. A borrow date
in the past is rejected, and expected_return_date must be later than
borrow_date.
After the database transaction is committed, the service attempts to send a Telegram message containing the user email, book title, borrow date, and expected return date. A Telegram API failure does not roll back the borrowing.
List active borrowings:
GET /borrowings/?is_active=trueList returned borrowings:
GET /borrowings/?is_active=falseStaff users can filter borrowings by user ID:
GET /borrowings/?user_id=3Filters can be combined:
GET /borrowings/?user_id=3&is_active=trueis_active=true means that actual_return_date is empty. Non-staff users
remain restricted to their own records even if they provide user_id.
POST /borrowings/1/return/
Authorize: Bearer <access-token>No request body is required. The endpoint sets actual_return_date to the
current date and increases the book inventory by one. A borrowing cannot be
returned twice.
After starting the server, the documentation is available at:
- Swagger UI:
http://127.0.0.1:8000/api/schema/swagger-ui/ - ReDoc:
http://127.0.0.1:8000/api/schema/redoc/ - OpenAPI schema:
http://127.0.0.1:8000/api/schema/
For authenticated requests, remember that this project expects the custom
Authorize header.
Run the Django test suite:
python manage.py testTo measure test coverage, install coverage and run:
pip install coverage
coverage run --source=books_service,users_service,borrowing_service,permissions \
manage.py test
coverage reportLibrary-Service-Project/
- books_service/ # Book model, serializer, views, and routes
- borrowing_service/ # Borrowing logic, filters, returns, and tests
- borrowing_service/telegram.py # Telegram notification helper
- users_service/ # Custom user model, registration, and profile API
- permissions/ # Shared REST framework permissions
- library_conf/ # Django settings and root URL configuration
- Dockerfile
- docker-compose.yml
- manage.py
- requirements.txt
- README.md
The project currently implements the following selected tasks:
- Books Service CRUD
- Books Service permissions
- Users Service CRUD and JWT authentication
- Borrowing list and detail endpoints
- Borrowing creation with inventory management
- Borrowing filtering and user access restrictions
- Borrowing return functionality
- Telegram notification on borrowing creation
The full Library Service specification also includes functionality that is not part of the current implementation:
- Stripe payments and overdue fines
- Scheduled overdue borrowing checks