https://github.com/dangkhoa2016/toy-api-server-nodejs
Toy API Server is a Fastify-based REST API that manages toy data in memory with automatic TTL expiry, rate limiting, and optional basic authentication.
https://github.com/dangkhoa2016/toy-api-server-nodejs
crud fastify nodejs restful-api toy-story toys
Last synced: about 1 month ago
JSON representation
Toy API Server is a Fastify-based REST API that manages toy data in memory with automatic TTL expiry, rate limiting, and optional basic authentication.
- Host: GitHub
- URL: https://github.com/dangkhoa2016/toy-api-server-nodejs
- Owner: dangkhoa2016
- Created: 2023-01-05T12:17:46.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2026-04-22T12:16:01.000Z (2 months ago)
- Last Synced: 2026-04-22T14:18:51.592Z (2 months ago)
- Topics: crud, fastify, nodejs, restful-api, toy-story, toys
- Language: JavaScript
- Homepage:
- Size: 133 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Toy API Server
> 🌐 Language / Ngôn ngữ: **English** | [Tiếng Việt](README.vi.md)
Sample REST API built with Fastify for managing toy records with short-lived in-memory storage, automatic TTL cleanup, per-IP anti-abuse controls, optional Basic Auth, and Swagger/OpenAPI documentation.
This repository is the source-of-truth Node.js implementation for the toy API behavior, validation rules, and naming conventions that are also mirrored by the [Toy-Api-Server-Cloudflare-Worker](https://github.com/dangkhoa2016/Toy-Api-Server-Cloudflare-Worker) port.
For a file-by-file comparison with the Cloudflare Worker version, see [docs/comparison-with-cloudflare-worker.md](docs/comparison-with-cloudflare-worker.md).
([Tiếng Việt](docs/comparison-with-cloudflare-worker.vi.md))
For details on the anti-abuse and rate-limiting policy, see [docs/rate-limit.md](docs/rate-limit.md).
([Tiếng Việt](docs/rate-limit.vi.md))
The project intentionally keeps all toy data in memory and expires records automatically after a short TTL.
## Frontend Integrations
This API is also used as the backend for these frontend projects:
- [Toys-UI-Javascript](https://github.com/dangkhoa2016/Toys-UI-Javascript)
- [Toys-UI-VueJs](https://github.com/dangkhoa2016/Toys-UI-VueJs)
## Setup
1. Install dependencies:
`npm install`
2. Copy environment values if needed:
`cp .env.example .env.local`
3. Start the development server:
`npm run dev`
When running outside production, `bin/www` automatically loads variables from `.env.local`.
## Technologies Used
- Core runtime: `Node.js` (CommonJS modules) + `Fastify`.
- Fastify ecosystem: `@fastify/cors`, `@fastify/helmet`, `@fastify/swagger`, `@fastify/swagger-ui`, `fastify-plugin`.
- Utilities: `debug`, `lodash-core`, `dotenv`.
- Developer tooling: `nodemon`, `eslint`, `@eslint/js`, `globals`, `prettier`.
- Testing and CI: Node.js built-in test runner (`node --test`) and GitHub Actions (Node 20 workflow).
## Environment
- `PORT`: port to listen on.
- `HOST`: host interface to bind.
- `CORS_ORIGINS`: comma-separated list of trusted origins.
- `LOG_LEVEL`: structured logger level; when set, it enables Fastify logging in any environment.
- `RATE_LIMIT_ENABLED`: enable or disable in-memory rate limiting.
- `RATE_LIMIT_MAX`: maximum create requests allowed per window per client IP for `POST /api/toys`.
- `DEFAULT_RATE_LIMIT_WINDOW_MINUTES`: fallback create rate-limit window in minutes used when `RATE_LIMIT_WINDOW_MS` is not set.
- `RATE_LIMIT_WINDOW_MS`: create rate-limit window length in milliseconds.
- `DEFAULT_MAX_TOYS_PER_IP`: fallback active-toy cap used when `MAX_TOYS_PER_IP` is not set.
- `MAX_TOYS_PER_IP`: maximum active toy records retained per client IP.
- `DEFAULT_SEED_MAX_TOYS_PER_IP`: fallback seed cap used when `SEED_MAX_TOYS_PER_IP` is not set.
- `SEED_MAX_TOYS_PER_IP`: temporary active-toy cap allowed while an IP is seeding its first batch.
- `DEFAULT_SEED_WINDOW_MINUTES`: fallback seed window in minutes used when `SEED_WINDOW_MS` is not set.
- `SEED_WINDOW_MS`: how long an IP keeps the temporary seeding allowance after its first successful create.
- `SECURITY_HEADERS_ENABLED`: enable or disable security headers.
- `BASIC_AUTH_ENABLED`: protect API and docs with HTTP Basic Auth.
- `BASIC_AUTH_USERNAME`: username used when basic auth is enabled.
- `BASIC_AUTH_PASSWORD`: password used when basic auth is enabled.
- `BASIC_AUTH_REALM`: optional realm sent in the `WWW-Authenticate` header.
- `DEFAULT_TOY_TTL_MINUTES`: fallback toy TTL in minutes used when `TOY_TTL_MS` is not set.
- `TOY_TTL_MS`: time-to-live for each toy record in milliseconds.
- `DEFAULT_TOY_CLEANUP_INTERVAL_MINUTES`: fallback cleanup interval in minutes used when `TOY_CLEANUP_INTERVAL_MS` is not set.
- `TOY_CLEANUP_INTERVAL_MS`: cleanup interval used by background maintenance to remove expired toys, stale rate-limit entries, and stale seed states.
When `NODE_ENV=production`, requests with an untrusted `Origin` header are rejected.
Preflight responses for trusted origins advertise `GET, POST, PUT, PATCH, DELETE, OPTIONS`, so cross-origin updates such as `PATCH /api/toys/:id/likes` are allowed when the caller origin is trusted.
Fastify's structured JSON logger is enabled whenever `LOG_LEVEL` is set, and production defaults it to `info`; responses still return `x-request-id` and `x-correlation-id` headers for request tracing.
Rate limiting is in-memory and only applies to `POST /api/toys` by default.
Responses to `POST /api/toys` include `x-ratelimit-limit`, `x-ratelimit-remaining`, and `x-ratelimit-reset`; when blocked, they also include `retry-after`.
When basic auth is enabled, all routes except `/healthz` and favicon assets require credentials.
## Scripts
- `npm run dev`: run with nodemon and debug logs.
- `npm run lint`: lint JavaScript files with ESLint.
- `npm run lint:fix`: lint and apply safe automatic fixes.
- `npm run format`: format the repository with Prettier.
- `npm run format:check`: check formatting without changing files.
- `npm start`: run once with Node.js.
- `npm test`: run unit and integration tests with the Node test runner.
## API Docs
- Swagger UI: `/docs/`
- OpenAPI JSON: `/openapi.json`
- When basic auth is enabled, both `/docs/` and `/openapi.json` require credentials.
- When basic auth is enabled, Swagger UI shows an `Authorize` button for the shared `basicAuth` scheme.
- In production, Swagger UI requests still pass CORS when the docs page is served from a trusted forwarded host but the browser-origin is a local loopback proxy such as `http://localhost:8080`.
## Data Lifecycle
- State only lives in memory and is cleared when the process stops.
- Toy records expire automatically after `TOY_TTL_MS` and are removed by reads plus background cleanup.
- Background maintenance runs every `TOY_CLEANUP_INTERVAL_MS` (default: 1 minute) and also cleans stale rate-limit and seed state entries.
- A client IP can temporarily grow beyond `MAX_TOYS_PER_IP` during its first seed session, up to `SEED_MAX_TOYS_PER_IP` within `SEED_WINDOW_MS`.
- Updating a toy or its likes does not extend its existing TTL.
## Security
- Security headers are provided by Fastify Helmet.
- CORS only trusts origins listed in `CORS_ORIGINS` in production, except for the narrow `/docs` proxy case above.
- Rate limiting is enforced per client IP for `POST /api/toys` and returns `429` with `x-ratelimit-*` headers when exceeded.
- Active toy records are capped per client IP and new creates return `429` once the quota is exhausted.
- Seeding only changes the active-toy cap; it does not bypass the request rate limit for `POST /api/toys`.
- Optional basic auth protects API and Swagger endpoints with `401` + `WWW-Authenticate` when credentials are missing or invalid.
## API notes
- `DELETE /api/toys/:id` is the only supported delete endpoint.
- `GET /healthz` returns a lightweight service health payload.
- Full toy updates accept `POST`, `PUT`, or `PATCH` on `/api/toys/:id`; likes-only updates accept `POST`, `PUT`, or `PATCH` on `/api/toys/:id/likes`.
- Create and update requests enforce `likes >= 0`, a bounded name length, and an absolute image URI.
- Create requests store toys for 15 minutes by default before automatic expiry.
- The first successful creates from one IP can grow that IP up to 15 active toys by default before the normal cap of 5 resumes.
- Updates keep the original expiry time instead of resetting the 15-minute TTL.
- Error responses are standardized as:
```json
{
"error": {
"statusCode": 404,
"message": "Route not found"
}
}
```
## curl Examples
Start the server first:
```bash
npm run dev
```
If basic auth is enabled, add credentials to curl calls with `-u user:password`.
List toys:
```bash
curl http://localhost:8080/api/toys
```
List toys with basic auth enabled:
```bash
curl -u admin:secret http://localhost:8080/api/toys
```
Create a toy:
```bash
curl -X POST http://localhost:8080/api/toys \
-H 'Content-Type: application/json' \
-d '{
"name": "Toy Robot",
"image": "https://example.com/robot.png",
"likes": 0
}'
```
Get a toy by id:
```bash
curl http://localhost:8080/api/toys/1
```
Update a toy:
```bash
curl -X PUT http://localhost:8080/api/toys/1 \
-H 'Content-Type: application/json' \
-d '{
"name": "Toy Boat",
"image": "https://example.com/boat.png",
"likes": 3
}'
```
Update a toy with an alternative method:
```bash
curl -X POST http://localhost:8080/api/toys/1 \
-H 'Content-Type: application/json' \
-d '{
"name": "Toy Boat",
"image": "https://example.com/boat.png",
"likes": 3
}'
```
Update likes only:
```bash
curl -X PATCH http://localhost:8080/api/toys/1/likes \
-H 'Content-Type: application/json' \
-d '{"likes": 5}'
```
Delete a toy:
```bash
curl -X DELETE http://localhost:8080/api/toys/1
```
Export toys as JSON:
```bash
curl -OJ http://localhost:8080/api/toys/export
```
Fetch the OpenAPI document:
```bash
curl -u admin:secret http://localhost:8080/openapi.json
```
## CI
GitHub Actions currently installs dependencies with `yarn install --frozen-lockfile` and runs `yarn test` on every push and pull request.