|
1 | 1 | # wuwa-api |
2 | 2 |
|
3 | | -A Node.js (Fastify) REST API for **Wuthering Waves** data. It serves structured JSON (characters, weapons, metadata) and local images. |
| 3 | +A Node.js (Fastify) REST API for **Wuthering Waves** data — serves structured JSON (characters, weapons) and optimized WebP images. |
4 | 4 |
|
5 | | -Not affiliated with or endorsed by Kuro Games. |
| 5 | +> Not affiliated with or endorsed by Kuro Games. |
6 | 6 |
|
7 | | -## Features |
8 | | - |
9 | | -- REST API under `/v1/*` |
10 | | -- Swagger UI at `/docs` |
11 | | -- File-backed dataset: |
12 | | - - `assets/data/**/en.json` |
13 | | - - `assets/images/**` (normalized `.webp`) |
14 | | -- Published dataset under `assets/` (generated privately from community sources and normalized): |
15 | | - - character skills + scaling tables |
16 | | - - character base stats at levels `20/50/90` |
17 | | - - character images: `icon/card/splash/attribute` |
18 | | - - weapon base ATK at `20/50/90` + passive effects + weapon icon |
19 | | -- Data validation command (`npm run validate-data`) |
20 | | - |
21 | | -## Requirements |
22 | | - |
23 | | -- Node.js (recommend current LTS) |
24 | | - |
25 | | -## Install |
| 7 | +## Quick Start |
26 | 8 |
|
27 | 9 | ```bash |
28 | 10 | npm install |
| 11 | +cp .env.example .env # optional |
| 12 | +npm run dev # http://127.0.0.1:3000/docs |
29 | 13 | ``` |
30 | 14 |
|
31 | | -## Run |
32 | | - |
33 | | -### Development |
34 | | - |
35 | | -```bash |
36 | | -# optional: copy and tweak env |
37 | | -cp .env.example .env |
38 | | - |
39 | | -npm run dev |
40 | | -``` |
41 | | - |
42 | | -Open: |
43 | | -- Swagger UI: `http://127.0.0.1:3000/docs` |
44 | | - |
45 | | -### Build + start |
46 | | - |
47 | | -```bash |
48 | | -npm run build |
49 | | -npm run start |
50 | | -``` |
51 | | - |
52 | | -## Environment variables |
53 | | - |
54 | | -- `PORT` (default `3000`) |
55 | | -- `HOST` (default `0.0.0.0`) |
56 | | -- `DATA_ROOT` (default `assets/data`) |
57 | | -- `IMAGES_ROOT` (default `assets/images`) |
58 | | - |
59 | | -Rate limit tiers (self-hosters can change these in `.env` or in `docker-compose.yml`): |
60 | | -- `RATE_LIMIT_WINDOW` (default `1 minute`) |
61 | | -- `RATE_LIMIT_LIST_MAX` (default `15`) — list endpoints (`/v1/characters`, `/v1/weapons`) |
62 | | -- `RATE_LIMIT_DETAIL_MAX` (default `60`) — detail endpoints (`/v1/characters/:id`, `/v1/weapons/:id`) |
63 | | -- `RATE_LIMIT_IMAGE_MAX` (default `60`) — character image routes |
64 | | -- `RATE_LIMIT_DOCS_MAX` (default `120`) — docs routes (`/docs`, `/documentation/*`) |
65 | | - |
66 | | -CORS: |
67 | | -- `CORS_ALLOWED_ORIGINS` (comma-separated) — browser allowlist for cross-origin requests (curl/server-to-server clients are unaffected) |
68 | | - |
69 | | -Demo: |
70 | | -- `ENABLE_DEMO` (default `false`) — enables `/demo/*` (served from `.local/demo` and not intended for public deploys) |
71 | | - |
72 | | -Example: |
73 | | - |
74 | | -```bash |
75 | | -# easiest way: use a .env file |
76 | | -cp .env.example .env |
77 | | - |
78 | | -# then edit .env and run |
79 | | -npm run dev |
80 | | -``` |
81 | | - |
82 | | -## API endpoints |
83 | | - |
84 | | -### Health |
85 | | - |
86 | | -- `GET /healthz` |
87 | | - |
88 | | -```bash |
89 | | -curl -s http://127.0.0.1:3000/healthz |
90 | | -``` |
91 | | - |
92 | | -### Swagger docs |
93 | | - |
94 | | -- `GET /docs` |
95 | | - |
96 | | -### Meta |
97 | | - |
98 | | -- `GET /v1/meta` |
99 | | - |
100 | | -```bash |
101 | | -curl -s http://127.0.0.1:3000/v1/meta |
102 | | -``` |
103 | | - |
104 | | -### Characters |
| 15 | +**Production:** `npm run build && npm start` |
105 | 16 |
|
106 | | -- `GET /v1/characters` (supports `search`, `element`, `weaponType`, `rarity`, `limit`, `offset`) |
107 | | -- `GET /v1/characters/:id` |
| 17 | +## Features |
108 | 18 |
|
| 19 | +- REST API at `/v1/*` with Swagger UI at `/docs` |
| 20 | +- File-backed dataset (`assets/data`, `assets/images`) |
| 21 | +- Characters: skills, scaling tables, stats at 20/50/90, images (icon/card/splash/attribute) |
| 22 | +- Weapons: base ATK at 20/50/90, passive effects, icons |
| 23 | +- Rate limiting by route tier |
| 24 | +- Data validation: `npm run validate-data` |
| 25 | + |
| 26 | +--- |
| 27 | + |
| 28 | +<details> |
| 29 | +<summary><strong>API Endpoints</strong></summary> |
| 30 | + |
| 31 | +| Endpoint | Description | |
| 32 | +|----------|-------------| |
| 33 | +| `GET /healthz` | Health check | |
| 34 | +| `GET /docs` | Swagger UI | |
| 35 | +| `GET /v1/meta` | API & dataset metadata | |
| 36 | +| `GET /v1/characters` | List characters (`search`, `element`, `weaponType`, `rarity`, `limit`, `offset`) | |
| 37 | +| `GET /v1/characters/:id` | Character details | |
| 38 | +| `GET /v1/characters/:id/images` | List available images | |
| 39 | +| `GET /v1/characters/:id/images/:file` | Get image file | |
| 40 | +| `GET /v1/weapons` | List weapons (`search`, `type`, `rarity`, `limit`, `offset`) | |
| 41 | +| `GET /v1/weapons/:id` | Weapon details | |
| 42 | +| `GET /v1/images/...` | Static image files (legacy) | |
| 43 | + |
| 44 | +**Examples:** |
109 | 45 | ```bash |
110 | 46 | curl -s "http://127.0.0.1:3000/v1/characters?search=augusta" | jq |
111 | 47 | curl -s http://127.0.0.1:3000/v1/characters/augusta | jq |
112 | | -``` |
113 | | - |
114 | | -### Character images |
115 | | - |
116 | | -- `GET /v1/characters/:id/images` (lists available `.webp` files) |
117 | | -- `GET /v1/characters/:id/images/:file` |
118 | | - |
119 | | -```bash |
120 | | -curl -s http://127.0.0.1:3000/v1/characters/augusta/images | jq |
121 | | -curl -L http://127.0.0.1:3000/v1/characters/augusta/images/splash.webp --output splash.webp |
122 | | -``` |
123 | | - |
124 | | -### Weapons |
125 | | - |
126 | | -- `GET /v1/weapons` (supports `search`, `type`, `rarity`, `limit`, `offset`) |
127 | | -- `GET /v1/weapons/:id` |
128 | | - |
129 | | -```bash |
130 | | -curl -s "http://127.0.0.1:3000/v1/weapons?search=harvest" | jq |
131 | 48 | curl -s http://127.0.0.1:3000/v1/weapons/ages-of-harvest | jq |
132 | 49 | ``` |
133 | 50 |
|
134 | | -### Static image root (legacy/compat) |
135 | | - |
136 | | -- `GET /v1/images/...` serves static files rooted at `IMAGES_ROOT`. |
137 | | - |
138 | | -Example: |
139 | | - |
140 | | -```bash |
141 | | -curl -I http://127.0.0.1:3000/v1/images/characters/augusta/splash.webp |
142 | | -``` |
143 | | - |
144 | | -## Rate limiting |
145 | | - |
146 | | -Rate limiting is enforced by `@fastify/rate-limit` using **route-level tiers**. |
147 | | - |
148 | | -- List endpoints (`/v1/characters`, `/v1/weapons`): `RATE_LIMIT_LIST_MAX` per `RATE_LIMIT_WINDOW` |
149 | | -- Detail endpoints (`/v1/characters/:id`, `/v1/weapons/:id`): `RATE_LIMIT_DETAIL_MAX` per `RATE_LIMIT_WINDOW` |
150 | | -- Character image endpoints (`/v1/characters/:id/images*`): `RATE_LIMIT_IMAGE_MAX` per `RATE_LIMIT_WINDOW` |
151 | | -- Docs endpoints (`/docs`, `/documentation/*`): `RATE_LIMIT_DOCS_MAX` per `RATE_LIMIT_WINDOW` |
152 | | - |
153 | | -This API is designed to be public but discourage heavy usage (encourage self-hosting). |
154 | | - |
155 | | -## Validate data |
156 | | - |
157 | | -After updating `assets/` (or after manual edits): |
158 | | - |
159 | | -```bash |
160 | | -npm run validate-data |
161 | | -``` |
162 | | - |
163 | | -## Temporary demo page |
| 51 | +</details> |
164 | 52 |
|
165 | | -A temporary demo page exists at: |
166 | | -- `/demo/augusta.html` |
| 53 | +<details> |
| 54 | +<summary><strong>Environment Variables</strong></summary> |
167 | 55 |
|
168 | | -It is **disabled by default**. Enable it only for local testing: |
| 56 | +| Variable | Default | Description | |
| 57 | +|----------|---------|-------------| |
| 58 | +| `PORT` | `3000` | Server port | |
| 59 | +| `HOST` | `0.0.0.0` | Server host | |
| 60 | +| `DATA_ROOT` | `assets/data` | JSON data directory | |
| 61 | +| `IMAGES_ROOT` | `assets/images` | Image directory | |
| 62 | +| `RATE_LIMIT_WINDOW` | `1 minute` | Rate limit window | |
| 63 | +| `RATE_LIMIT_LIST_MAX` | `15` | List endpoints limit | |
| 64 | +| `RATE_LIMIT_DETAIL_MAX` | `60` | Detail endpoints limit | |
| 65 | +| `RATE_LIMIT_IMAGE_MAX` | `60` | Image endpoints limit | |
| 66 | +| `RATE_LIMIT_DOCS_MAX` | `120` | Docs endpoints limit | |
| 67 | +| `CORS_ALLOWED_ORIGINS` | — | Comma-separated browser allowlist | |
| 68 | +| `ENABLE_DEMO` | `false` | Enable `/demo/*` (local testing only) | |
169 | 69 |
|
170 | | -```bash |
171 | | -ENABLE_DEMO=true npm run dev |
172 | | -``` |
| 70 | +See `.env.example` for a complete template. |
173 | 71 |
|
174 | | -The demo HTML is loaded from `.local/demo` (gitignored) so it won’t be published with the repo. |
| 72 | +</details> |
175 | 73 |
|
176 | | -## Data sources |
| 74 | +<details> |
| 75 | +<summary><strong>Docker & Deployment</strong></summary> |
177 | 76 |
|
178 | | -The dataset in `assets/` is sourced from community resources (e.g. Prydwen and Fandom). |
179 | | - |
180 | | - |
181 | | -## Docker + deployment notes |
182 | | - |
183 | | -This repo can be deployed anywhere that runs Docker. |
184 | | - |
185 | | -### Build + run locally |
| 77 | +### Local Docker |
186 | 78 |
|
187 | 79 | ```bash |
188 | 80 | docker build -t wuwa-api . |
189 | 81 | docker run --rm -p 3000:3000 wuwa-api |
190 | 82 | ``` |
191 | 83 |
|
192 | | -The API is then available at: |
193 | | -- `http://127.0.0.1:3000/docs` |
194 | | - |
195 | | -### Deploy with Nginx Proxy Manager (recommended for home server) |
| 84 | +### With Nginx Proxy Manager |
196 | 85 |
|
197 | | -Use `docker-compose.yml` and connect the container to the same external Docker network as Nginx Proxy Manager. (GHCR image names must be lowercase; this repo uses `ghcr.io/projektcode/wuwa-api`.) |
198 | | - |
199 | | -Key points: |
200 | | -- Do **not** publish the API container port to the internet (no `ports:` section). |
201 | | -- Use `expose: 3000` and attach to the NPM network (this repo’s compose uses the external network name `nginx_proxy`). |
202 | | - |
203 | | -Run: |
| 86 | +Use `docker-compose.yml` on the same Docker network as NPM: |
204 | 87 |
|
205 | 88 | ```bash |
206 | | -# first start |
207 | | -docker compose up -d |
208 | | - |
209 | | -# updates (pull new image) |
210 | | -docker compose pull |
211 | | -docker compose up -d |
| 89 | +docker compose up -d # first start |
| 90 | +docker compose pull && docker compose up -d # updates |
212 | 91 | ``` |
213 | 92 |
|
214 | | -#### NPM UI setup |
215 | | - |
216 | | -In Nginx Proxy Manager → **Proxy Hosts** → **Add Proxy Host**: |
217 | | -- **Domain Names**: `api.custom.domain` |
218 | | -- **Scheme**: `http` |
219 | | -- **Forward Hostname / IP**: `wuwa-api` |
220 | | -- **Forward Port**: `3000` |
| 93 | +**NPM setup:** Add proxy host → Forward `wuwa-api:3000` → Enable SSL. |
221 | 94 |
|
222 | | -Then under **SSL**: |
223 | | -- request/choose a cert for `api.custom.domain` |
224 | | -- enable **Force SSL** |
| 95 | +### Hosting Options |
225 | 96 |
|
226 | | -After that, your API should be reachable at: |
227 | | -- `https://api.custom.domain/v1/characters/augusta` |
| 97 | +| Platform | Notes | |
| 98 | +|----------|-------| |
| 99 | +| Fly.io / Render / Railway | Simple deploy; free tier limits apply | |
| 100 | +| Google Cloud Run | Usage-based; scales to zero | |
| 101 | +| Small VPS / Oracle Free VM | Best for always-on self-host | |
228 | 102 |
|
229 | | -### Hosting recommendations |
| 103 | +### Scaling Images |
230 | 104 |
|
231 | | -Because this API serves a lot of image bytes, free tiers are often limited by bandwidth. |
| 105 | +Start with images in `assets/images`. If bandwidth becomes an issue, move to object storage + CDN (S3/R2) and update image URLs. |
232 | 106 |
|
233 | | -Good fits: |
234 | | -- Fly.io / Render / Railway / Koyeb (simple deploy; expect cold starts/limits on free tiers) |
235 | | -- Google Cloud Run (good free usage-based tier; container-based; scales to zero) |
236 | | -- A small VPS / Oracle Cloud Always Free VM (best for always-on self-host) |
| 107 | +</details> |
237 | 108 |
|
238 | | -### Images: cache vs object storage |
| 109 | +--- |
239 | 110 |
|
240 | | -Start simple: |
241 | | -- keep images in `assets/images` |
242 | | -- run with `NODE_ENV=production` so image responses can be cached aggressively |
| 111 | +## Data Sources |
243 | 112 |
|
244 | | -If bandwidth becomes a problem: |
245 | | -- move images to object storage + CDN (e.g., S3/R2 + a CDN) |
246 | | -- keep the JSON API here, but change image URLs to point at the CDN |
| 113 | +Dataset in `assets/` is sourced from community resources (Prydwen, Fandom). |
0 commit comments