{"id":26365145,"url":"https://github.com/dangkhoa2016/elasticsearch-restaurants-api-nodejs","last_synced_at":"2026-04-29T21:02:40.518Z","repository":{"id":216906002,"uuid":"447571413","full_name":"dangkhoa2016/Elasticsearch-Restaurants-Api-Nodejs","owner":"dangkhoa2016","description":"This project is a demo about using Elasticsearch with Nodejs for search restaurants.","archived":false,"fork":false,"pushed_at":"2023-01-21T04:18:57.000Z","size":939,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-16T19:35:03.399Z","etag":null,"topics":["elasticsearch","fastify","nodejs"],"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":"2022-01-13T11:19:27.000Z","updated_at":"2023-01-21T04:19:45.000Z","dependencies_parsed_at":"2024-01-13T16:41:06.755Z","dependency_job_id":"18d3110c-47c6-4500-bdcf-a16703db097c","html_url":"https://github.com/dangkhoa2016/Elasticsearch-Restaurants-Api-Nodejs","commit_stats":null,"previous_names":["dangkhoa2016/elasticsearch-restaurants-api-nodejs"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dangkhoa2016/Elasticsearch-Restaurants-Api-Nodejs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dangkhoa2016%2FElasticsearch-Restaurants-Api-Nodejs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dangkhoa2016%2FElasticsearch-Restaurants-Api-Nodejs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dangkhoa2016%2FElasticsearch-Restaurants-Api-Nodejs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dangkhoa2016%2FElasticsearch-Restaurants-Api-Nodejs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dangkhoa2016","download_url":"https://codeload.github.com/dangkhoa2016/Elasticsearch-Restaurants-Api-Nodejs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dangkhoa2016%2FElasticsearch-Restaurants-Api-Nodejs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281322869,"owners_count":26481547,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-27T02:00:05.855Z","response_time":61,"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":["elasticsearch","fastify","nodejs"],"created_at":"2025-03-16T19:30:43.612Z","updated_at":"2026-04-29T21:02:40.511Z","avatar_url":"https://github.com/dangkhoa2016.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Elasticsearch Restaurants API (Node.js + Fastify + OpenSearch)\n\n\u003e 🌐 Language / Ngôn ngữ: **English** | [Tiếng Việt](readme.vi.md)\n\n## About\n\nA geo-search API for restaurants built with [Fastify](https://fastify.dev/) and an OpenSearch/Elasticsearch-compatible backend.\n\nThis API serves as the **backend** for [Elasticsearch-Restaurants-Map-UI](https://github.com/dangkhoa2016/Elasticsearch-Restaurants-Map-UI) — a map-based UI that displays restaurant locations using the geo-search endpoints provided by this project.\n\nA Cloudflare Worker port of this project is available at [Elasticsearch-Restaurants-Api-Cloudflare-Worker](https://github.com/dangkhoa2016/Elasticsearch-Restaurants-Api-Cloudflare-Worker).\n\nSee the detailed source code similarity analysis between the two projects: [English](docs/comparison-with-cloudflare-worker.md) | [Tiếng Việt](docs/comparison-with-cloudflare-worker.vi.md)\n\nFor a list of top locations with the most restaurants (useful for testing geo-search): [English](docs/list-top-location-for-test.md) | [Tiếng Việt](docs/list-top-location-for-test.vi.md)\n\n**Features:**\n\n- Geo-search by circle (centre + distance) or rectangle (bounding box).\n- Fetch any document by ID.\n- Environment-driven runtime config — no hardcoded values.\n- Structured JSON logging (Pino via Fastify) for production; `debug` namespaces for development.\n- Per-IP rate limiting via `@fastify/rate-limit`.\n- Security headers via `@fastify/helmet`.\n- CORS allowlist via `@fastify/cors`.\n- Debug sleep support on `/search`, gated by env and an optional secret header.\n- Health (`/health`) and readiness (`/ready`) endpoints.\n- Backward-compatible response contract — raw OpenSearch fields are preserved alongside normalised `items` and `pageInfo`.\n\n## Technologies Used\n\n| Category | Technology |\n|---|---|\n| Runtime | [Node.js](https://nodejs.org/) 20+ |\n| Web framework | [Fastify](https://fastify.dev/) 5.x |\n| OpenSearch client | [@opensearch-project/opensearch](https://github.com/opensearch-project/opensearch-js) |\n| OpenSearch plugin | [@fastify/opensearch](https://github.com/fastify/fastify-opensearch) |\n| CORS | [@fastify/cors](https://github.com/fastify/fastify-cors) |\n| Security headers | [@fastify/helmet](https://github.com/fastify/fastify-helmet) |\n| Rate limiting | [@fastify/rate-limit](https://github.com/fastify/fastify-rate-limit) |\n| Form body parsing | [@fastify/formbody](https://github.com/fastify/fastify-formbody) |\n| Structured logging | [Pino](https://getpino.io/) (built into Fastify) |\n| Debug logging | [debug](https://github.com/debug-js/debug) |\n| Environment loading | [dotenv](https://github.com/motdotla/dotenv) |\n| Dev auto-reload | [nodemon](https://nodemon.io/) |\n| Linting | [ESLint](https://eslint.org/) 10.x |\n| Testing | Node.js built-in `node:test` runner |\n\n## Requirements\n\n- Node.js 20+\n- Yarn 1.x (or npm)\n- An OpenSearch or Elasticsearch-compatible endpoint\n\n## Directory structure\n\n```text\nElasticsearch-Restaurants-Api-Nodejs/\n  readme.md\n  package.json\n  .env.sample              ← template for environment variables\n  .env.local               ← local overrides, git-ignored\n  bin/\n    www                    ← server entrypoint, reads .env.local in dev\n  app/\n    index.js               ← Fastify factory build_server()\n    config/\n      runtime.js           ← parse + validate all env vars\n    routes/\n      home.js              ← GET /, /health, /ready, /favicon.*\n      elasticsearch.js     ← POST /search, GET /doc/:id\n      errors.js            ← 404 handler\n    services/\n      elasticsearch_client.js  ← OpenSearch client singleton\n      helper.js            ← geo-search query builder\n      response_formatter.js    ← normalise search + document responses\n      logger.js            ← request/response logging Fastify plugin\n    manual/                ← standalone import/debug scripts\n  test/\n    app.test.js            ← integration tests (5 tests)\n    helper.test.js         ← unit tests (3 tests)\n```\n\n## Environment variables\n\nCopy `.env.sample` to `.env.local` and adjust the values:\n\n```bash\ncp .env.sample .env.local\n```\n\n| Variable | Required | Default | Description |\n| --- | :---: | --- | --- |\n| `ELASTICSEARCH_URL` | ✅ | — | OpenSearch/Elasticsearch endpoint. Required in `staging`/`production`. Defaults to `http://localhost:9200` in development. |\n| `DEFAULT_INDEX` | | `restaurants` | Index used when a request does not specify one. |\n| `PORT` | | `8080` | Port the server listens on. |\n| `HOST` | | `0.0.0.0` | Host/interface the server binds to. |\n| `ALLOWED_ORIGINS` | | `\"\"` (empty) | Comma-separated CORS allowlist, e.g. `https://app.example.com`. In development, all `localhost` origins are allowed when empty. |\n| `ALLOW_DEBUG_SLEEP` | | `true` in dev, `false` in production | Enables the `sleep` field on `POST /search`. Keep `false` in production. |\n| `DEBUG_SLEEP_HEADER` | | `x-debug-sleep-token` | Header name checked when `DEBUG_SLEEP_SECRET` is set. |\n| `DEBUG_SLEEP_SECRET` | | `\"\"` (empty) | When set, the `DEBUG_SLEEP_HEADER` must match this value to use debug sleep. |\n| `MAX_DEBUG_SLEEP_MS` | | `5000` | Maximum sleep duration in milliseconds. |\n| `REQUEST_BODY_LIMIT` | | `1048576` (1 MB) | Maximum request body size in bytes. |\n| `REQUEST_TIMEOUT_MS` | | `30000` | Fastify request timeout in milliseconds. |\n| `OPENSEARCH_REQUEST_TIMEOUT_MS` | | `30000` | OpenSearch client request timeout in milliseconds. |\n| `RATE_LIMIT_MAX` | | `100` | Maximum requests per time window per IP. |\n| `RATE_LIMIT_WINDOW` | | `1 minute` | Rate limit time window. |\n| `LOG_LEVEL` | | `debug` in dev, `info` in production | Minimum Fastify/Pino log level. |\n| `ENABLE_STRUCTURED_LOGGING` | | `true` | Enable Pino JSON logging. Set to `false` for plain output during local development. |\n| `NODE_ENV` | | `development` | Operating mode. `production` and `staging` enforce CORS and disable debug sleep by default. |\n\n\u003e **Note:** `.env.local` is listed in `.gitignore` — never commit it. `.env.sample` is the file committed to the repo (no sensitive values).\n\n## Run locally\n\nInstall dependencies:\n\n```bash\nyarn install\n```\n\nRun in development mode (with `nodemon` auto-reload and `DEBUG` namespaces):\n\n```bash\nyarn dev\n```\n\nRun in production mode:\n\n```bash\nyarn start\n```\n\n## Scripts\n\n| Script | Command | Description |\n| --- | --- | --- |\n| `yarn dev` | `DEBUG=elasticsearch-restaurants-api-nodejs* nodemon bin/www` | Development server with auto-reload. |\n| `yarn start` | `node bin/www` | Production server. |\n| `yarn lint` | `eslint .` | Run ESLint. |\n| `yarn test` | `node --test test/*.test.js` | Run all tests. |\n| `yarn check` | `yarn lint \u0026\u0026 yarn test` | Lint then test. |\n\n## API\n\n### `GET /`\n\n```json\n{ \"message\": \"Welcome to Elasticsearch Restaurants Api Nodejs.\" }\n```\n\n### `GET /health`\n\nReturns process health. Always `200` while the process is running.\n\n```json\n{ \"status\": \"ok\" }\n```\n\n### `GET /ready`\n\nChecks connectivity to OpenSearch. Returns `200` on success, `503` if the ping fails.\n\n```json\n{ \"status\": \"ready\" }\n```\n\n### `POST /search`\n\nSearch for restaurants by location. Request body must be `application/json` or `application/x-www-form-urlencoded`. The `type` field is required.\n\n**Circle search** — finds documents within a radius of a point:\n\n```json\n{\n  \"type\": \"circle\",\n  \"location\": \"-37.852,144.993165\",\n  \"distance\": \"200m\"\n}\n```\n\n`location` accepts a `\"lat,lon\"` string, a `{ lat, lon }` object, or a `[lat, lon]` array. `radius` is accepted as a deprecated alias for `distance`.\n\n**Rectangle search** — finds documents within a bounding box:\n\n```json\n{\n  \"type\": \"rectangle\",\n  \"top_left\": { \"lat\": -37.8, \"lon\": 144.9 },\n  \"bottom_right\": { \"lat\": -38, \"lon\": 145 }\n}\n```\n\n**Optional fields:**\n\n| Field | Description |\n| --- | --- |\n| `index` | Override the default OpenSearch index. |\n| `sleep` | Debug only — delay the response for this many milliseconds (see Debug sleep). |\n\n**Response:**\n\n```json\n{\n  \"took\": 3,\n  \"hits\": {\n    \"total\": { \"value\": 1, \"relation\": \"eq\" },\n    \"hits\": []\n  },\n  \"_shards\": { \"total\": 1, \"successful\": 1, \"failed\": 0 },\n  \"index\": \"restaurants\",\n  \"total\": 1,\n  \"returned\": 1,\n  \"items\": [\n    {\n      \"id\": \"restaurant-1\",\n      \"index\": \"restaurants\",\n      \"score\": 1,\n      \"source\": { \"name\": \"Mesa Verde\" },\n      \"sort\": [],\n      \"fields\": {}\n    }\n  ],\n  \"pageInfo\": {\n    \"limit\": 80,\n    \"offset\": 0,\n    \"returned\": 1,\n    \"total\": 1,\n    \"hasMore\": false\n  }\n}\n```\n\nExisting consumers reading `hits.hits`, `took`, or `_shards` continue to work. New consumers should use `items`, `total`, and `pageInfo`.\n\n**Validation error:**\n\n```json\n{\n  \"error\": \"Invalid request payload.\",\n  \"code\": \"VALIDATION_ERROR\",\n  \"details\": [\n    { \"instancePath\": \"\", \"keyword\": \"required\", \"message\": \"must have required property 'distance'\" }\n  ]\n}\n```\n\n### `GET /doc/:id`\n\nFetch a document by ID. Optional query parameters: `index` (string) and `_source` (boolean or string).\n\n```json\n{\n  \"_id\": \"restaurant-1\",\n  \"_index\": \"restaurants\",\n  \"_source\": { \"name\": \"Mesa Verde\" },\n  \"found\": true,\n  \"item\": {\n    \"id\": \"restaurant-1\",\n    \"index\": \"restaurants\",\n    \"source\": { \"name\": \"Mesa Verde\" },\n    \"found\": true\n  }\n}\n```\n\n### Error response shape\n\nAll errors follow a consistent JSON contract:\n\n```json\n{ \"error\": \"Human-readable message\", \"code\": \"MACHINE_READABLE_CODE\" }\n```\n\nCommon error codes:\n\n| Code | Status | Description |\n| --- | --- | --- |\n| `VALIDATION_ERROR` | 400 | Request body failed schema validation. |\n| `INVALID_SEARCH_QUERY` | 400 | Geo-search parameters could not be parsed. |\n| `DEBUG_SLEEP_LIMIT_EXCEEDED` | 400 | `sleep` value exceeds `MAX_DEBUG_SLEEP_MS`. |\n| `DEBUG_SLEEP_NOT_ALLOWED` | 403 | Debug sleep is not permitted for this request. |\n| `OPENSEARCH_NOT_READY` | 503 | OpenSearch ping failed on `/ready`. |\n| `ROUTE_NOT_FOUND` | 404 | No route matched the request path. |\n| `INTERNAL_SERVER_ERROR` | 500 | Unhandled server error. |\n\n## Debug sleep\n\nThe `sleep` field on `POST /search` delays the response by the given number of milliseconds. It is designed for testing timeouts and loading states on the client.\n\nAccess is gated:\n\n- When `ALLOW_DEBUG_SLEEP=false` (the production default), the field is silently ignored and the request continues normally.\n- When `ALLOW_DEBUG_SLEEP=true` and `DEBUG_SLEEP_SECRET` is not set, any request from a non-production environment may use it.\n- When `ALLOW_DEBUG_SLEEP=true` and `DEBUG_SLEEP_SECRET` is set, the `DEBUG_SLEEP_HEADER` must be present and match the secret.\n\nRecommended setup for a shared staging environment:\n\n```\nALLOW_DEBUG_SLEEP=true\nDEBUG_SLEEP_HEADER=x-debug-sleep-token\nDEBUG_SLEEP_SECRET=change-me\nMAX_DEBUG_SLEEP_MS=5000\n```\n\nSend the secret in the request header:\n\n```bash\ncurl -X POST http://localhost:8080/search \\\n  -H \"Content-Type: application/json\" \\\n  -H \"x-debug-sleep-token: change-me\" \\\n  -d '{ \"type\": \"circle\", \"location\": \"-37.852,144.993165\", \"distance\": \"200m\", \"sleep\": 2000 }'\n```\n\nIf the guard is not satisfied, the API returns `403 DEBUG_SLEEP_NOT_ALLOWED`.\n\n## Tests\n\nRun all tests:\n\n```bash\nyarn test\n```\n\nThere are currently 8 tests across 2 files:\n\n- `test/app.test.js` — integration tests using Fastify's injection API (5 tests):\n  - `GET /health`, `GET /ready`, `POST /search` (validation + contract), `GET /doc/:id`\n- `test/helper.test.js` — unit tests for the geo-search builder (3 tests):\n  - Invalid coordinate handling, `radius` alias, missing distance error\n\n## Manual scripts\n\n`app/manual/` contains standalone Node.js scripts for dataset import and debugging. They share a bootstrap module that loads `.env.local` and reuses runtime config (no hardcoded credentials).\n\nScripts available:\n\n| Script | Description |\n| --- | --- |\n| `bootstrap.js` | Shared `.env.local` loader used by all manual scripts. |\n| `import.js` | Bulk-import restaurant data into OpenSearch. |\n| `elasticsearch.js` | Low-level query examples. |\n| `geo_search.js` | Test geo-search queries directly against OpenSearch. |\n| `debug.js` | Debug helper for inspecting raw responses. |\n| `client.js` | Standalone OpenSearch client setup. |\n\nRun a script directly:\n\n```bash\nnode app/manual/geo_search.js\n```\n\n## CI\n\nGitHub Actions runs on every push and pull request to `main`:\n\n```\nyarn lint\nyarn test\n```\n\nWorkflow file: `.github/workflows/ci.yml`.\n\n## Deploy\n\n### Prerequisites\n\n- A Node.js 20+ runtime environment.\n- An accessible OpenSearch/Elasticsearch endpoint.\n- All required environment variables set (see [Environment variables](#environment-variables)).\n\n### Option 1 — Direct Node.js (VPS / bare metal)\n\n1. Clone the repository and install dependencies:\n\n   ```bash\n   git clone https://github.com/dangkhoa2016/Elasticsearch-Restaurants-Api-Nodejs.git\n   cd Elasticsearch-Restaurants-Api-Nodejs\n   yarn install --frozen-lockfile\n   ```\n\n2. Set environment variables. The recommended approach is to export them in your shell profile or use a process manager (see Option 2):\n\n   ```bash\n   export NODE_ENV=production\n   export ELASTICSEARCH_URL=https://user:password@your-cluster.example.com\n   export ALLOWED_ORIGINS=https://your-frontend.example.com\n   export PORT=8080\n   ```\n\n3. Start the server:\n\n   ```bash\n   yarn start\n   ```\n\n### Option 2 — PM2 (recommended for long-running servers)\n\n[PM2](https://pm2.keymetrics.io/) manages the process, restarts on crash, and handles log rotation.\n\n1. Install PM2 globally:\n\n   ```bash\n   npm install -g pm2\n   ```\n\n2. Create an `ecosystem.config.js` file in the project root:\n\n   ```js\n   module.exports = {\n     apps: [\n       {\n         name: 'restaurants-api',\n         script: 'bin/www',\n         instances: 1,\n         exec_mode: 'fork',\n         env_production: {\n           NODE_ENV: 'production',\n           PORT: 8080,\n           ELASTICSEARCH_URL: 'https://user:password@your-cluster.example.com',\n           ALLOWED_ORIGINS: 'https://your-frontend.example.com',\n           ENABLE_STRUCTURED_LOGGING: 'true',\n           LOG_LEVEL: 'info'\n         }\n       }\n     ]\n   };\n   ```\n\n3. Start the app with PM2:\n\n   ```bash\n   pm2 start ecosystem.config.js --env production\n   pm2 save        # persist the process list across reboots\n   pm2 startup     # configure PM2 to start on system boot\n   ```\n\n4. Useful PM2 commands:\n\n   ```bash\n   pm2 list                     # list running apps\n   pm2 logs restaurants-api     # tail logs\n   pm2 reload restaurants-api   # zero-downtime reload\n   pm2 stop restaurants-api     # stop the app\n   ```\n\n### Option 3 — Docker\n\n1. Create a `Dockerfile` in the project root:\n\n   ```dockerfile\n   FROM node:20-alpine\n   WORKDIR /app\n   COPY package.json yarn.lock ./\n   RUN yarn install --frozen-lockfile --production\n   COPY . .\n   EXPOSE 8080\n   CMD [\"node\", \"bin/www\"]\n   ```\n\n2. Build and run:\n\n   ```bash\n   docker build -t restaurants-api .\n   docker run -d \\\n     -p 8080:8080 \\\n     -e NODE_ENV=production \\\n     -e ELASTICSEARCH_URL=https://user:password@your-cluster.example.com \\\n     -e ALLOWED_ORIGINS=https://your-frontend.example.com \\\n     --name restaurants-api \\\n     restaurants-api\n   ```\n\n### Option 4 — PaaS (Railway, Render, Fly.io, Heroku)\n\nMost PaaS platforms auto-detect a Node.js project and use the `start` script from `package.json`.\n\n1. Connect your repository to the platform.\n2. Set the required environment variables in the platform's dashboard:\n   - `NODE_ENV=production`\n   - `ELASTICSEARCH_URL`\n   - `ALLOWED_ORIGINS`\n   - Any other variables from the table above.\n3. Deploy — the platform runs `yarn install` then `yarn start` automatically.\n\n### Production checklist\n\n- [ ] `NODE_ENV` is set to `production`.\n- [ ] `ELASTICSEARCH_URL` points to a live cluster and does not use default localhost.\n- [ ] `ALLOWED_ORIGINS` is restricted to your real frontend domain(s).\n- [ ] `ALLOW_DEBUG_SLEEP` is `false` (or not set).\n- [ ] `ENABLE_STRUCTURED_LOGGING` is `true`.\n- [ ] `LOG_LEVEL` is `info` or `warn`.\n- [ ] `RATE_LIMIT_MAX` and `RATE_LIMIT_WINDOW` are tuned for your expected traffic.\n- [ ] A reverse proxy (nginx, Caddy, Cloudflare) terminates TLS in front of the Node.js process.\n\n## Related projects\n\n| Project | Description |\n| --- | --- |\n| [Elasticsearch-Restaurants-Api-Cloudflare-Worker](https://github.com/dangkhoa2016/Elasticsearch-Restaurants-Api-Cloudflare-Worker) | Cloudflare Worker port of this API — same business logic, Hono framework, ESM, custom fetch-based OpenSearch client. |\n| [Elasticsearch-Restaurants-Map-UI](https://github.com/dangkhoa2016/Elasticsearch-Restaurants-Map-UI) | Map-based UI that consumes this API to display restaurant locations. |\n\n## Other related projects by the author (not direct ports, but share the same dataset and similar goals):\n| [Elasticsearch-Restaurants-Aggregations-Api-Nodejs](https://github.com/dangkhoa2016/Elasticsearch-Restaurants-Aggregations-Api-Nodejs) | Node.js API with aggregation support for the same restaurant dataset. |\n| [Elasticsearch-Restaurants-Aggregations-UI](https://github.com/dangkhoa2016/Elasticsearch-Restaurants-Aggregations-UI) | UI for the aggregations API — charts and filters. |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdangkhoa2016%2Felasticsearch-restaurants-api-nodejs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdangkhoa2016%2Felasticsearch-restaurants-api-nodejs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdangkhoa2016%2Felasticsearch-restaurants-api-nodejs/lists"}