- Table of Contents
- Overview
- Technologies Used
- Project Structure
- Installation & Configuration
- Database Setup
- Usage
- API Documentation
- Contributing
- License
LibStats is a full-stack web app designed to track and report reference interaction, types, locations, and formats in a public library setting. The app features a React client and an Express.js backend API that interacts with a SQLite3 database to store and retrieve interaction data.
This app manages reference interactions in a library setting between different types, in different formats, and at different locations.
Key functionality includes:
- CRUD operations to manage reference interactions
- Linking interactions to pre-defined types, locations, and formats
- Reporting functions to retrieve interaction data from database
- Data visualization elements
- Docker
- Docker Compose
- Caddy
- React: Frontend JavaScript library
- Vite: Frontend tooling and build tool
- React Router: Client-side routing for React
- React Hot Toast: Notifications library for React
- Recharts: Data visualization library for React
- Express: Web framework for building the backend API
- Knex: SQL query builder and migrations tool
- better-sqlite3: SQLite database driver
- jsonwebtoken: Authorization using JWT
- SQLite3: A lightweight SQL database to store interactions, types, locations, and formats
/ ── project root
├── api
│ ├── data
│ ├── Dockerfile
│ ├── knexfile.js
│ ├── node_modules
│ ├── package.json
│ ├── package-lock.json
│ └── src
├── Caddyfile
├── client
│ ├── Dockerfile
│ ├── eslint.config.js
│ ├── index.html
│ ├── package.json
│ ├── package-lock.json
│ ├── public
│ ├── README.md
│ ├── src
│ └── vite.config.js
├── docker-compose.yaml
└── README.md
1. Clone the repository
git clone https://github.com/jamesspearsv/libstats-node.git
cd libstats-node
2. Install dependencies
For the frontend
cd client
npm install
For the backend
cd api
npm install
3. Set up environment variables: Create an .env
file in the project root with the following contents:
NODE_ENV=production
NODE_PORT=[...]
ORIGINS=[...]
VITE_API_HOST=http://[...]:${NODE_PORT}
TZ=America/New_York
SECRET_KEY=[...]
ADMIN_PASSWORD=[...]
These variables in this .env
are used by the app in production.
NODE_ENV
: Defines which environment the API is running in the API containerNODE_PORT
: Defines which port is exposed by the API container in Docker. Defaults to3001
if not providedORIGINS
: Comma separated list of client origins (http://localhost,http://localhost:3000
). Allowed origins in the CORS configuration for the APIVITE_API_HOST
: Variable to pass the API host to the client frontend. Replace placeholder with host server IP or hostname (localhost
)TZ
: Sets the timezone of the API container and impacts the default timezone for timestamps during database insertions and request loggingSECRET_KEY
: Secret value that is used for signing API access tokensADMIN_PASSWORD
: Secret value that is used for accessing protected API routes and client views
The app's data is stored using SQLite3 databases in ./api/data
. To create and set up a development or production
database follow the below instructions.
Use the --env production
flag to seed a production database.
1. Run migrations: In the api
directory, run the Knex migrations to set up the database. The following command will apply the latest migrations.
npx knex migrate:latest
or
npx knex migrate:latest --env production
2. Database schema: The database schema consists of the following tables:
interactions
: Stores details about each interaction (columns: id, type_id, location_id, format_id, date)types
: Defines available types of interactions (columns: id, value, desc)locations
: Defines available location at which interactions can occur (columns: id, value)formats
: Defines types of formats in which interaction can take place (columns: id, value)
3. Database seeds: Seed the types
, locations
, and formats
tables with predefined values
npx knex seed:run
or
npx knex seed:run --env production
Migration and seeds files can be found in ./api/src/db/migrations
and ./api/src/db/seeds
respectively
Only seed a production database once to avoid data lose
LibStats is designed to be run locally in development and run in production using docker and docker-compose
Run any of the following commands from the project root. Both the frontend and backend can be run independently or as a fullstack app
Run the frontend independently
cd client
npm run dev
Visit http://localhost:3000
to access the client locally
Run the backend independently
cd api
npm run dev
Visit http://localhost:3001
to access the API locally
Run the fullstack app
cd api
npm run app
1. Verify database set up
To run the app in production, ensure that a production.sqlite
file has been created with any migrations and seeds run.
cd api
npx knex migrate:latest --env production
npx knex seed:run --env production
2. Start the docker stack
From the project root run the following command:
sudo docker compose up -d --build
Once started, the app's client should be available at your machine's local IP address on port 80 while the app's backend will be available the value defined using NODE_PORT
in the app's .env
. If this is not set the API will be available on port 3001
All example endpoint responses are abbreviated for this README
GET /app/report
Returns a report of interactions between a range of given dates and at a given location.
Query Parameters
start
andend
(required): Filter interactions by date range (e.g.2024-01-01
to2024-01-03
)location
(required): Filter interaction by a given location id (e.g.1
)
Response
{
"message": "ok",
"rows": [],
"count_total": 0,
"count_format": [
{
"id": 1,
"value": "In-Person",
"number_of_interactions": 0
}
],
"count_type": [
{
"id": 1,
"value": "Directional",
"number_of_interactions": 0
}
],
"count_days": [
{
"date": "2024-01-01",
"number_of_interactions": 0
}
]
}
GET /app/options
Returns the defined rows in types
, locations
, and formats
tables. Used in build forms for client UI
Response
{
"message": "ok",
"types": [
{
"id": 1,
"value": "Directional",
"desc": "Simple questions about facilities, hours, etc."
}
],
"locations": [
{
"id": 1,
"value": "Circulation"
}
],
"formats": [
{
"id": 1,
"value": "In-Person"
}
]
}
POST /app/record
Inserts a new row into the interactions
table
Request Parameters
This endpoint expects a json body with a specified type id, location id, and format, id
{
"type": 1,
"location": 2,
"format": 1
}
Each parameter represents the unique id of a row in the respective database table
type
: Integer (required)location
: Integer (required)format
: Integer (required)
Response
{ "message": "data added" }
GET /app/summary
Returns data for the app homepage
Response
{ "message": "ok", "count_month": 10 }
GET /app/interactions
Returns all rows in the interactions
table
Response
[
{
"id": 52,
"type": "Directional",
"location": "Reference",
"format": "In-Person",
"date": "2024-10-23"
},
{
"id": 53,
"type": "Directional",
"location": "Reference",
"format": "In-Person",
"date": "2024-10-23"
}
]
POST /auth/token
This endpoint verifies a user based on the app admin password and return an authorization object if the provided password is correct.
Request Parameters
This endpoint expects a json body with a supplied password
{
"password": "[...]"
}
password
: string(required)
Response
A successful response will take the below format:
{
"message": "ok",
"token" : "[...]",
"token_type" : "Bearer",
"issued_at": "[...]",
"expires_in": 600
}
token
: String representing the issued JWT access tokentoken_type
: String representing the type of token issuedissued_at
: Unix timestamp in seconds representing when the token was issuedexpires_in
: Number of seconds since issuance that the token will expire. App defaults to 600 seconds (10 minutes)
POST /auth/verify
This verifies that a given token is valid.
Request Parameters
This endpoint expects a request with a correctly formed Authorization
header.
POST http://[...]/auth/verify
Content-Type: application/json
Authorization: Bearer [...]
Replace the placeholder above with a valid access token
All endpoints in this category require a valid access token requested using /auth/token
. Tokens should be included
in the request Authorization
header in the following format Authorization: Bearer [token]
.
Admin endpoints query a select set of tables including:
types
locations
formats
GET /admin/:table
Gets all rows from a given table
Request Parameters
This expect a valid table from the above choices and a valid access token.
The request below will return all rows from the types
table.
GET http://[...]/admin/types
Authroization: Bearer [...]
Response
Returns a json response containing a response message in the selected rows
{
"message": "ok",
"rows": [
{
"id": 1,
"value": "Directional",
"desc": "Simple questions about facilities, hours, etc."
},
{
"id": 2,
"value": "Digital Resources",
"desc": "Questions about accessing and using MPL digital resources."
}
]
}
GET /admin/:table/:id
Requests a single row by ID from a given table.
Request Parameters
This endpoints expects a valid table name, row ID, and access token. The below request will select a row with an ID
1 from the types
table
GET http://[...]/admin/types/1
Authorization: Bearer [...]
Response
Returns a json response with a response message and row if found.
{
"message": "row found",
"row": {
"id": 1,
"value": "Directional",
"desc": "Simple questions about facilities, hours, etc."
}
}
POST /admin/:table
Add a new row to a given table.
Request Parameters
This endpoint expects a valid table name and a correctly formed json body representing the columns and values to be inserted into the database excluding an ID.
POST http://[...]/admin/types
Content-Type: application/json
Authorization: Bearer [...]
{
"value": "Informational",
"desc": "Simple questions about hours, directions, etc."
}
Response
Returns a json response with a successful message
{ "message": "New row added",
"row": {
"id": 5,
"value": "Directional",
"desc": "Simple questions about hours, directions, etc."
}
}
POST /admin/:table/:id
Update a single row in a given table by ID
Request Parameters
Expects a valid table, row ID, authorization header with access token, and json body representing the new values for to update the row with excluding the row ID
POST http://[...]/admin/types/1
Content-Type: application/json
Authorization: Bearer [...]
{
"value": "Directional",
"desc": "Simple questions about facilities, hours, etc."
}
Response
Returns a json response with a successful message and the updated row
{
"message": "Row updated",
"row": {
"id": 1,
"value": "Directional",
"desc": "Simple questions about hours, directions, etc."
}
}
GET /admin/stats
This endpoint return all time statistics from the interaction table including total number of interactions and count of interaction grouped by category (types, formats, interactions)
Request Parameters
Expects a valid access token and Authorization
header
GET http://[...]/admin/stats
Authorization: Bearer [...]
Response
Returns a json response with counts from the interactions table and respective response message
{
"message": "ok",
"count_total": 157,
"count_type": [
{
"id": 1,
"value": "Directional",
"total_interactions": 30
}
],
"count_location": [
{
"id": 1,
"value": "Circulation",
"total_interactions": 48
}
],
"count_format": [
{
"id": 1,
"value": "In-Person",
"total_interactions": 88
}
]
}
Any endpoint that response unsuccessfully or encounters an error will respond with a json like the following and an appropriate http status code.
{ "message": "No row found" }
Contributions are welcome! Please follow these steps to contribute:
- Fork the repository.
- Create a new branch (
git checkout -b feature/new-feature
). - Make your changes and commit (
git commit -m 'Add new feature'
). - Push your branch (
git push origin feature/new-feature
). - Open a pull request and describe your changes.
This project is licensed under the MIT License