TT Lab
Get started
Learn Learning paths Courses

Building an EAI Middleware Layer

Stand Up a Gateway with nginx — Keys, Quotas, Versions

Continue in TT Lab

Goal

Build the principles of an API gateway with nginx — path proxy, API key authentication (401), per-consumer quota (429), version routing and Sunset, request ID propagation and logging, and the upstream timeout (504).

Why it matters

Authentication, call rate limits and versions are needed identically by every API regardless of the business. If you implement them separately in each API server, they drift apart slightly, one consumer's runaway slows everyone, and nobody knows who is using the old version. The gateway controls these in one place, without business logic.

Steps

  1. Start two upstreams: nohup python3 /opt/lab/fixtures/eaimw/gateway/api.py --port 9601 --version v1 > /root/eaimw/gw/v1.out 2>&1 &, … --port 9602 --version v2 …. Call each once with curl and save the two lines of response JSON to /root/eaimw/gw/up.txt.
  2. Write /root/eaimw/gw/nginx.conf (a complete configuration file: pid /root/eaimw/gw/nginx.pid;, logs in /root/eaimw/gw/logs/, listen 8090;, upstreams 127.0.0.1:9601 and 127.0.0.1:9602). Pass /v1/ to v1 and /v2/ to v2 with the path as it is. Start it with nginx -p /root/eaimw/gw -c /root/eaimw/gw/nginx.conf.
  3. API key authentication: in /root/eaimw/gw/keys.map, put two lines, key-channel-7f3a channel; and key-partner-19c2 partner;, and include them with map $http_x_api_key $consumer. If there is no consumer (no key, wrong key), return 401 and a JSON body. To the upstream, pass X-Consumer: <소비자> (the consumer) and do not pass X-API-Key.
  4. Quota: limit_req_zone $consumer … rate=5r/s, limit_req … burst=5 nodelay and limit_req_status 429. If one consumer sends in a burst it gets 429, and other consumers must be unaffected.
  5. Version: pass /api/ to v2 if X-API-Version: 2, and to v1 if absent (map $http_x_api_version). To responses going to v1 (the /v1/ and the v1 of /api/), attach the header Sunset: Thu, 31 Dec 2026 23:59:59 GMT, and do not attach it to v2.
  6. Request ID: if an incoming X-Request-ID exists, pass it as is, and if not, pass $request_id to the upstream as X-Request-ID. Leave the access log in the format '<요청ID> <소비자> <상태> "<요청줄>"' (request ID, consumer, status, request line; for example log_format gw '$rid $consumer $status "$request"';) in /root/eaimw/gw/logs/access.log.
  7. With proxy_read_timeout 2s, if the upstream is slow (/v1/slow takes 5 seconds), return 504 in about 2 seconds.

Notes

Start the two upstream versions

Start v1 (9601) and v2 (9602) with api.py and save the two lines of response JSON to /root/eaimw/gw/up.txt.

api.py mirrors the request it received as JSON. Call the two ports with curl -s and collect them into one file with >>.

Split versions by path and pass through

With /root/eaimw/gw/nginx.conf, on 8090 pass /v1/→9601 and /v2/→9602 with the path as it is.

Two upstream blocks and two locations. If you do not attach a URI part to proxy_pass, the request path is passed through as it is.

Identify the consumer by API key

Decide the consumer with a map that includes /root/eaimw/gw/keys.map, and if there is none, 401 JSON; if there is, pass only X-Consumer and strip the key.

After map $http_x_api_key $consumer { default ""; include …; }, use if ($consumer = "") { return 401 '…'; } in the server. To strip a header, give proxy_set_header an empty value.

Apply a quota per consumer

Apply a per-consumer quota with limit_req_zone $consumer rate=5r/s, burst=5 nodelay and limit_req_status 429.

What the zone key is is everything. The default status code for exceeding the limit is 503, so you must change it to 429 for the caller to know "I sent too much."

Choose the version by header and announce the version to be retired

/api/ goes to v2 if X-API-Version: 2, and to v1 if absent. Attach a Sunset header to v1 responses.

Choose the upstream name with map $http_x_api_version $api_ver and pass it on with proxy_pass http://$api_ver; . If you also use a map for Sunset to give a value only for v1, an add_header with an empty value is not attached.

Connect the request ID and leave it in the log

Pass X-Request-ID as is if present and $request_id if not, and leave an access log in the format ' ""'.

You can use a variable in a map value, like map $http_x_request_id $rid { "" $request_id; default $http_x_request_id; }. Define log_format and attach that name to access_log.

Cut off a slow upstream quickly

With proxy_read_timeout 2s, cut /v1/slow (5 seconds) off with a 504 in about 2 seconds.

The default is 60 seconds. For an API where the caller gives up first, there is no reason for the gateway to hold on longer. Put it in every location in common.