{"id":26365113,"url":"https://github.com/dangkhoa2016/toy-api-server-nodejs","last_synced_at":"2026-05-09T18:35:37.908Z","repository":{"id":216906027,"uuid":"585533610","full_name":"dangkhoa2016/Toy-Api-Server-Nodejs","owner":"dangkhoa2016","description":"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.","archived":false,"fork":false,"pushed_at":"2026-04-22T12:16:01.000Z","size":136,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-22T14:18:51.592Z","etag":null,"topics":["crud","fastify","nodejs","restful-api","toy-story","toys"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dangkhoa2016.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-01-05T12:17:46.000Z","updated_at":"2026-04-22T12:21:21.000Z","dependencies_parsed_at":"2024-01-13T16:41:11.139Z","dependency_job_id":"bbb3e313-83c4-49d9-a9e6-48894a462b3f","html_url":"https://github.com/dangkhoa2016/Toy-Api-Server-Nodejs","commit_stats":null,"previous_names":["dangkhoa2016/toy-api-server-nodejs"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dangkhoa2016/Toy-Api-Server-Nodejs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dangkhoa2016%2FToy-Api-Server-Nodejs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dangkhoa2016%2FToy-Api-Server-Nodejs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dangkhoa2016%2FToy-Api-Server-Nodejs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dangkhoa2016%2FToy-Api-Server-Nodejs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dangkhoa2016","download_url":"https://codeload.github.com/dangkhoa2016/Toy-Api-Server-Nodejs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dangkhoa2016%2FToy-Api-Server-Nodejs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32831167,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"online","status_checked_at":"2026-05-09T02:00:06.633Z","response_time":123,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crud","fastify","nodejs","restful-api","toy-story","toys"],"created_at":"2025-03-16T19:30:23.486Z","updated_at":"2026-05-09T18:35:37.900Z","avatar_url":"https://github.com/dangkhoa2016.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Toy API Server\n\n\u003e 🌐 Language / Ngôn ngữ: **English** | [Tiếng Việt](README.vi.md)\n\nSample 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.\n\nThis 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.\n\nFor a file-by-file comparison with the Cloudflare Worker version, see [docs/comparison-with-cloudflare-worker.md](docs/comparison-with-cloudflare-worker.md).\n([Tiếng Việt](docs/comparison-with-cloudflare-worker.vi.md))\n\nFor details on the anti-abuse and rate-limiting policy, see [docs/rate-limit.md](docs/rate-limit.md).\n([Tiếng Việt](docs/rate-limit.vi.md))\n\nThe project intentionally keeps all toy data in memory and expires records automatically after a short TTL.\n\n## Frontend Integrations\n\nThis API is also used as the backend for these frontend projects:\n\n- [Toys-UI-Javascript](https://github.com/dangkhoa2016/Toys-UI-Javascript)\n- [Toys-UI-VueJs](https://github.com/dangkhoa2016/Toys-UI-VueJs)\n\n## Setup\n\n1. Install dependencies:\n   `npm install`\n2. Copy environment values if needed:\n   `cp .env.example .env.local`\n3. Start the development server:\n   `npm run dev`\n\nWhen running outside production, `bin/www` automatically loads variables from `.env.local`.\n\n## Technologies Used\n\n- Core runtime: `Node.js` (CommonJS modules) + `Fastify`.\n- Fastify ecosystem: `@fastify/cors`, `@fastify/helmet`, `@fastify/swagger`, `@fastify/swagger-ui`, `fastify-plugin`.\n- Utilities: `debug`, `lodash-core`, `dotenv`.\n- Developer tooling: `nodemon`, `eslint`, `@eslint/js`, `globals`, `prettier`.\n- Testing and CI: Node.js built-in test runner (`node --test`) and GitHub Actions (Node 20 workflow).\n\n## Environment\n\n- `PORT`: port to listen on.\n- `HOST`: host interface to bind.\n- `CORS_ORIGINS`: comma-separated list of trusted origins.\n- `LOG_LEVEL`: structured logger level; when set, it enables Fastify logging in any environment.\n- `RATE_LIMIT_ENABLED`: enable or disable in-memory rate limiting.\n- `RATE_LIMIT_MAX`: maximum create requests allowed per window per client IP for `POST /api/toys`.\n- `DEFAULT_RATE_LIMIT_WINDOW_MINUTES`: fallback create rate-limit window in minutes used when `RATE_LIMIT_WINDOW_MS` is not set.\n- `RATE_LIMIT_WINDOW_MS`: create rate-limit window length in milliseconds.\n- `DEFAULT_MAX_TOYS_PER_IP`: fallback active-toy cap used when `MAX_TOYS_PER_IP` is not set.\n- `MAX_TOYS_PER_IP`: maximum active toy records retained per client IP.\n- `DEFAULT_SEED_MAX_TOYS_PER_IP`: fallback seed cap used when `SEED_MAX_TOYS_PER_IP` is not set.\n- `SEED_MAX_TOYS_PER_IP`: temporary active-toy cap allowed while an IP is seeding its first batch.\n- `DEFAULT_SEED_WINDOW_MINUTES`: fallback seed window in minutes used when `SEED_WINDOW_MS` is not set.\n- `SEED_WINDOW_MS`: how long an IP keeps the temporary seeding allowance after its first successful create.\n- `SECURITY_HEADERS_ENABLED`: enable or disable security headers.\n- `BASIC_AUTH_ENABLED`: protect API and docs with HTTP Basic Auth.\n- `BASIC_AUTH_USERNAME`: username used when basic auth is enabled.\n- `BASIC_AUTH_PASSWORD`: password used when basic auth is enabled.\n- `BASIC_AUTH_REALM`: optional realm sent in the `WWW-Authenticate` header.\n- `DEFAULT_TOY_TTL_MINUTES`: fallback toy TTL in minutes used when `TOY_TTL_MS` is not set.\n- `TOY_TTL_MS`: time-to-live for each toy record in milliseconds.\n- `DEFAULT_TOY_CLEANUP_INTERVAL_MINUTES`: fallback cleanup interval in minutes used when `TOY_CLEANUP_INTERVAL_MS` is not set.\n- `TOY_CLEANUP_INTERVAL_MS`: cleanup interval used by background maintenance to remove expired toys, stale rate-limit entries, and stale seed states.\n\nWhen `NODE_ENV=production`, requests with an untrusted `Origin` header are rejected.\nPreflight 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.\nFastify'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.\nRate limiting is in-memory and only applies to `POST /api/toys` by default.\nResponses to `POST /api/toys` include `x-ratelimit-limit`, `x-ratelimit-remaining`, and `x-ratelimit-reset`; when blocked, they also include `retry-after`.\nWhen basic auth is enabled, all routes except `/healthz` and favicon assets require credentials.\n\n## Scripts\n\n- `npm run dev`: run with nodemon and debug logs.\n- `npm run lint`: lint JavaScript files with ESLint.\n- `npm run lint:fix`: lint and apply safe automatic fixes.\n- `npm run format`: format the repository with Prettier.\n- `npm run format:check`: check formatting without changing files.\n- `npm start`: run once with Node.js.\n- `npm test`: run unit and integration tests with the Node test runner.\n\n## API Docs\n\n- Swagger UI: `/docs/`\n- OpenAPI JSON: `/openapi.json`\n- When basic auth is enabled, both `/docs/` and `/openapi.json` require credentials.\n- When basic auth is enabled, Swagger UI shows an `Authorize` button for the shared `basicAuth` scheme.\n- 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`.\n\n## Data Lifecycle\n\n- State only lives in memory and is cleared when the process stops.\n- Toy records expire automatically after `TOY_TTL_MS` and are removed by reads plus background cleanup.\n- Background maintenance runs every `TOY_CLEANUP_INTERVAL_MS` (default: 1 minute) and also cleans stale rate-limit and seed state entries.\n- 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`.\n- Updating a toy or its likes does not extend its existing TTL.\n\n## Security\n\n- Security headers are provided by Fastify Helmet.\n- CORS only trusts origins listed in `CORS_ORIGINS` in production, except for the narrow `/docs` proxy case above.\n- Rate limiting is enforced per client IP for `POST /api/toys` and returns `429` with `x-ratelimit-*` headers when exceeded.\n- Active toy records are capped per client IP and new creates return `429` once the quota is exhausted.\n- Seeding only changes the active-toy cap; it does not bypass the request rate limit for `POST /api/toys`.\n- Optional basic auth protects API and Swagger endpoints with `401` + `WWW-Authenticate` when credentials are missing or invalid.\n\n## API notes\n\n- `DELETE /api/toys/:id` is the only supported delete endpoint.\n- `GET /healthz` returns a lightweight service health payload.\n- 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`.\n- Create and update requests enforce `likes \u003e= 0`, a bounded name length, and an absolute image URI.\n- Create requests store toys for 15 minutes by default before automatic expiry.\n- 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.\n- Updates keep the original expiry time instead of resetting the 15-minute TTL.\n- Error responses are standardized as:\n\n```json\n{\n  \"error\": {\n    \"statusCode\": 404,\n    \"message\": \"Route not found\"\n  }\n}\n```\n\n## curl Examples\n\nStart the server first:\n\n```bash\nnpm run dev\n```\n\nIf basic auth is enabled, add credentials to curl calls with `-u user:password`.\n\nList toys:\n\n```bash\ncurl http://localhost:8080/api/toys\n```\n\nList toys with basic auth enabled:\n\n```bash\ncurl -u admin:secret http://localhost:8080/api/toys\n```\n\nCreate a toy:\n\n```bash\ncurl -X POST http://localhost:8080/api/toys \\\n   -H 'Content-Type: application/json' \\\n   -d '{\n      \"name\": \"Toy Robot\",\n      \"image\": \"https://example.com/robot.png\",\n      \"likes\": 0\n   }'\n```\n\nGet a toy by id:\n\n```bash\ncurl http://localhost:8080/api/toys/1\n```\n\nUpdate a toy:\n\n```bash\ncurl -X PUT http://localhost:8080/api/toys/1 \\\n   -H 'Content-Type: application/json' \\\n   -d '{\n      \"name\": \"Toy Boat\",\n      \"image\": \"https://example.com/boat.png\",\n      \"likes\": 3\n   }'\n```\n\nUpdate a toy with an alternative method:\n\n```bash\ncurl -X POST http://localhost:8080/api/toys/1 \\\n   -H 'Content-Type: application/json' \\\n   -d '{\n      \"name\": \"Toy Boat\",\n      \"image\": \"https://example.com/boat.png\",\n      \"likes\": 3\n   }'\n```\n\nUpdate likes only:\n\n```bash\ncurl -X PATCH http://localhost:8080/api/toys/1/likes \\\n   -H 'Content-Type: application/json' \\\n   -d '{\"likes\": 5}'\n```\n\nDelete a toy:\n\n```bash\ncurl -X DELETE http://localhost:8080/api/toys/1\n```\n\nExport toys as JSON:\n\n```bash\ncurl -OJ http://localhost:8080/api/toys/export\n```\n\nFetch the OpenAPI document:\n\n```bash\ncurl -u admin:secret http://localhost:8080/openapi.json\n```\n\n## CI\n\nGitHub Actions currently installs dependencies with `yarn install --frozen-lockfile` and runs `yarn test` on every push and pull request.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdangkhoa2016%2Ftoy-api-server-nodejs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdangkhoa2016%2Ftoy-api-server-nodejs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdangkhoa2016%2Ftoy-api-server-nodejs/lists"}